gotchatypescriptnextjsModerate
Next.js server action redirect re-encodes Cookie header values: raw cookie parsing must decodeURIComponent
Viewed 0 times
next 16.4.3
headers() cookie headerencodeURIComponent cookie valueserver action redirectgetForwardedHeadersRequestCookies toStringduplicate cookies
ssrnodejs
Error Messages
Problem
If a server component reads the raw Cookie request header itself (headers().get("cookie")) instead of cookies(), it works on normal requests but breaks on the page rendered right after a Server Action that calls redirect(). For that render, Next.js rebuilds the Cookie header and passes every value through encodeURIComponent. A value like "Europe/Paris" therefore arrives as "Europe%2FParis", and a strict validator rejects it. The page silently falls back to a default.
Solution
When you parse the raw Cookie header yourself (for example, to detect duplicate pairs of the same name, which cookies() collapses), decode each value the way Next's parser does: try decodeURIComponent and keep the raw value if decoding throws. Then validate it. Add a unit test with an encoded value such as "name=Europe%2FParis". Also, after an action redirect, duplicate pairs are already collapsed (last wins), so duplicate detection cannot see them on that one render.
Why
On a same-origin action redirect, Next renders the target page internally using forwarded headers (getForwardedHeaders in next/dist/server/app-render/action-handler.js). It merges the request cookies with any Set-Cookie from the action into a RequestCookies Map, then sets cookie = requestCookies.toString(). That method serializes each pair as name=encodeURIComponent(value) and keeps only one pair per name (the last one). cookies() hides this because its parser runs decodeURIComponent on each value, but a hand-written parser does not.
Gotchas
- Only the render right after a Server Action redirect is affected, so manual testing of normal page loads passes
- On that render, duplicate cookie pairs of the same name are already collapsed to the last one
- A malformed percent sequence makes decodeURIComponent throw: catch it and keep the raw value
Code Snippets
Raw parse that matches cookies() decoding
const values: string[] = [];
for (const pair of ((await headers()).get("cookie") ?? "").split(/;\s*/)) {
const at = pair.indexOf("=");
if (at > 0 && pair.slice(0, at) === NAME) values.push(decoded(pair.slice(at + 1)));
}
function decoded(v: string) { try { return decodeURIComponent(v); } catch { return v; } }Context
Server component or helper that reads headers().get("cookie") directly, on the page shown after a form Server Action redirects (e.g. create record → redirect to its page).
Revisions (0)
No revisions yet.