SPEC — Système de Sidebar Bimodale Starlight & Indexation Algolia Séparée
Cycle : DOC → SPEC → CODE → TEST → INTÉG
Composant : Sidebar Bimodale Starlight & Recherche Algolia Séparée
Fichier :docs/L2_Presentation/astro/starlight/SPEC-Starlight-Dual-Sidebar-Algolia.md
Statut : SPEC
Date : 2026-10-12
1. Périmètre et Objectifs
Section titled “1. Périmètre et Objectifs”1.1. Objectifs
Section titled “1.1. Objectifs”- Fournir une navigation Starlight fluide et accueillante pour les visiteurs non authentifiés, affichant exclusivement les documents publics sans aucun lien verrouillé ni redirection intempestive.
- Fournir aux membres authentifiés via Better Auth un accès complet à l’ensemble de la documentation (publique et privée) avec un indicateur visuel clair (badge “Privé”).
- Assainir la pagination bas de page (
prev/next) pour éviter toute navigation accidentelle vers un document privé pour un utilisateur non connecté. - Rediriger la route racine
/docsvers la page de documentation publique principale au lieu d’un code 404. - Séparer physiquement les index de recherche Algolia DocSearch en deux index distincts (
publicetall) afin d’interdire toute fuite de contenu sensible via les clés de recherche publiques.
1.2. Contraintes Architecturales
Section titled “1.2. Contraintes Architecturales”- Utiliser l’API officielle Starlight
routeMiddlewareviadefineRouteMiddleware(@astrojs/starlight/route-data). - Ne pas modifier ni écraser les composants internes Starlight (
Sidebar.astro), afin de garantir la pérennité lors des mises à jour du framework. - Maintenir le principe de sécurité par défaut : tout document dont la visibilité n’est pas explicitement
'public'est considéré'private'. - Zéro fuite de données : aucun titre, slug ou extrait privé dans l’index Algolia public.
2. Spécification des Types et Contrats de Données
Section titled “2. Spécification des Types et Contrats de Données”2.1. Types Starlight Route Data
Section titled “2.1. Types Starlight Route Data”D’après la documentation officielle Starlight (@astrojs/starlight/route-data) :
export interface Badge { text: string; variant: 'note' | 'danger' | 'success' | 'caution' | 'tip' | 'default'; class?: string;}
export interface SidebarLink { type: 'link'; label: string; href: string; isCurrent: boolean; attrs: Record<string, string | number | boolean | undefined>; badge?: Badge; autogenerate?: { directory: string };}
export interface SidebarGroup { type: 'group'; label: string; entries: Array<SidebarLink | SidebarGroup>; collapsed: boolean; badge?: Badge; autogenerate?: { directory: string };}
export type SidebarEntry = SidebarLink | SidebarGroup;
export interface StarlightRouteData { sidebar: SidebarEntry[]; pagination: { prev?: SidebarLink; next?: SidebarLink; }; entry: { id: string; slug: string; data: { title: string; description?: string; visibility?: 'public' | 'private'; [key: string]: unknown; }; }; [key: string]: unknown;}2.2. Contexte d’Exécution SSR
Section titled “2.2. Contexte d’Exécution SSR”Dans le pipeline Astro SSR :
apps/web/src/middleware.tss’exécute en premier :- Extrait le token de session Better Auth depuis les cookies (
better-auth.session_token). - Valide la session et peuple
context.locals.useravec l’utilisateur authentifié (ounull/undefined).
- Extrait le token de session Better Auth depuis les cookies (
- Starlight initialise
context.locals.starlightRoute. - Le middleware de route Starlight (
apps/web/src/starlightRouteData.ts) s’exécute avant le rendu des composants :- Lit
context.locals.userpour déterminer si la requête est authentifiée. - Modifie par mutation contrôlée
context.locals.starlightRoute.sidebaretcontext.locals.starlightRoute.pagination.
- Lit
3. Spécification de l’Algorithme de Filtrage de la Sidebar
Section titled “3. Spécification de l’Algorithme de Filtrage de la Sidebar”3.1. Résolution de la Visibilité depuis l’URL (href)
Section titled “3.1. Résolution de la Visibilité depuis l’URL (href)”Pour chaque lien de navigation avec href (ex. /docs/l1_contenu/sanity/visual-editing/... ou /gettingstarted/01-setup-local/) :
- Normaliser le chemin en retirant les slashes initiaux/finaux.
- Mapper vers l’identifiant de la collection
docs:- Ex.
/docs/l1_contenu/sanity/.../->l1_contenu/sanity/... - Ex.
/gettingstarted/01-setup-local/->gettingstarted/01-setup-local
- Ex.
- Vérifier la visibilité du document :
- Requêter la collection de documents via
getCollection('docs')ougetEntry('docs', id). - Si
visibility === 'public'-> Visibilité Publique. - Sinon (ou en cas d’erreur/absence) -> Visibilité Privée (Secure by default).
- Requêter la collection de documents via
3.2. Filtrage Récursif des Éléments (filterSidebar)
Section titled “3.2. Filtrage Récursif des Éléments (filterSidebar)”Pour un utilisateur non authentifié (!locals.user) :
function filterSidebarItems( items: SidebarEntry[], visibilityMap: Map<string, 'public' | 'private'>): SidebarEntry[] { const result: SidebarEntry[] = [];
for (const item of items) { if (item.type === 'link') { const isPublic = isHrefPublic(item.href, visibilityMap); if (isPublic) { result.push(item); } } else if (item.type === 'group') { // Filtrer récursivement les sous-éléments const filteredEntries = filterSidebarItems(item.entries, visibilityMap); // Élagage : ne conserver le groupe que s'il contient au moins un élément visible if (filteredEntries.length > 0) { result.push({ ...item, entries: filteredEntries, }); } } }
return result;}3.3. Décoration pour Utilisateur Authentifié
Section titled “3.3. Décoration pour Utilisateur Authentifié”Pour un utilisateur authentifié (locals.user présent) :
- Tous les éléments de navigation sont conservés.
- Pour chaque
SidebarLink, si le document associé est privé (visibility !== 'public'), un badge est ajouté :if (!item.badge) {item.badge = {text: 'Privé',variant: 'caution',};}
3.4. Assainissement de la Pagination (pagination)
Section titled “3.4. Assainissement de la Pagination (pagination)”Starlight génère les liens prev et next basés sur l’ordre de la sidebar :
- Pour un visiteur non authentifié :
- Si
pagination.prevpointe vers un document privé ->pagination.prev = undefined. - Si
pagination.nextpointe vers un document privé ->pagination.next = undefined.
- Si
- Pour un utilisateur authentifié :
- Les liens
prevetnextrestent inchangés.
- Les liens
4. Spécification du Déclencheur d’Authentification (Header Starlight)
Section titled “4. Spécification du Déclencheur d’Authentification (Header Starlight)”4.1. Problématique UX
Section titled “4.1. Problématique UX”Avec une sidebar et une pagination strictement épurées pour les visiteurs publics, un internaute ne rencontre plus jamais de redirection forcée vers /login. La démarche d’authentification doit donc être explicite et accessible en permanence sans perturber la lecture.
4.2. Composant de Surcharge HeaderAuth.astro
Section titled “4.2. Composant de Surcharge HeaderAuth.astro”Starlight permet de surcharger le composant de barre d’outils supérieure SocialIcons situé dans le Header, à droite du champ de recherche et à gauche du sélecteur de thème.
-
Enregistrement dans la configuration (
apps/web/astro.config.mjs) :starlight({components: {SocialIcons: './src/components/starlight/HeaderAuth.astro',},}) -
Logique du composant
src/components/starlight/HeaderAuth.astro:- Réutilise le composant natif :
import Default from '@astrojs/starlight/components/SocialIcons.astro'; - Détecte l’état d’authentification via
Astro.locals.user. - Visiteur Public (
!locals.user) :- Affiche un bouton d’action
Connexionmenant à/login?callbackURL=${encodeURIComponent(Astro.url.pathname)}. - Permet une redirection post-connexion exacte vers la documentation en cours de consultation.
- Affiche un bouton d’action
- Membre Authentifié (
locals.userprésent) :- Affiche un badge discret contenant l’email de l’utilisateur (
user.email). - Affiche un bouton
DéconnexiondéclenchantauthClient.signOut()côté client, suivi d’un rechargement de la page pour repasser instantanément en vue publique.
- Affiche un badge discret contenant l’email de l’utilisateur (
- Rend les icônes sociales d’origine via
<Default><slot /></Default>.
- Réutilise le composant natif :
5. Spécification de la Route Racine /docs
Section titled “5. Spécification de la Route Racine /docs”5.1. Problème Actuel
Section titled “5.1. Problème Actuel”Dans apps/web/src/pages/[slug].astro :
const reservedStarlightSlugs = ['docs', 'gettingstarted', 'getting-started'];if (reservedStarlightSlugs.includes(slug?.toLowerCase())) { return new Response(null, { status: 404 });}L’accès à http://localhost:8787/docs déclenche un code HTTP 404.
5.2. Comportement Spécifié
Section titled “5.2. Comportement Spécifié”Dans apps/web/src/pages/[slug].astro :
- Si
slugfait partie des slugs réservés :- Pour
docs: Rediriger viaAstro.redirect('/docs/gettingstarted/01-setup-local/', 302)(ou le document d’accueil public convenu). - Pour
gettingstarted/getting-started: Rediriger vers/gettingstarted/01-setup-local/.
- Pour
- Dans
apps/web/src/middleware.ts:- La requête
/docsou/docs/est autorisée publiquement pour permettre la redirection fluide.
- La requête
6. Spécification de la Séparation des Index Algolia DocSearch
Section titled “6. Spécification de la Séparation des Index Algolia DocSearch”6.1. Problématique de Sécurité
Section titled “6.1. Problématique de Sécurité”Algolia DocSearch requiert une clé PUBLIC_ALGOLIA_SEARCH_API_KEY injectée dans le code JavaScript du navigateur. Si des enregistrements privés sont indexés dans un index partagé avec un simple tag visibility: 'private', n’importe quel internaute peut appeler directement l’API REST Algolia sans filtre et aspirer l’ensemble des titres, sous-titres, extraits et blocs de code privés.
6.2. Stratégie du Double Index
Section titled “6.2. Stratégie du Double Index”Deux index physiquement isolés sont créés et alimentés :
| Index | Variables d’Environnement | Contenu autorisé | Audience |
|---|---|---|---|
| Index Public | PUBLIC_ALGOLIA_INDEX_NAME_PUBLIC (défaut : amn_docs_public) |
Uniquement documents avec visibility: 'public' |
Tout visiteur anonyme |
| Index Membre | PUBLIC_ALGOLIA_INDEX_NAME_ALL (défaut : amn_docs_all) |
Documents publics + documents privés | Utilisateurs connectés Better Auth |
6.3. Spécification du Script d’Indexation (index-algolia-docs.ts)
Section titled “6.3. Spécification du Script d’Indexation (index-algolia-docs.ts)”Le script de génération des records :
- Parcourt tous les fichiers
.mdet.mdxdeapps/web/src/content/docs/. - Extrait le frontmatter et détermine la visibilité (
publicouprivate). - Découpe le contenu en fragments DocSearch standard (
lvl0,lvl1,lvl2,lvl3,content,url). - Construit deux ensembles de données :
publicRecords: filtrerecord.visibility === 'public'allRecords: l’intégralité des enregistrements
- Envoie
publicRecordsà l’indexPUBLIC_ALGOLIA_INDEX_NAME_PUBLIC. - Envoie
allRecordsà l’indexPUBLIC_ALGOLIA_INDEX_NAME_ALL. - Configure les paramètres de pertinence et de recherche (
searchableAttributes,customRanking,highlightPreTag) sur les deux index de manière identique.
6.4. Sélection de l’Index dans docsearch.ts
Section titled “6.4. Sélection de l’Index dans docsearch.ts”Dans apps/web/src/config/docsearch.ts :
- Détection de l’état d’authentification :
- Vérification de la présence du cookie
better-auth.session_tokencôté client (ou lecture d’un attribut de données injecté sur<html>/<body>lors du SSR). - Si session active -> utiliser
PUBLIC_ALGOLIA_INDEX_NAME_ALL. - Si session inactive -> utiliser
PUBLIC_ALGOLIA_INDEX_NAME_PUBLIC.
- Vérification de la présence du cookie
7. Plan de Test et Critères d’Acceptation (TEST)
Section titled “7. Plan de Test et Critères d’Acceptation (TEST)”7.1. Critères d’Acceptation Visiteur Public (Non Authentifié)
Section titled “7.1. Critères d’Acceptation Visiteur Public (Non Authentifié)”- Accès
/docs: Une requête GET surhttp://localhost:8787/docsredirige vers une page publique valide (200 OK) sans demander de connexion. - Déclencheur d’Authentification : Le bouton « Connexion » est visible dans la barre supérieure (Header). Au clic, il renvoie vers
/login?callbackURL=.... - Sidebar épurée :
- La sidebar ne contient AUCUN document privé.
- Aucun groupe vide n’est affiché.
- Tous les liens cliquables mènent à des pages avec code 200 OK (jamais 302 vers
/login).
- Pagination sécurisée : Les boutons “Page précédente” et “Page suivante” ne pointent jamais vers un document privé.
- Recherche Algolia :
- La recherche interroge l’index public.
- Aucune recherche ne retourne de résultat issu d’un document privé (ex: “SANITY_PROJECT_ID”, “Cloudflare Secret”, etc.).
7.2. Critères d’Acceptation Membre Authentifié (Connecté)
Section titled “7.2. Critères d’Acceptation Membre Authentifié (Connecté)”- État du Header : Le Header affiche le badge de l’utilisateur (email) ainsi que le bouton « Déconnexion ». Le clic sur « Déconnexion » déconnecte l’utilisateur et recharge la page en mode public.
- Sidebar complète :
- La sidebar affiche tous les documents (publics et privés).
- Les documents privés affichent un badge visuel distinct “Privé”.
- Navigation fluide : Les documents privés s’affichent correctement en 200 OK.
- Recherche Algolia :
- La recherche interroge l’index complet.
- Les résultats incluent les documents privés avec leurs ancres.
7.3. Commandes de Validation Requises
Section titled “7.3. Commandes de Validation Requises”pnpm --filter amn-astro build: compilation SSR sans erreur TypeScript ni rupture de contrat Starlight.task test:better-auth/vitest run: tests unitaires d’authentification opérationnels.task dev:wrangler: vérification manuelle via cURL et navigateur.
8. Fichiers Concernés pour la Phase 2 (CODE)
Section titled “8. Fichiers Concernés pour la Phase 2 (CODE)”apps/web/astro.config.mjs: Déclaration derouteMiddlewareet composant de surchargeSocialIcons: './src/components/starlight/HeaderAuth.astro'.apps/web/src/components/starlight/HeaderAuth.astro: Composant de bouton de connexion / déconnexion dans le Header.apps/web/src/starlightRouteData.ts: Création du middleware de route Starlight (defineRouteMiddleware).apps/web/src/pages/[slug].astro: Redirection propre de/docsvers la documentation publique.apps/web/scripts/index-algolia-docs.ts: Indexation double (public et complet).apps/web/src/config/docsearch.ts: Sélection dynamique de l’index selon l’authentification.GettingStarted/38-test-starlight-sidebar-dual-view.md: Guide d’instructions de test pour validation locale.