Add a sitemap page and tidy sitemap.xml: site order, x-default and build-date last-modified

This commit is contained in:
Alejandro Veras
2026-10-27 15:21:32 -04:00
parent 9a337239b2
commit ac82825cb5
8 changed files with 204 additions and 4 deletions
+2
View File
@@ -3,6 +3,8 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
cacheComponents: true, 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, partialPrefetching: true,
turbopack: { turbopack: {
rules: { rules: {
+76
View File
@@ -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>
);
}
+90
View File
@@ -2980,3 +2980,93 @@ html[lang="ar"] .nav a {
align-items: flex-start; 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
View File
@@ -6,22 +6,26 @@ export default function sitemap(): MetadataRoute.Sitemap {
const paths = [ const paths = [
"/", "/",
"/about", "/about",
"/contact",
"/services", "/services",
...serviceNav.map((s) => `/services/${s.slug}`), ...serviceNav.map((s) => `/services/${s.slug}`),
"/industries", "/industries",
...industryNav.map((s) => `/industries/${s.slug}`), ...industryNav.map((s) => `/industries/${s.slug}`),
"/contact",
"/sitemap",
]; ];
const url = (p: string) => SITE_URL + (p === "/" ? "" : p); 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) => return locales.flatMap((locale) =>
paths.map((p) => ({ paths.map((p) => ({
url: url(localize(p, locale)), url: url(localize(p, locale)),
lastModified, lastModified,
changeFrequency: "monthly" as const, changeFrequency: "monthly" as const,
priority: p === "/" ? 1 : p.split("/").length === 2 ? 0.8 : 0.6, 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))])) }, alternates: {
languages: { ...Object.fromEntries(locales.map((l) => [l, url(localize(p, l))])), "x-default": url(p) },
},
})), })),
); );
} }
+1
View File
@@ -50,6 +50,7 @@ export default async function SiteFooter() {
{ label: t.nav.allServices, href: localize("/services", locale) }, { label: t.nav.allServices, href: localize("/services", locale) },
{ label: t.nav.allIndustries, href: localize("/industries", locale) }, { label: t.nav.allIndustries, href: localize("/industries", locale) },
{ label: t.nav.contact, href: localize("/contact", locale) }, { label: t.nav.contact, href: localize("/contact", locale) },
{ label: t.sitemap.nav, href: localize("/sitemap", locale) },
], ],
}, },
]; ];
+9
View File
@@ -53,6 +53,15 @@ export const ar: UI = {
rights: "© 2026 AI Growth Technologies. جميع الحقوق محفوظة.", rights: "© 2026 AI Growth Technologies. جميع الحقوق محفوظة.",
cities: "دبي · هيوستن · ميامي · نيويورك", cities: "دبي · هيوستن · ميامي · نيويورك",
}, },
sitemap: {
nav: "خريطة الموقع",
title: "خريطة الموقع",
metaTitle: "خريطة الموقع | AI Growth Technologies",
intro: "جميع صفحات الموقع في مكان واحد، مرتبة حسب القسم.",
company: "الشركة",
home: "الرئيسية",
languages: "اللغات",
},
crumbs: { label: "مسار التنقل", home: "الرئيسية" }, crumbs: { label: "مسار التنقل", home: "الرئيسية" },
faqTitle: "الأسئلة الشائعة", faqTitle: "الأسئلة الشائعة",
office: { phone: "الهاتف", email: "البريد الإلكتروني" }, office: { phone: "الهاتف", email: "البريد الإلكتروني" },
+9
View File
@@ -53,6 +53,15 @@ export const en = {
rights: "© 2026 AI Growth Technologies. All rights reserved.", rights: "© 2026 AI Growth Technologies. All rights reserved.",
cities: "Dubai · Houston · Miami · New York", 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" }, crumbs: { label: "Breadcrumb", home: "Home" },
faqTitle: "Frequently Asked Questions", faqTitle: "Frequently Asked Questions",
office: { phone: "Phone", email: "Email" }, office: { phone: "Phone", email: "Email" },
+9
View File
@@ -53,6 +53,15 @@ export const es: UI = {
rights: "© 2026 AI Growth Technologies. Todos los derechos reservados.", rights: "© 2026 AI Growth Technologies. Todos los derechos reservados.",
cities: "Dubái · Houston · Miami · Nueva York", 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" }, crumbs: { label: "Ruta de navegación", home: "Inicio" },
faqTitle: "Preguntas frecuentes", faqTitle: "Preguntas frecuentes",
office: { phone: "Teléfono", email: "Correo" }, office: { phone: "Teléfono", email: "Correo" },