// app/api/revalidate/route.ts
import { revalidateTag, revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = request.headers.get('x-webhook-secret');
if (secret !== process.env.MY_WEBHOOK_SECRET) {
return NextResponse.json({ message: 'Token invalid' }, { status: 401 });
}
const body = await request.json();
const { tag, path } = body;
if (tag) {
revalidateTag(tag);
return NextResponse.json({ revalidated: true, tag, now: Date.now() });
}
if (path) {
revalidatePath(path);
return NextResponse.json({ revalidated: true, path, now: Date.now() });
}
return NextResponse.json({ message: 'Lipsește tag-ul sau path-ul' }, { status: 400 });
}Săptămâna trecută făceam code review pe un proiect Next.js App Router și am găsit export const revalidate = 60 trântit la nivel de layout global. Când am întrebat de ce, răspunsul a fost "să fim siguri că sunt datele proaspete". Nimic mai greșit.
De ce contează strategia de rendering
În App Router, caching-ul e agresiv și dacă nu îl controlezi, ajungi la două extreme: ori plătești găleți de bani pe serverless invocations la fiecare request (SSR inutil), ori le arăți clienților stocuri epuizate de acum trei zile.
La un magazin cu 45.000 de SKUs la care am lucrat anul trecut, am scăzut build time-ul de la 28 de minute la doar 90 de secunde pur și simplu mutând 95% din pagini de la SSG pur la On-Demand Revalidation. Nu mai generăm totul la build, ci doar paginile cele mai vizitate, iar restul se randează la cerere și se validează când din CMS vine o schimbare.
Diagrama de decizie pe 5 scenarii reale
Asta e matricea pe care o folosesc în echipă când începem un modul nou:
-
Pagini de Termeni și Condiții / Despre Noi (SSG pur)
- Schimbări: O dată pe an.
- Soluție: Page statică fără revalidare. Facem rebuild la deploy dacă se schimbă ceva. Zero cost de server.
-
Blog cu 500+ articole (ISR cu timp - Time-based)
- Schimbări: Ocazional, comentarii noi la câteva ore.
- Soluție:
revalidate = 3600(1 oră). Paginile rămân statice în CDN. Primul user după o oră declanșează revalidarea în fundal.
-
Magazin E-commerce - Prețuri și Stocuri (On-Demand Revalidation)
- Schimbări: Oricând se modifică stocul în ERP/PIM.
- Soluție: Fetch cu tag-uri (
next: { tags: ['product-123'] }). Revalidăm prin Webhook din backend doar când se modifică efectiv prețul sau stocul.
-
Pagina de Checkout / User Cart (SSR / Dynamic)
- Schimbări: La fiecare secundă, e per-utilizator.
- Soluție:
cache: 'no-store'sau apelare directă decookies()/headers(). Fără cache, randare directă la request.
-
Dashboard cu date financiare / Analytics (Client-side)
- Schimbări: Continuu, interacțiune intensă.
- Soluție: Layout static (shell), iar datele vin prin API direct în browser cu SWR sau TanStack Query. Nu obosești serverul Node pentru randare HTML la grafice interactive.
Revalidarea on-demand în acțiune
Dacă folosești Next.js 14 sau 15, cea mai curată abordare pentru e-commerce sau CMS-uri e revalidarea pe bază de tag-uri. Pui un tag pe request-ul de fetch, iar când se modifică ceva în backend, trimiți un POST scurt către un API Route din Next care apelează revalidateTag().
Trade-off-uri de care nu-ți spune nimeni
ISR-ul pe bază de timp are o mare problemă: Stale-While-Revalidate. Primul utilizator care accesează pagina expirată va vedea ÎNCĂ datele vechi, în timp ce serverul generează în fundal noua pagină. Dacă e vorba de prețul unui produs la reducere flash, e un dezastru.
On-Demand Revalidation e excelent, dar depinzi 100% de webhook-uri. Dacă pică webhook-ul din CMS sau ai o eroare în API Route, rămâi cu pagina veche la infinit. Ca strategie de apărare, eu pun de obicei un fallback cu time-based revalidation la 24 de ore (revalidate = 86400) chiar și pe paginile revalidate prin tag.
Voi ce folosiți cel mai des în producție? Mergeți pe ISR clasic cu timpi fixați sau ați trecut complet pe webhook-uri și revalidateTag?