nextjs - ✅(Solved) Fix dynamic segments with un-decodable entities → error 400 in dev, 500 in prod [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#92527Fetched 2026-04-09 07:49:56
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Author
Participants
Timeline (top)
labeled ×2cross-referenced ×1issue_type_added ×1referenced ×1

Error Message

Instead, dev resolves to a 400 error and prod resolves to a 500. You won't see this because the app throws an error: I expect no un-decodable entities at all to cause a 500 error. This gets picked up by error-logging apparatuses, causing noise. Dynamic Routes, Error Handling

Root Cause

You won't see this because the app throws an error:

foo/bar%A0

Fix Action

Fixed

PR fix notes

PR #92557: fix(routing): return 404 for dynamic segments with un-decodable percent-encoded sequences

Description (problem / solution / changelog)

What?

Dynamic routes like /foo/[slug] produce inconsistent errors when the URL contains a percent-encoded sequence that is not valid UTF-8 (e.g. /foo/%A0):

  • Development: HTTP 400 Bad Request
  • Production: HTTP 500 Internal Server Error

Why?

When route.match() in resolve-routes.ts is called for a dynamic route against a URL with an un-decodable segment, it calls decodeURIComponent('%A0') which throws a URIError. This was wrapped into a DecodeError in route-matcher.ts, but in certain production paths the error propagated unhandled, resulting in a 500.

More importantly, both 400 and 500 are wrong responses. A URL that cannot be decoded cannot match any real resource — the correct response is 404 Not Found.

How?

Catch DecodeError thrown by route.match() at both call sites in resolve-routes.ts:

  1. checkTrue() — the dynamic route loop used for filesystem-based route resolution. When a DecodeError is caught, continue to the next route, allowing the request to fall through to 404 handling.
  2. handleRoute() — the custom-route handler (headers, redirects, rewrites). When a DecodeError is caught, return (no match), so the loop continues.

A new e2e test (test/e2e/app-dir/dynamic-segment-invalid-encoding) covers both the broken case (%A0 → 404) and the valid case (%20 → 200).

Fixes #92527

Changed files

  • packages/next/src/server/lib/router-utils/resolve-routes.ts (modified, +25/-3)
  • test/e2e/app-dir/dynamic-segment-invalid-encoding/app/[slug]/page.tsx (added, +8/-0)
  • test/e2e/app-dir/dynamic-segment-invalid-encoding/app/layout.tsx (added, +11/-0)
  • test/e2e/app-dir/dynamic-segment-invalid-encoding/index.test.ts (added, +23/-0)

Code Example

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.6.0: Wed Nov  5 21:33:58 PST 2025; root:xnu-11417.140.69.705.2~1/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 10
Binaries:
  Node: 22.22.0
  npm: 10.9.4
  Yarn: 1.22.21
  pnpm: 8.15.9
Relevant Packages:
  next: 16.2.1-canary.26 // Latest available version is detected (16.2.1-canary.26).
  eslint-config-next: N/A
  react: 19.2.4
  react-dom: 19.2.4
  typescript: 5.9.3
Next.js Config:
  output: N/A
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/neptunus/nextjs-bad-decode

To Reproduce

  1. run pnpm run or pnpm build && pnpm start
  2. navigate to the front page of the demo
  3. click the links to see different test cases

-- OR --

In any running NextJs app, load any dynamic segment with %A0 in the slug—for example: example.com/foo/%A0

Current vs. Expected behavior

I expected the bad link, which contains an un-decodable entity, to resolve in the same way as the good ones; probably just escaping the percentage sign (as happens with valid encoded characters such as %20)

Loads fine and escapes entities:

foo/this%20is%20fine

Instead, dev resolves to a 400 error and prod resolves to a 500.

You won't see this because the app throws an error:

foo/bar%A0

I expect no un-decodable entities at all to cause a 500 error.

This gets picked up by error-logging apparatuses, causing noise.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.6.0: Wed Nov  5 21:33:58 PST 2025; root:xnu-11417.140.69.705.2~1/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 10
Binaries:
  Node: 22.22.0
  npm: 10.9.4
  Yarn: 1.22.21
  pnpm: 8.15.9
Relevant Packages:
  next: 16.2.1-canary.26 // Latest available version is detected (16.2.1-canary.26).
  eslint-config-next: N/A
  react: 19.2.4
  react-dom: 19.2.4
  typescript: 5.9.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Dynamic Routes, Error Handling

Which stage(s) are affected? (Select all that apply)

next dev (local), next build (local), next start (local), Vercel (Deployed), Other (Deployed)

Additional context

This problem can be dealt with by redirecting using proxy.ts, but that's a level of overhead I'd rather not have to handle.

extent analysis

TL;DR

The issue can be addressed by properly handling or escaping un-decodable entities in dynamic route slugs, potentially through Next.js configuration or custom routing logic.

Guidance

  • Investigate Next.js routing configuration options to handle special characters in slugs, such as using a custom getStaticPaths function or adjusting the router settings.
  • Consider implementing a middleware or a custom routing solution to catch and handle un-decodable entities before they cause errors.
  • Review the next.config.js file for any existing configurations that might be contributing to the issue, such as trailingSlash or basePath settings.
  • Look into using a library or utility function to properly encode or decode URL slugs, ensuring that un-decodable entities are handled gracefully.

Example

No specific code example can be provided without further details on the desired handling of un-decodable entities. However, a potential approach might involve creating a custom middleware function to preprocess route slugs and handle special characters.

Notes

The provided environment information and package versions may be relevant to the issue, but without more specific details on the desired behavior, it's challenging to provide a precise solution. The use of a canary version of Next.js (16.2.1-canary.26) might also be a factor, and checking the official Next.js documentation or issue tracker for similar problems could be helpful.

Recommendation

Apply a workaround by implementing custom routing logic or middleware to handle un-decodable entities, as upgrading to a fixed version is not explicitly implied in the issue description. This approach allows for a targeted solution without relying on potential fixes in future versions of Next.js.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

nextjs - ✅(Solved) Fix dynamic segments with un-decodable entities → error 400 in dev, 500 in prod [1 pull requests, 1 participants]