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:
Claude committed 2026-09-05 10:41:53 +00:00
1 parent dd350038f0
commit a8adb9ae6a
21 files changed
+1113 -36

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
import { headers } from "next/headers";
import { serializeJsonLd } from "@/lib/seo";
/**
* Bloc de données structurées `application/ld+json`.
*
* Le contenu est sérialisé par `serializeJsonLd`, qui échappe `<`, `>` et `&` :
* les descriptions saisies par les adhérents ne peuvent pas fermer la balise.
* Le nonce de la CSP est recopié par cohérence, même si un bloc JSON-LD n'est
* jamais exécuté par le navigateur.
*/
export async function JsonLd({ data }: { data: unknown | unknown[] }) {
const nonce = (await headers()).get("x-nonce") ?? undefined;
const items = Array.isArray(data) ? data : [data];
return (
<>
{items.map((item, index) => (
<script
key={index}
type="application/ld+json"
nonce={nonce}
dangerouslySetInnerHTML={{ __html: serializeJsonLd(item) }}
/>
))}
</>
);
}