# Déploiement sur Vercel

Un **projet Vercel par instance**, même dépôt, même commande de build, variables différentes.
L'auto-hébergement (`pnpm build:<instance>`, artefact `standalone`) reste possible sans changement :
les deux chemins coexistent.

## Pourquoi la donnée est dans le dépôt

Vercel ne moissonne pas. La moisson demande **20 minutes, 2,1 Go et onze sources externes** ;
mise dans le chemin d'un déploiement, elle en fait un déploiement qui casse le jour où une source
tousse — et theses.fr est déjà en échec. Les JSON publiés sont donc versionnés :

| Versionné                 |    Taille | Pourquoi                                                |
| ------------------------- | --------: | ------------------------------------------------------- |
| `data/public/*/*.json`    | **18 Mo** | Lu au build par chaque page : sans eux, site vide       |
| `data/public/*/markdown/` |     60 Mo | **Non versionné** — rien ne le lit dans l'application   |
| `data/raw/`               |    2,1 Go | **Non versionné** — réponses brutes des sources         |
| Sortie de build           |    6,7 Go | **Non versionnée** — 287 905 fichiers, Vercel la refait |

Régénérer et pousser :

```bash
pnpm data:extract && pnpm data:build   # ou pnpm data:build seul si data/raw est à jour
pnpm data:freeze                        # fige la nouvelle référence
git add data/public tests/reference && git commit -m "Données régénérées"
```

Le hook de pré-push refuse un dépôt où la donnée manque, n'est pas suivie, ou diverge de la
référence figée.

## Le piège de l'index de recherche

L'index Pagefind se construit à partir du HTML des pages, donc **après** `next build`. Trois
pièges en ont découlé, chacun trouvé en déployant pour de vrai :

1. **`next build` fige le contenu de `public/`.** Un index écrit après n'est pas servi.
2. **Pagefind déduit la racine du site de l'emplacement de son propre script**, en coupant à
   `/pagefind/`. Servi depuis `/_next/static/pagefind/…`, il préfixait toutes ses URL de résultat
   par `/_next/static` : les titres restaient justes, **tous les liens menaient à un 404**. Le
   composant retire ce préfixe (`INDEX_ROOT`), et deux tests couvrent les deux emplacements.
3. **Sur Vercel, Next tourne avec un adaptateur** (`NEXT_ADAPTER_PATH`), qui change deux choses :
   - les pages pré-rendues sont rangées sous
     `.next/server/route-cache/APP_PAGE/<empreinte de la route>/$/<chemin>.html` au lieu de
     `.next/server/app/<chemin>.html` : Pagefind, pointé sur l'ancien dossier, ne trouvait rien
     (« Found 0 files matching … ») ;
   - la liste des fichiers à déployer est remise à l'adaptateur **à la fin de `next build`** : un
     index écrit ensuite, même dans `.next/static/`, est construit mais jamais servi (404, et la
     page de recherche affiche « index non construit »).

D'où la solution : **`scripts/pagefind-adapter.mjs` s'intercale devant l'adaptateur de Vercel.**
`onBuildComplete` est le seul moment où le HTML pré-rendu existe et où la liste des fichiers à
déployer est encore ouverte. L'adaptateur y rassemble les pages à leur chemin public, construit
l'index, le déclare sous `/pagefind/<instance>/`, puis passe la main à l'adaptateur de Vercel,
inchangé. Une seule passe de build.

<Mermaid
  title="Build Vercel : l'index ajouté par un adaptateur intercalé"
  chart={`flowchart LR
  VB["scripts/vercel-build.mjs<br/>garde NEXT_ADAPTER_PATH de Vercel,<br/>pose le nôtre"]
  NB["next build<br/>pages pré-rendues"]
  PA["pagefind-adapter.mjs<br/>onBuildComplete"]
  IDX["pagefind<br/>pages rassemblées depuis<br/>server/app ou route-cache"]
  DECL["outputs.staticFiles<br/>+ /pagefind/&lt;instance&gt;/…"]
  VA["Adaptateur Vercel<br/>déploie la liste complète"]
  VB --> NB --> PA --> IDX --> DECL --> VA`}
