Files
flux-srl/src/app/sitemap.ts
T
davidherran fbfffb28d9 feat(analytics): activate GA4 (G-KQ1JRV3KN7) + GDPR privacy page + GSC support
Client provided the GA4 Measurement ID and approved the standard policy.

- Activate analytics: NEXT_PUBLIC_GA_ID set to the FLUX property
  G-KQ1JRV3KN7 in the env template, with the same value as the
  docker-compose build-arg fallback so it works out of the box on deploy.
  (GA Measurement IDs are public — they ship in page HTML — safe to commit.)
- New GDPR-compliant Privacy & Cookie Policy page at /[locale]/privacy
  (all 5 locales), linked from the consent banner. Includes a clearly
  marked template disclaimer for legal review and a TODO on the contact
  email. Added to sitemap.
- Consent banner now links via the locale-aware next-intl Link.
- Google Search Console: optional NEXT_PUBLIC_GSC_VERIFICATION env var
  emits the google-site-verification meta tag (Dockerfile arg +
  docker-compose wired). Empty by default.

Verified: build inlines G-KQ1JRV3KN7 into the client bundle; the 5
/privacy routes render; TypeScript clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:00:44 -05:00

107 lines
4.0 KiB
TypeScript

// src/app/sitemap.ts
// ─────────────────────────────────────────────────────────────────────────────
// Dynamic sitemap generated from Prisma data — emits one entry per locale per
// active page (home, applications, news articles, heritage, news hub).
//
// Auto-discoverable at /sitemap.xml, no Nginx config needed.
// Search engines re-crawl this on each visit; Next.js caches it for `revalidate`.
// ─────────────────────────────────────────────────────────────────────────────
import type { MetadataRoute } from "next";
import { prisma } from "@/lib/prisma";
const LOCALES = ["en", "it", "vec", "es", "de"] as const;
function baseUrl() {
return (process.env.NEXT_PUBLIC_APP_URL || "https://rf-flux.com").replace(/\/$/, "");
}
export const revalidate = 3600; // Re-generate sitemap once per hour
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const base = baseUrl();
const now = new Date();
const entries: MetadataRoute.Sitemap = [];
// ── Static routes ─────────────────────────────────────────────
const staticPaths = [
{ path: "", priority: 1.0, changeFrequency: "weekly" as const },
{ path: "/news", priority: 0.7, changeFrequency: "daily" as const },
{ path: "/heritage", priority: 0.6, changeFrequency: "monthly" as const },
{ path: "/team", priority: 0.6, changeFrequency: "monthly" as const },
{ path: "/privacy", priority: 0.3, changeFrequency: "yearly" as const },
];
for (const locale of LOCALES) {
for (const { path, priority, changeFrequency } of staticPaths) {
entries.push({
url: `${base}/${locale}${path}`,
lastModified: now,
changeFrequency,
priority,
alternates: {
languages: Object.fromEntries(
LOCALES.map((alt) => [alt, `${base}/${alt}${path}`])
),
},
});
}
}
// ── Application pages ─────────────────────────────────────────
try {
const applications = await prisma.application.findMany({
where: { isActive: true },
select: { slug: true, updatedAt: true },
});
for (const app of applications) {
for (const locale of LOCALES) {
entries.push({
url: `${base}/${locale}/applications/${app.slug}`,
lastModified: app.updatedAt,
changeFrequency: "weekly",
priority: 0.9,
alternates: {
languages: Object.fromEntries(
LOCALES.map((alt) => [alt, `${base}/${alt}/applications/${app.slug}`])
),
},
});
}
}
} catch (error) {
console.error("[sitemap] Failed to load applications:", error);
}
// ── News articles ─────────────────────────────────────────────
try {
const articles = await prisma.newsArticle.findMany({
where: { isActive: true },
select: { slug: true, updatedAt: true, publishedAt: true },
orderBy: { publishedAt: "desc" },
});
for (const article of articles) {
for (const locale of LOCALES) {
entries.push({
url: `${base}/${locale}/news/${article.slug}`,
lastModified: article.updatedAt,
changeFrequency: "monthly",
priority: 0.7,
alternates: {
languages: Object.fromEntries(
LOCALES.map((alt) => [alt, `${base}/${alt}/news/${article.slug}`])
),
},
});
}
}
} catch (error) {
console.error("[sitemap] Failed to load news articles:", error);
}
return entries;
}