Next.js redirect() ve streamované SSR skončí v global-error místo navigace
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Nová route s podmíněným redirect() v server komponentě funguje v testech i lokálně, ale na produkci uživatel místo přesměrování vidí globální error stránku („Něco se pokazilo”). V konzoli minifikovaný React error, HTTP odpověď je 200 (ne 307) a v HTML těle je vidět digest: "NEXT_REDIRECT;replace;/cilova-url;...".
Root cause
Sekce “Root cause”Next.js 14 App Router streamuje SSR: u stránky s export const dynamic = "force-dynamic" se shell odešle klientovi hned (status 200) a page komponenta se dopočítává. Když redirect() vyhodí NEXT_REDIRECT až po odeslání shellu (typicky za await DB dotazem), nedá se už poslat 307 — digest se serializuje do streamu a má ho zpracovat klientský runtime. V některých konfiguracích (vlastní global-error.tsx, hydration mismatch) ho místo navigace chytí error boundary.
// Server komponenta — vypadá správně, na produkci padá do global-errorexport default async function Page({ params }) { const entity = await prisma.entity.findUnique({ where: { id: params.id } }); // ← shell už odešel if (entity.stage === "other") redirect(`/other/${entity.id}`); // ← NEXT_REDIRECT do streamu return <Detail entity={entity} />;}Fix
Sekce “Fix”Klientská redirect komponenta — router.replace v useEffect je nezávislý na streamování:
"use client";export function RedirectTo({ href }: { href: string }) { const router = useRouter(); useEffect(() => { router.replace(href); }, [href, router]); return <div>Přesměrování…</div>;}// V server komponentě místo redirect():if (entity.stage === "other") return <RedirectTo href={`/other/${entity.id}`} />;Pro interní aplikace ekvivalentní; pro veřejné SEO stránky raději redirect vyřešit v middleware (před streamem, skutečná 307).
Jak se tomu vyvarovat v jiných systémech
Sekce “Jak se tomu vyvarovat v jiných systémech”- Detection: grep
redirect(v server komponentách a zkontrolovat, zda je před nímawait; E2E test přes skutečný prohlížeč (curl 307 nestačí — bug se projeví jen ve streamu). - Anti-pattern:
await prisma...; if (...) redirect(...)veforce-dynamicstránce s vlastnímglobal-error.tsx. - Lepší přístup: routing rozhodnutí v middleware, nebo klientský
RedirectTo.
Sister bugs / související
Sekce “Sister bugs / související”Funkční testy (jest/API) tohle nechytí — projevuje se jen při hydrataci v prohlížeči. Další argument pro povinný vizuální Playwright průchod po deployi.