Files
PleinR/src/app/sitemap.ts
T
Claude a8adb9ae6a Référencement : métadonnées complètes, données structurées, sitemap et robots
- Layout : metadataBase, gabarit de titre « %s · Plein R », description,
  mots-clés, Open Graph / Twitter, robots, icônes, manifeste, viewport et
  couleur de thème ; JSON-LD Organization + WebSite (recherche annuaire).
- Chaque page publique pose titre, description (≤ 160 caractères) et
  canonique via pageMetadata() ; l'annuaire ignore ainsi ?q=. Fiche adhérent :
  titre « Nom · Catégorie à Ville », description dérivée, LocalBusiness avec
  adresse, contacts et horaires (OpeningHoursSpecification), BreadcrumbList.
  Annuaire : ItemList des adhérents. Association : BusinessEvent par
  rencontre à venir. Rencontres passées : fil d'Ariane.
- Nouvelles routes : robots.txt, sitemap.xml (pages statiques + adhérents
  actifs), manifest.webmanifest, image de partage 1200×630 générée avec le
  logo, page 404 dédiée (noindex).
- /backend, /login, /inscription/* et les fiches inactives en noindex.
- Landmarks <main> sur toutes les pages publiques, fil d'Ariane en <nav>.
- serializeJsonLd() échappe <, > et & : les saisies des adhérents ne peuvent
  pas fermer le bloc <script>. Tests unitaires dans tests/seo.test.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDbKbJrPWweXXYSxA8MXW7
2026-09-05 10:41:53 +00:00

41 lines
1.3 KiB
TypeScript

import type { MetadataRoute } from "next";
import { asc, eq } from "drizzle-orm";
import { db } from "@/db";
import { members } from "@/db/schema";
import { STATIC_ROUTES, absoluteUrl, memberPath } from "@/lib/seo";
import { publicBaseUrl } from "@/lib/seo-server";
export const dynamic = "force-dynamic";
/** /sitemap.xml : pages publiques + une entrée par adhérent actif. */
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const base = await publicBaseUrl();
// Pas de `lastModified` : sans date de mise à jour fiable en base, mieux vaut
// l'omettre qu'annoncer une valeur que les moteurs finiraient par ignorer.
const statics: MetadataRoute.Sitemap = STATIC_ROUTES.map((route) => ({
url: absoluteUrl(base, route.path),
changeFrequency: route.changeFrequency,
priority: route.priority,
}));
let rows: { id: number }[] = [];
try {
rows = await db
.select({ id: members.id })
.from(members)
.where(eq(members.status, "active"))
.orderBy(asc(members.id));
} catch {
// Base indisponible : on livre au moins les pages statiques.
}
const fiches: MetadataRoute.Sitemap = rows.map((m) => ({
url: absoluteUrl(base, memberPath(m.id)),
changeFrequency: "weekly",
priority: 0.7,
}));
return [...statics, ...fiches];
}