Explorer
KNOW-PAT-137

Architecture cron sur Vercel Hobby : éviter timeout, persistance volatile et cache statique

Domaine
dev-general
Type
pattern
Priorité
P1

Parent : [[INDEX-DEV-GENERAL]]

Architecture cron sur Vercel Hobby : éviter timeout, persistance volatile et cache statique

Problème

Sur Vercel Hobby (offre gratuite), un cron job quotidien qui ingère des données externes échoue silencieusement pour 4 raisons corrélées :

  1. Timeout 10s : une seule fonction serverless traite trop de sources (RSS + scraping) en série. Vercel tue la fonction au-delà de 10s.
  2. Persistance volatile : le fallback memory écrit dans une variable Node.js locale. Le filesystem serverless est éphémère. Les données "persistées" disparaissent à la fin de l'invocation.
  3. Cache Next.js figé : les pages Server Components sans dynamic/revalidate sont statiques au build. Le front affiche toujours les données du build précédent.
  4. Syntaxe cron invalide : vercel.json accepte "path": "/api/cron?mode=daily", mais le paramètre de requête est ignoré ou mal interprété par le scheduler Vercel.

Solution

  1. Splitter le cron en endpoints dédiés (un par responsabilité) pour rester sous 10s chacun.
  2. Verrouiller une base externe (Postgres/Neon ou Supabase) et supprimer le fallback memory en production.
  3. Forcer le rendu dynamique avec export const dynamic = 'force-dynamic' et invalider le cache via revalidatePath() depuis le cron.
  4. Déclarer des chemins propres dans vercel.json sans query string.

Exemple

Correct

// vercel.json
{
  "crons": [
    { "path": "/api/cron/rss", "schedule": "0 6 * * *" },
    { "path": "/api/cron/arena", "schedule": "0 7 * * *" }
  ]
}
// app/api/cron/rss/route.ts
import { revalidatePath } from 'next/cache'
import { runScheduledIngestion } from '@/lib/scheduler'

export async function GET(request: NextRequest) {
  // ... auth check ...
  const result = await runScheduledIngestion('daily')
  revalidatePath('/')
  revalidatePath('/leaderboard')
  return NextResponse.json({ ok: true, items_ingested: result.items_ingested })
}
// lib/db.ts — fail-fast en production
function detectMode(): DbMode {
  if (process.env.DATABASE_URL) return 'postgres'
  const supaKey = process.env.SUPABASE_SERVICE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
  if (process.env.NEXT_PUBLIC_SUPABASE_URL && supaKey) return 'supabase'
  if (process.env.NODE_ENV === 'development') return 'memory'
  throw new Error('Aucune base de données configurée en production.')
}
// app/page.tsx
export const dynamic = 'force-dynamic'

Incorrect

// vercel.json — query string interdite
{
  "crons": [
    { "path": "/api/cron?mode=daily", "schedule": "0 6 * * *" }
  ]
}
// lib/db.ts — fallback silencieux qui fait perdre les données
function detectMode(): DbMode {
  if (process.env.DATABASE_URL) return 'postgres'
  if (process.env.NEXT_PUBLIC_SUPABASE_URL) return 'supabase'
  return 'memory' // ← données jetées à la fin de l'invocation
}
// app/api/cron/route.ts — tout en une seule fonction
try {
  await runScheduledIngestion('daily')     // 10 sources RSS
  await getArenaScores()                   // scraping
  await saveArenaScoresToSupabase(scores)  // DB
} catch { /* timeout à 10s, rien n'est persisté */ }

Pourquoi ça marche

  • Vercel Hobby impose un hard timeout de 10s par invocation. En divisant le travail, chaque fonction reste sous cette limite.
  • Le filesystem et la mémoire d'une fonction serverless sont recyclés entre deux invocations. Seule une base externe garantit la persistance.
  • Next.js 14 génère des Server Components statiques par défaut. dynamic = 'force-dynamic' force le SSR à chaque requête, et revalidatePath purge le Data Cache côté CDN.
  • Les Cron Jobs Vercel appellent l'endpoint avec un header Authorization implicite. Le path doit être strict, sans ?.

Quand ne PAS l'utiliser

  • Sur Vercel Pro/Enterprise (timeout 60s–900s), le split n'est pas toujours nécessaire.
  • Si tu utilises une Edge Function (export const runtime = 'edge'), la limite est de 30s mais la persistance locale reste impossible.
  • Si les données ingérées ne sont pas affichées sur le front (pure log), le revalidatePath est inutile.

Source

  • Projet : ai-hub
  • Contexte : Mise à jour quotidienne d'un classement IA (news RSS + scores Arena) sur Vercel Hobby
  • Validé le : 2026-05-12