mirror of
https://github.com/R0m1k3/PleinR.git
synced 2026-10-23 07:08:05 +02:00
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
This commit is contained in:
21 files changed
+1113
-36
No files matched your search
@@ -0,0 +1,40 @@
|
||||
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];
|
||||
}
|
||||
Reference in new issue
Block a user