Link to the code that reproduces this issue
https://github.com/ornakash/nextjs-hebrew-slug-repro
To Reproduce
git clone https://github.com/ornakash/nextjs-hebrew-slug-repro
cd nextjs-hebrew-slug-repro
pnpm install
pnpm build
pnpm start
Then in another shell:
curl -I http://localhost:3000/hello # → 200 OK
curl -I http://localhost:3000/מידע # → 500 Internal Server Error
The Hebrew slug is statically pre-rendered by generateStaticParams. The same 500 occurs when deployed to Vercel.
Current vs. Expected behavior
Current: any dynamic App Router route whose matched params value contains a non-ASCII character (Hebrew, Arabic, Chinese, emoji, ...) responds with 500 on every request. Server log:
TypeError: Invalid character in header content ["x-next-cache-tags"]
at p (.next/server/chunks/ssr/..._dist_esm_build_templates_app-page_....js)
code: 'ERR_INVALID_CHAR'
Root cause: Next.js writes the matched route path into the internal x-next-cache-tags HTTP header (used by the edge cache to index ISR responses by tag). HTTP headers are ASCII-only per RFC 7230, so any byte outside 0x00..0x7F in the path triggers Node's validateHeaderValue. The tag value is not percent-encoded or otherwise sanitized before being written to the header.
Expected: Next.js should encodeURI (or similarly ASCII-escape) the tag value before writing x-next-cache-tags, so non-ASCII paths don't crash ISR. Internal tag comparisons already round-trip through a normalized form, so encoding should be compatible with invalidation.
Workarounds that do NOT work
- URL-encoding slugs in
generateStaticParams — Next.js decodes and re-tags with the raw non-ASCII form at request time.
- Middleware
NextResponse.rewrite to an ASCII path (including double-encoded and base64url-encoded variants) — the cache tag is built from the original request URL, not the rewritten internal path.
- Monkey-patching
Headers.prototype.set/.append, http.OutgoingMessage.prototype.setHeader, http.ServerResponse.prototype.setHeader, .writeHead, .setHeaders from instrumentation.ts — all patches install but the throw originates from a bundled path in .next/server/chunks/ssr/..._dist_esm_build_templates_app-page_... that bypasses every JS-reachable setter.
- Patching
http.validateHeaderValue — export is a non-configurable getter in the Vercel Node runtime.
- External caches (CDN / Redis / custom
cacheHandler) — the origin response is already a 500; there is nothing valid to cache.
The only workaround found is export const dynamic = 'force-dynamic' on the affected route, which disables ISR entirely.
Provide environment information
Operating System:
Platform: win32
Arch: x64
Version: Windows 11 Enterprise
Binaries:
Node: 22.22.0
npm: 10.9.4
pnpm: 10.33.0
Relevant Packages:
next: 16.2.4 // Latest available version is detected (16.2.4).
react: 19.2.4
react-dom: 19.2.4
typescript: 5.7.3
Next.js Config:
output: N/A
Also reproduces on Vercel deployment; also reproduces with next@16.2.2.
Which area(s) are affected? (Select all that apply)
Dynamic Routes, Headers, Internationalization (i18n), Runtime
Which stage(s) are affected? (Select all that apply)
next start (local), Vercel (Deployed)
Additional context
Link to the code that reproduces this issue
https://github.com/ornakash/nextjs-hebrew-slug-repro
To Reproduce
git clone https://github.com/ornakash/nextjs-hebrew-slug-repro cd nextjs-hebrew-slug-repro pnpm install pnpm build pnpm startThen in another shell:
The Hebrew slug is statically pre-rendered by
generateStaticParams. The same 500 occurs when deployed to Vercel.Current vs. Expected behavior
Current: any dynamic App Router route whose matched
paramsvalue contains a non-ASCII character (Hebrew, Arabic, Chinese, emoji, ...) responds with 500 on every request. Server log:Root cause: Next.js writes the matched route path into the internal
x-next-cache-tagsHTTP header (used by the edge cache to index ISR responses by tag). HTTP headers are ASCII-only per RFC 7230, so any byte outside0x00..0x7Fin the path triggers Node'svalidateHeaderValue. The tag value is not percent-encoded or otherwise sanitized before being written to the header.Expected: Next.js should
encodeURI(or similarly ASCII-escape) the tag value before writingx-next-cache-tags, so non-ASCII paths don't crash ISR. Internal tag comparisons already round-trip through a normalized form, so encoding should be compatible with invalidation.Workarounds that do NOT work
generateStaticParams— Next.js decodes and re-tags with the raw non-ASCII form at request time.NextResponse.rewriteto an ASCII path (including double-encoded and base64url-encoded variants) — the cache tag is built from the original request URL, not the rewritten internal path.Headers.prototype.set/.append,http.OutgoingMessage.prototype.setHeader,http.ServerResponse.prototype.setHeader,.writeHead,.setHeadersfrominstrumentation.ts— all patches install but the throw originates from a bundled path in.next/server/chunks/ssr/..._dist_esm_build_templates_app-page_...that bypasses every JS-reachable setter.http.validateHeaderValue— export is a non-configurable getter in the Vercel Node runtime.cacheHandler) — the origin response is already a 500; there is nothing valid to cache.The only workaround found is
export const dynamic = 'force-dynamic'on the affected route, which disables ISR entirely.Provide environment information
Also reproduces on Vercel deployment; also reproduces with
next@16.2.2.Which area(s) are affected? (Select all that apply)
Dynamic Routes, Headers, Internationalization (i18n), Runtime
Which stage(s) are affected? (Select all that apply)
next start (local), Vercel (Deployed)Additional context
next@16.2.4confirmed brokennext@16.2.2confirmed broken/hello→ 200,/מידע→ 500)next startand Vercel production deployment, confirming it's a Next.js bug rather than a Vercel-runtime specific issue.