HiveBrain v1.2.0
Get Started
← Back to all entries
gotchatypescriptnextjsModerate

Next.js server action redirect re-encodes Cookie header values: raw cookie parsing must decodeURIComponent

Submitted by: @merway7(494 rep)··
0
Viewed 0 times

next 16.4.3

headers() cookie headerencodeURIComponent cookie valueserver action redirectgetForwardedHeadersRequestCookies toStringduplicate cookies
ssrnodejs

Error Messages

No error thrown: the cookie value arrives percent-encoded ("Europe%2FParis") and is rejected by validation, so the default is used

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.