Runbooks & opérationsChecklist variables d'environnement : production Vercel

Checklist variables d'environnement : production Vercel

Compagnon de vercel-dashboard-checklist.md. La liste EXHAUSTIVE (184 vars) vit dans .env.example et le schéma typé dans src/env/server.ts (fail-soft : une var absente = "", l'app démarre mais la feature se dégrade EN…

Compagnon de vercel-dashboard-checklist.md. La liste EXHAUSTIVE (184 vars) vit dans .env.example et le schéma typé dans src/env/server.ts (fail-soft : une var absente = "", l'app démarre mais la feature se dégrade EN SILENCE). Ce fichier ne liste que les vars dont l'absence a un impact opérationnel réel, avec ce qui casse et comment le voir.

Vérification rapide : vercel env ls production ou pnpm env:pull:production puis diff avec .env.example. Depuis l'audit du 11 juin 2026, toute var manquante côté pipeline se repère dans les logs via failureKind: "config_missing".

Tier 0 — L'app ne fonctionne pas sans

VarCe qui casse si absenteSignal
DATABASE_URL (+ alias Neon, auto-injectées)Tout.500 globaux
NEXTAUTH_SECRETSessions invalides, personne ne peut se connecterBoucle de login
RESEND_API_KEY + RESEND_DOMAINAuth entière (l'OTP à six chiffres est le SEUL provider — le magic link a été retiré, security-identity/0267) + tous les emailsProvider "Disabled" dans NextAuth
NEXT_PUBLIC_APP_URLURLs absolues fausses (liens d'invitation, callbacks, self-fetch)Liens cassés dans les emails
KV_REST_API_URL + KV_REST_API_TOKEN (auto Upstash)Cache /api/me, rate limiting (fallback mémoire par instance)rate-limit en mode dégradé

Tier 1 — L'argent

VarCe qui casseSignal
STRIPE_SECRET_KEYCheckout, billing portal, Connect, refundsgetStripeClient throw → 500 sur achat
STRIPE_WEBHOOK_SECRETAucun webhook traité → crédits achetés jamais livrés, plans jamais provisionnésSignatures rejetées dans les logs
NEXT_PUBLIC_STRIPE_PRO_/MAX_* (price IDs)Plan introuvable au checkoutErreur au clic "Upgrade"
AI_GATEWAY_API_KEYLe chat = le produit payant ne répond pluschat.stream.error avec failureKind: auth_failed/config_missing

Tier 2 — Le pipeline de données (la cause des "datas manquantes")

VarCe qui casseSignal
CRON_SECRETLes 71 crons déclarés dans vercel.json répondent tous 401 — plus aucun refresh, reset crédits, digest…cron.*.missing_secret / unauthorized
QSTASH_TOKEN + QSTASH_CURRENT_SIGNING_KEY + QSTASH_NEXT_SIGNING_KEYTous les jobs async (indexation stores, briefings, vitals, AEO, backfills). Les jobs lourds refusent désormais le fallback inline sur Verceljobs.enqueue.inline_fallback ou throw "QStash is required"
FIRECRAWL_API_KEYScraping JS-rendered (le maillon le plus riche des deep-scans) → records partielsSonde /admin/ai/intelligence/health + failureKind: config_missing
APIFY_TOKEN + APIFY_SHOPIFY_DATASET_ID + APIFY_*_ACTOR_IDAd Library (Meta/TikTok/Google/Pinterest) + dataset discovery → colonnes Ads videsSonde Apify du health panel
BROWSERBASE_API_KEY + BROWSERBASE_PROJECT_IDScans tracking navigateur (pixels, consent)Scans tracking en échec
GOOGLE_PAGESPEED_API_KEYVitals PSI (rapports performance stores)jobs.worker.failed sur fetch-psi
BLOB_READ_WRITE_TOKEN (auto Vercel Blob)Uploads images + rapports PSI brutsupload.failed
AD_LIBRARY_PROVIDERSélection du provider ads (défaut sain si absente)—

Tier 3 — Connecteurs & confort (dégradation propre, à brancher quand utilisés)

  • OAuth connecteurs : GOOGLE_CLIENT_ID/SECRET/REDIRECT_URI, META_APP_ID/SECRET/REDIRECT_URI, NOTION_*, FIGMA_*, le connecteur concerné affiche "non configuré".
  • Anti-bot : NEXT_PUBLIC_TURNSTILE_SITE_KEY (+ secret), sans elles, l'OTP repose sur rate-limit + lockout seuls.
  • Voice : HUME_API_KEY / HUME_SECRET_KEY / NEXT_PUBLIC_HUME_CONFIG_ID.
  • SEO data : AHREFS_API_KEY, DATAFORSEO_API_KEY, DATOS_API_KEY, colonnes SEO/traffic vides sinon.
  • Proxy scraping : BRIGHTDATA_*, la chaîne fetch-providers saute ce maillon.
  • Analytics : DATAFAST_* (la lecture admin exige DATAFAST_API_TOKEN et DATAFAST_WEBSITE_ID : le token seul ne désigne pas un site), CLICKHOUSE_URL.
  • Trustpilot : TRUSTPILOT_API_KEY + TRUSTPILOT_BUSINESS_UNIT_ID (Trustpilot Business, Integrations > API). Facultatives : la ligne « Rated … out of 5 » des pages agents (readTrustpilotSummary, src/lib/seo/trustpilot.ts) lit, dans cet ordre, l'API quand les deux clés sont posées et qu'elle répond, puis la note et le nombre d'avis qu'un admin a lus sur le profil public et saisis dans /admin/content/agent-status (section trustpilot de PlatformConfig, avec l'adresse du profil), puis rien. Une réponse valide de l'API fait foi, même sous le seuil : elle ne retombe pas sur une lecture manuelle plus ancienne. Dans les deux cas la ligne n'apparaît qu'à partir de cinq avis (TRUSTPILOT_MIN_REVIEWS) ; jamais de valeur par défaut ni d'estimation.
  • Growth → Postiz : POSTIZ_API_KEY (Public API). Sans elle, aucun bouton « Draft in Postiz » sur /admin/content/growth : le handoff manuel reste. POSTIZ_API_URL seulement pour un Postiz auto-hébergé.
  • Growth → Remotion : GITHUB_TOKEN avec actions: write sur BoostEcom/boostecom.app pour « Lancer un rendu » (dispatch de creative-render.yml). Côté GitHub, STUDIO_SOURCE_TOKEN est obligatoire et limité à la lecture de BoostEcom/Ecosystem : le workflow checkout le runtime Studio sur un SHA immuable puis ne persiste pas ce credential. BLOB_READ_WRITE_TOKEN reste optionnel (sans lui, artefact de run seulement). CREATIVE_RENDER_CALLBACK_SECRET et CREATIVE_RENDER_CALLBACK_URL règlent la ligne GenerationRequest quand elle existe.
  • Contenu → voix off : ELEVENLABS_API_KEY + ELEVENLABS_VOICE_ID sur Vercel aussi (services/creative/voiceover.ts) : c'est ce qui permet à une recrue de synthétiser une lecture depuis /ops en une action, le MP3 atterrissant sur Blob (BLOB_READ_WRITE_TOKEN au runtime). Sans elles, aucun bouton, et le cockpit nomme la valeur manquante.
  • Siège créatif → le tenant Fondateur : INTERNAL_ORG_SLUGS. Elle est structurante depuis l'ADR 0026, pas seulement tarifaire : l'organisation qu'elle nomme EST la plateforme, et sa boutique est la marque sur laquelle /ops/creative/studio s'ouvre. Absente, le siège ne se rabat sur aucune organisation (volontairement : l'ancien repli ouvrait la boutique personnelle du fondateur), il nomme ce qui manque et on ne produit rien depuis /ops. Elle décide aussi du tarif : sans elle, nos propres rendus sont comptés au markup client.
  • Ops : ADMIN_EMAIL, COST_ALERT_EMAIL (alertes coûts), GOOGLE_SITE_VERIFICATION/BING_SITE_VERIFICATION.
  • Dev console / flotte (docs/architecture/dev-console.md §8, item backlog/inbox/2723) :
    • GITHUB_FLEET_TOKEN — token d'écriture (repository_dispatch). Absent, le moteur Claude Code Action refuse (engine-not-configured) ; le deep link local et Cursor continuent. Ne jamais y mettre la valeur de GITHUB_TOKEN, qui est un credential de lecture pour l'ingestion.
    • GITHUB_FLEET_REPO — owner/name visé ; absente, défaut BoostEcom/boostecom.app.
    • GITHUB_WEBHOOK_SECRET — signature HMAC-SHA256 de /api/webhooks/github. Absente, toute livraison répond 401 (fail closed délibéré) : les AgentRun restent figés et aucune ligne de roadmap n'avance. Signal : fleet.github_webhook.rejected dans les logs, et la liste de livraisons côté GitHub.
    • CURSOR_API_KEY — moteur de secours, utilisé quand GitHub Actions ne démarre plus de jobs (cf. AGENTS.md). Absente, ce moteur est refusé.
  • Signaux de pilier (docs/architecture/pillar-signals.md, item backlog/commerce-systems/3081) : cinq bascules, qu'aucun agent n'allume ; décisions D1-D4 tranchées le 2026-09-27 (docs/ops/operator-decisions.md). Absentes, le comportement est celui décidé ci-dessous.
    • VITALS_SHOPIFY_RUM_ENABLED — pull quotidien ShopifyQL web_performance. Allumée seulement après la vérification S8a (requête live en 2026-07 + fixture).
    • GOOGLE_SEARCH_CONSOLE_ENABLED — décision D1 : oui, mais reste éteinte tant que la vérification Google de l'app OAuth et la mise à jour de la politique de confidentialité ne sont pas faites (scope sensible webmasters.readonly, requêtes gardées 16 mois).
    • TRACKING_SCHEDULED_RESCAN_ENABLED — décision D2 : allumée par défaut (absente = allumée). Cron tracking-rescan mensuel (le 2 du mois), plans payants, boutiques déjà scannées ; 0 ou false l'éteint (minutes Browserbase).
    • GEO_CITATION_RUNS_PER_PROMPT — décision D3 : absente, 3 sur un plan payant et 1 sinon ; un entier 1..5 surcharge les deux (dépense modèle).
    • PIXEL_REFERRER_CAPTURE_ENABLED — décision D4 : oui, mais reste éteinte tant que l'extension pixel n'envoie pas referrerHost (hors de ce dépôt ; hôte IA seul, soumis au consentement).

Règles d'hygiène

  1. Toute nouvelle var : déclarer dans src/env/server.ts (schéma typé) + .env.example + ce fichier si Tier 0-2.
  2. Rotation de clé : QStash a deux clés (CURRENT + NEXT) précisément pour la rotation sans coupure, utiliser les deux champs.
  3. Build vs runtime : Neon n'expose pas DATABASE_URL au build par défaut, c'est PRÉVU, le schema guard gère (voir CLAUDE.md §Schema sync).
  4. Après tout ajout : redéployer (les vars ne sont lues qu'au déploiement) puis vérifier /admin/ai/intelligence/health (sondes Apify/QStash/Firecrawl/CRT/CommonCrawl) et l'absence de failureKind: config_missing dans les logs.