Link to the code that reproduces this issue
https://github.com/ornakash/nextjs-hebrew-slug-repro
Deployed on Vercel: https://nextjs-hebrew-slug-repro.vercel.app
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?
Dynamic Routes, Headers, Internationalization (i18n), Runtime
Which stage(s) are affected?
next start (local), Vercel (Deployed)
Additional context
next@16.2.4 confirmed broken
next@16.2.2 confirmed broken
- Reproduces on both local
next start and Vercel production deployment, confirming it's a Next.js bug rather than a Vercel-runtime specific issue.
- Haven't bisected canary yet; happy to bisect if requested.
Link to the code that reproduces this issue
https://github.com/ornakash/nextjs-hebrew-slug-repro
Deployed on Vercel: https://nextjs-hebrew-slug-repro.vercel.app
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?
Dynamic Routes, Headers, Internationalization (i18n), Runtime
Which stage(s) are affected?
next start (local), Vercel (Deployed)Additional context
next@16.2.4confirmed brokennext@16.2.2confirmed brokennext startand Vercel production deployment, confirming it's a Next.js bug rather than a Vercel-runtime specific issue.