/>

L'index est servi à son emplacement standard, `/pagefind/<instance>/`, sans variable à poser ;
`next.config.ts` exclut déjà ce chemin de la réécriture vers `/fr`. `NEXT_PUBLIC_PAGEFIND_BASE`
reste disponible pour le servir ailleurs, par exemple depuis un stockage objet.

Vérifié en local le 29/10/2026 sur Paris 1, avec un adaptateur de test dans le rôle de celui de
Vercel : il recevait **0 fichier d'index** quand l'index était écrit après le build (et aussi
après une seconde passe de `next build`, essayée puis écartée : `public/` n'entre pas dans la
liste remise à l'adaptateur), **4 829** avec l'adaptateur intercalé.

## Mettre en place un projet, en ligne de commande

Un projet Vercel par site, tous sur le même dépôt : un par instance (racine du dépôt, `vercel.json`
de la racine), un pour la documentation (racine `docs/`, son propre `vercel.json`). Chaque push
sur `main` les reconstruit tous.

`scripts/vercel-project.mjs` crée le projet, règle son dossier racine et son framework, pose les
variables et le relie au dépôt GitHub. Il est rejouable : sur un projet existant, il met à jour les
réglages sans écraser l'adresse publique (sauf `--site-url`) ni le jeton de métriques.

```bash
npx vercel@65.7.0 login                     # une fois par poste, ou export VERCEL_TOKEN=…

pnpm vercel:project docs --domain lucarne.vicode.fr                 # → lucarne.vicode.fr
pnpm vercel:project saclay --domain saclay.lucarne.vicode.fr --demo # → saclay.lucarne.vicode.fr
pnpm vercel:project paris1 --domain paris1.lucarne.vicode.fr --demo # → paris1.lucarne.vicode.fr

# Options : --domain <hôte>, --demo, --site-url <url>, --name <projet>, --scope <équipe>, --deploy (premier déploiement tout de suite),
# --dry-run (affiche les commandes sans rien faire)
```

| Projet          | Racine  | Framework | Variables posées                                                                          |
| --------------- | ------- | --------- | ----------------------------------------------------------------------------------------- |
| Documentation   | `docs/` | Vite      | `DOCS_SITE_URL`                                                                           |
| Instance `<id>` | `.`     | Next.js   | `NEXT_PUBLIC_RESEARCH_INSTANCE`, `NEXT_PUBLIC_SITE_URL`, `METRICS_TOKEN` (généré, secret) |

Ajouter une nouvelle instance revient à créer `instances/<id>/`, publier ses données
(`pnpm data:build --instance <id>`), les commiter, puis `pnpm vercel:project <id>` : le script
refuse une instance dont les données ne sont pas dans le dépôt. La procédure complète est dans
[Marque blanche](/architecture/marque-blanche).

`--deploy` envoie le dossier local à Vercel. `.vercelignore` écarte ce qui ne doit jamais partir :
`data/raw` (2,1 Go, des noms de personnes), les sorties de build (`.next*`, plusieurs Go), les
`node_modules`. Sans `--deploy`, rien n'est envoyé depuis le poste : Vercel construit depuis Git.

Les mêmes réglages restent possibles à la main dans le tableau de bord : copier
`instances/<id>/vercel.env.example` dans Settings → Environment Variables.

## Ce qui reste à vérifier chez vous

**Le nombre de fichiers.** La sortie compte environ **142 500 fichiers** (pages, charges RSC,
statique) plus **23 787** pour l'index. C'est la limite la plus susceptible de bloquer, bien avant
la durée de build. Si elle est dépassée, sortir l'index vers un stockage objet retire d'un coup
23 787 fichiers et 110 Mo : `NEXT_PUBLIC_PAGEFIND_BASE` accepte déjà une URL absolue.

**La durée.** Moins de 4 minutes mesurées ici, pour 23 696 pages, plus l'indexation (moins
d'une minute). La limite de Vercel dépend du
plan.
