CLAIR DocsCLAIR Docs
  • clair.vote
  • GitHub
  • Nous soutenir
Nous soutenir
  • Accueil
  • Plateforme & Guide
  • Données & Base de Données
  • Explorateur API
CLAIR

Plateforme citoyenne de transparence politique. Données 100% publiques, 0% d'opinion.

Explorer

  • Députés
  • Sénateurs
  • Groupes politiques
  • Sujets
  • Dossiers législatifs
  • Scrutins
  • Agenda
  • Lobbying

Ressources

  • Nous soutenir
  • À propos
  • Méthodologie
  • Contact
  • Comprendre
  • Guide pratique
  • API ouverte

Légal

  • Mentions légales
  • Confidentialité
  • CGU

© 2026 CLAIR. Sources : Assemblée nationale · Sénat · HATVP · Ministère de l'Intérieur · DILA

Vue d'ensembleStack TechniqueVision & Valeurs
Référence API
    Vue d'ensembleLimites & CacheDossiers & AmendementsExemples de CodeLobbying HATVPParlementairesScrutins & Votes
Guide Développeur
    Guide ContributeurContribution & NormesDémarrage LocalDéploiement Production
Guide Développeur

Topologie de Déploiement en Production

🌐 Vue d'ensemble

ComposantHébergeurDéclenchementAdresse publique
Site (apps/web)Vercel, région cdg1 (Paris)Projet Vercelclair.vote
Documentation (docs)Vercel (configuration dans docs/vercel.json)Projet Verceldocs.clair.vote
API (apps/api)Railway, service « CLAIR »Déploiement depuis la branche master de accelaire/CLAIRapi.clair.vote
Batch nocturneRailway, service « Ingestion nightly »Cron 0 3 * * * (UTC), depuis master
Batch intradayRailway, service « Ingestion intraday »Cron 0 6-21/2 * * * (UTC), depuis master
PostgreSQL 17Railway, service « Postgres » (image postgres-ssl:17)
Redis 8.2Railway, service « Redis » (image redis:8.2.1)
DNSDynadot

L'API et les deux services d'ingestion joignent PostgreSQL par le réseau privé de Railway (*.railway.internal), comme l'API pour Redis. L'ingestion n'utilise pas Redis.

Une fusion sur master redéploie donc l'API et les deux services d'ingestion. Les migrations de base, elles, ne suivent pas (voir plus bas).


🖥️ Site : Vercel

La configuration versionnée est dans apps/web/vercel.json :

  • région cdg1 ;
  • en-têtes de sécurité sur toutes les pages (X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy) ;
  • cache au CDN de Vercel (Vercel-CDN-Cache-Control) : 10 min sur les pages de liste (/deputes, /scrutins, /sujets…), 1 h sur les pages de détail, avec stale-while-revalidate de 24 h ; 1 min sur /senatoriales-2026 (10 min de stale-while-revalidate), pour les résultats. Le navigateur, lui, revalide à chaque visite (max-age=0).

La version de Node vient du champ engines de apps/web/package.json (24.x), qui prime sur le réglage du projet Vercel : la preview d'une pull request se construit donc avec la version que déclare la branche.

Variables lues par le code du site :

