Add a sitemap page and tidy sitemap.xml: site order, x-default and build-date last-modified
This commit is contained in:
@@ -3,6 +3,8 @@ import type { NextConfig } from "next";
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
cacheComponents: true,
|
||||
// The sitemap's last-modified date: the day this deploy was built.
|
||||
env: { BUILD_DATE: new Date().toISOString().slice(0, 10) },
|
||||
partialPrefetching: true,
|
||||
turbopack: {
|
||||
rules: {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { locales, localeNames, localize } from "@/i18n/config";
|
||||
import { industryLinks, serviceLinks, type NavLink } from "@/i18n/nav";
|
||||
import { getUI } from "@/i18n/server";
|
||||
import { localeAlternates } from "@/lib/metadata";
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const { locale, t } = await getUI();
|
||||
return { title: t.sitemap.metaTitle, description: t.sitemap.intro, alternates: localeAlternates("/sitemap", locale) };
|
||||
}
|
||||
|
||||
/** A readable index of every page, for visitors; search engines use /sitemap.xml. */
|
||||
export default async function SitemapPage() {
|
||||
const { locale, t } = await getUI();
|
||||
const groups: { title: string; links: NavLink[] }[] = [
|
||||
{
|
||||
title: t.sitemap.company,
|
||||
links: [
|
||||
{ label: t.sitemap.home, href: localize("/", locale) },
|
||||
{ label: t.nav.about, href: localize("/about", locale) },
|
||||
{ label: t.nav.contact, href: localize("/contact", locale) },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t.nav.services,
|
||||
links: [{ label: t.nav.allServices, href: localize("/services", locale) }, ...serviceLinks(t, locale)],
|
||||
},
|
||||
{
|
||||
title: t.nav.industries,
|
||||
links: [{ label: t.nav.allIndustries, href: localize("/industries", locale) }, ...industryLinks(t, locale)],
|
||||
},
|
||||
{
|
||||
title: t.sitemap.languages,
|
||||
links: locales.map((l) => ({ label: localeNames[l], href: localize("/sitemap", l) })),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="section sitemap" aria-labelledby="page-title">
|
||||
<div className="wrap">
|
||||
<nav className="crumbs" aria-label={t.crumbs.label}>
|
||||
<span>
|
||||
<Link href={localize("/", locale)}>{t.crumbs.home}</Link>
|
||||
<i aria-hidden="true">/</i>
|
||||
</span>
|
||||
<span aria-current="page">{t.sitemap.title}</span>
|
||||
</nav>
|
||||
<p className="eyebrow">{t.sitemap.title}</p>
|
||||
<h1 id="page-title" className="display h2">
|
||||
{t.sitemap.title}
|
||||
</h1>
|
||||
<p className="lead muted sitemap-intro">{t.sitemap.intro}</p>
|
||||
<div className="sitemap-grid">
|
||||
{groups.map((g) => (
|
||||
<div key={g.title} className="sitemap-group">
|
||||
<h2>
|
||||
{g.title} <span>{String(g.links.length).padStart(2, "0")}</span>
|
||||
</h2>
|
||||
<ul>
|
||||
{g.links.map((l) => (
|
||||
<li key={l.href}>
|
||||
<Link href={l.href} aria-current={l.href === localize("/sitemap", locale) ? "page" : undefined}>
|
||||
{l.label}
|
||||
<span className="arrow">→</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -2980,3 +2980,93 @@ html[lang="ar"] .nav a {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
Sitemap page
|
||||
============================================================ */
|
||||
.sitemap {
|
||||
padding-top: 152px;
|
||||
}
|
||||
|
||||
.sitemap h1 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.sitemap-intro {
|
||||
max-width: 46ch;
|
||||
margin: 24px 0 0;
|
||||
}
|
||||
|
||||
.sitemap-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
margin-top: 64px;
|
||||
}
|
||||
|
||||
.sitemap-group {
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--grafite);
|
||||
}
|
||||
|
||||
.sitemap-group h2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 0 20px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sitemap-group h2 span {
|
||||
color: var(--rosso);
|
||||
}
|
||||
|
||||
.sitemap-group ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sitemap-group a {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--grafite);
|
||||
font-size: 15px;
|
||||
color: var(--fumo);
|
||||
transition: color 0.3s var(--ease);
|
||||
}
|
||||
|
||||
.sitemap-group a .arrow {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s var(--ease);
|
||||
}
|
||||
|
||||
.sitemap-group a:hover,
|
||||
.sitemap-group a[aria-current] {
|
||||
color: var(--bianco);
|
||||
}
|
||||
|
||||
.sitemap-group a:hover .arrow {
|
||||
opacity: 1;
|
||||
color: var(--rosso);
|
||||
}
|
||||
|
||||
[dir="rtl"] .sitemap-group a .arrow {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.sitemap-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 599px) {
|
||||
.sitemap-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -6,22 +6,26 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const paths = [
|
||||
"/",
|
||||
"/about",
|
||||
"/contact",
|
||||
"/services",
|
||||
...serviceNav.map((s) => `/services/${s.slug}`),
|
||||
"/industries",
|
||||
...industryNav.map((s) => `/industries/${s.slug}`),
|
||||
"/contact",
|
||||
"/sitemap",
|
||||
];
|
||||
const url = (p: string) => SITE_URL + (p === "/" ? "" : p);
|
||||
const lastModified = new Date();
|
||||
// The day this deploy was built (set in next.config.ts), so dates only move when the site changes.
|
||||
const lastModified = process.env.BUILD_DATE;
|
||||
|
||||
return locales.flatMap((locale) =>
|
||||
paths.map((p) => ({
|
||||
url: url(localize(p, locale)),
|
||||
lastModified,
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: p === "/" ? 1 : p.split("/").length === 2 ? 0.8 : 0.6,
|
||||
alternates: { languages: Object.fromEntries(locales.map((l) => [l, url(localize(p, l))])) },
|
||||
priority: p === "/" ? 1 : p === "/sitemap" ? 0.3 : p.split("/").length === 2 ? 0.8 : 0.6,
|
||||
alternates: {
|
||||
languages: { ...Object.fromEntries(locales.map((l) => [l, url(localize(p, l))])), "x-default": url(p) },
|
||||
},
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ export default async function SiteFooter() {
|
||||
{ label: t.nav.allServices, href: localize("/services", locale) },
|
||||
{ label: t.nav.allIndustries, href: localize("/industries", locale) },
|
||||
{ label: t.nav.contact, href: localize("/contact", locale) },
|
||||
{ label: t.sitemap.nav, href: localize("/sitemap", locale) },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -53,6 +53,15 @@ export const ar: UI = {
|
||||
rights: "© 2026 AI Growth Technologies. جميع الحقوق محفوظة.",
|
||||
cities: "دبي · هيوستن · ميامي · نيويورك",
|
||||
},
|
||||
sitemap: {
|
||||
nav: "خريطة الموقع",
|
||||
title: "خريطة الموقع",
|
||||
metaTitle: "خريطة الموقع | AI Growth Technologies",
|
||||
intro: "جميع صفحات الموقع في مكان واحد، مرتبة حسب القسم.",
|
||||
company: "الشركة",
|
||||
home: "الرئيسية",
|
||||
languages: "اللغات",
|
||||
},
|
||||
crumbs: { label: "مسار التنقل", home: "الرئيسية" },
|
||||
faqTitle: "الأسئلة الشائعة",
|
||||
office: { phone: "الهاتف", email: "البريد الإلكتروني" },
|
||||
|
||||
@@ -53,6 +53,15 @@ export const en = {
|
||||
rights: "© 2026 AI Growth Technologies. All rights reserved.",
|
||||
cities: "Dubai · Houston · Miami · New York",
|
||||
},
|
||||
sitemap: {
|
||||
nav: "Sitemap",
|
||||
title: "Sitemap",
|
||||
metaTitle: "Sitemap | AI Growth Technologies",
|
||||
intro: "Every page on the site in one place, organized by section.",
|
||||
company: "Company",
|
||||
home: "Home",
|
||||
languages: "Languages",
|
||||
},
|
||||
crumbs: { label: "Breadcrumb", home: "Home" },
|
||||
faqTitle: "Frequently Asked Questions",
|
||||
office: { phone: "Phone", email: "Email" },
|
||||
|
||||
@@ -53,6 +53,15 @@ export const es: UI = {
|
||||
rights: "© 2026 AI Growth Technologies. Todos los derechos reservados.",
|
||||
cities: "Dubái · Houston · Miami · Nueva York",
|
||||
},
|
||||
sitemap: {
|
||||
nav: "Mapa del sitio",
|
||||
title: "Mapa del sitio",
|
||||
metaTitle: "Mapa del sitio | AI Growth Technologies",
|
||||
intro: "Todas las páginas del sitio en un solo lugar, organizadas por sección.",
|
||||
company: "Empresa",
|
||||
home: "Inicio",
|
||||
languages: "Idiomas",
|
||||
},
|
||||
crumbs: { label: "Ruta de navegación", home: "Inicio" },
|
||||
faqTitle: "Preguntas frecuentes",
|
||||
office: { phone: "Teléfono", email: "Correo" },
|
||||
|
||||
Reference in New Issue
Block a user