fbfffb28d9
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>
107 lines
4.0 KiB
TypeScript
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;
|
|
}
|