VariableRôle
NEXT_PUBLIC_API_URLAdresse de l'API (défaut http://localhost:3001)
NEXT_PUBLIC_APP_URLAdresse publique du site (défaut https://clair.vote), pour les liens absolus et le SEO
NEXT_PUBLIC_PLAUSIBLE_DOMAINDomaine déclaré à Plausible ; sans lui, aucune mesure d'audience n'est chargée
NEXT_PUBLIC_PLAUSIBLE_SCRIPT_URLSScripts Plausible à charger, séparés par des virgules
CLAIR_INTERNAL_SECRETSecret partagé avec l'API, utilisé côté serveur uniquement (rendu serveur, sitemap, proxy /api/v1). Ne jamais le préfixer par NEXT_PUBLIC_

API_URL et CLAIR_INTERNAL_SECRET sont déclarées dans globalEnv de turbo.json : sans cela, Turborepo ne les transmettrait pas au build sur Vercel. Le formulaire de don HelloAsso est intégré par une adresse écrite dans le code (app/soutenir/page.tsx) : aucune variable n'est nécessaire.

📚 Documentation : Vercel

docs/vercel.json déclare le framework (vite) et la commande de build (pnpm build) ; la version de Node vient, comme pour le site, des engines de docs/package.json (24.x). Le build télécharge la spécification OpenAPI de l'API de production, régénère la navigation, construit le site statique, puis y injecte les balises Open Graph. La référence de l'API affichée par la doc est donc celle de l'API en production au moment du build. Comme une fusion sur master déploie la doc (Vercel, environ 30 s) en même temps que l'API (Railway, environ 1 min 30), le build de production de la doc attend que l'API ait changé de version : il lit le commit qu'elle sert sur /health (fourni par Railway dans RAILWAY_GIT_COMMIT_SHA) et patiente tant que c'est le commit précédent, 5 minutes au plus. Si l'API n'annonce pas de commit ou ne change pas à temps, la doc se construit avec la spécification en ligne, et le log du build le signale ; un nouveau build de la doc rattrape alors l'écart. Les previews de pull request n'attendent pas.

🌍 DNS

Zone gérée chez Dynadot :

Code
Type Nom Valeur A @ 76.76.21.21 (Vercel) CNAME www cname.vercel-dns.com (Vercel, redirige vers clair.vote) CNAME docs cname.vercel-dns.com (Vercel) CNAME api <domaine Railway du service API>

⚡ API : Railway

  • Image construite depuis le Dockerfile à la racine du dépôt (node:24-alpine, build pnpm, génération du client Prisma).
  • Démarrage : node --max-old-space-size=4096 dist/index.js, port 3001.
  • Healthcheck Railway : /health. Redémarrage en cas d'échec (ON_FAILURE).
  • Au démarrage en production, l'API préchauffe son cache Redis (voir Limites & Cache).

Variables lues par le code de l'API :

VariableRôle
DATABASE_URL, REDIS_URLBase et cache
NODE_ENVproduction en prod
PORT, HOSTÉcoute (défauts 3001 et 0.0.0.0)
LOG_LEVELNiveau de log
CORS_ORIGINOrigines autorisées en production, séparées par des virgules
CLAIR_INTERNAL_SECRETSecret partagé avec le site et l'ingestion (voir Limites & Cache)
FEEDBACK_DISCORD_WEBHOOK_URLNotification des retours anonymes
PUBLIC_APP_URLLien vers le site dans la notification de retour (défaut https://clair.vote)
PLAUSIBLE_EVENTS_URL, PLAUSIBLE_API_DOMAINMesure d'usage de l'API (voir Vue d'ensemble de l'API) ; inactive sans PLAUSIBLE_EVENTS_URL
API_URLHors production seulement : adresse annoncée par la spécification OpenAPI (défaut http://localhost:3001). En production, elle annonce toujours https://api.clair.vote
SENATORIALES_HORLOGERejouer une soirée électorale à une heure simulée ; ignorée en production

apps/api/railway.toml décrit ce déploiement mais n'est pas lu par Railway : la configuration fait foi dans le tableau de bord.


📥 Ingestion : Railway

Les deux services sont construits depuis services/ingestion/Dockerfile (node:24-alpine), seul à installer poppler-utils (pdftotext), indispensable pour lire les comptes rendus de commission de l'Assemblée. Le conteneur tourne avec TZ=Europe/Paris, mais les crons Railway s'expriment en UTC.

ServiceCron (UTC)Commande
Ingestion nightly0 3 * * * : 5 h à Paris en heure d'été, 4 h en heure d'hivernode --max-old-space-size=6144 services/ingestion/dist/cli.js smart-sync --all (le CMD du Dockerfile)
Ingestion intraday0 6-21/2 * * * : de 8 h à 22 h à Paris en heure d'éténode services/ingestion/dist/cli.js sync-intraday

Ni l'un ni l'autre n'est relancé en cas d'échec (politique de redémarrage NEVER) : un passage raté attend le suivant. Le batch nocturne ne sort d'ailleurs en erreur que si aucune source n'a pu être synchronisée (voir Orchestrateur d'ingestion).

Variables lues par le code de l'ingestion : DATABASE_URL, LOG_LEVEL, NODE_ENV, MISTRAL_API_KEY, TAVILY_API_KEY, API_URL et CLAIR_INTERNAL_SECRET (pour reconstruire le cache de la page d'accueil en fin de batch). Le service intraday n'utilise que DATABASE_URL, LOG_LEVEL et NODE_ENV.

services/ingestion/railway.toml et railway.intraday.toml recopient la configuration du tableau de bord pour la documenter ; Railway ne les lit pas.


🗄️ Base de données

  • Les migrations ne sont pas appliquées automatiquement au déploiement. Elles s'appliquent avec prisma migrate deploy, lancé depuis apps/api avec le DATABASE_URL de production (voir Modèle Prisma). Une migration dont dépend le code doit donc être appliquée avant la fusion sur master.
  • La base est mise à jour chaque nuit par le batch nocturne, et dans la journée par le batch intraday.

🔁 CI : GitHub Actions

.github/workflows/ci.yml, sur chaque push et pull request vers master (ou main), avec la version de Node lue dans .nvmrc :

  1. lint-and-test : installation, génération du client Prisma, pnpm lint, pnpm test, couverture de l'API envoyée à Codecov sur les pull requests. Le type-check est désactivé (erreurs préexistantes dans l'ingestion et le site).
  2. build : démarre PostgreSQL 16 et Redis 7, applique le schéma par prisma db push puis les triggers (db:triggers), construit et lance l'API, puis construit tout le monorepo. Le build du site prérend /sitemap.xml, qui interroge l'API : c'est ce qui impose une API réelle pendant la CI.

La CI ne déploie rien.

Démarrage Local
On this page
  • 🌐 Vue d'ensemble
  • 🖥️ Site : Vercel
  • 📚 Documentation : Vercel
  • 🌍 DNS
  • ⚡ API : Railway
  • 📥 Ingestion : Railway
  • 🗄️ Base de données
  • 🔁 CI : GitHub Actions