Skip to content

App Router: non-ASCII dynamic route params crash with ERR_INVALID_CHAR on x-next-cache-tags header #93142

@ornakash

Description

@ornakash

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions