#99347·Next.js

Malformed percent-encoding in an App Router dynamic segment returns 500 instead of 400

Author: y-pakornCreated Sep 28, 2026Updated Sep 28, 2026
LabelsError HandlingDynamic Routes

Link to the code that reproduces this issue

https://github.com/y-pakorn/next-malformed-param-500

To Reproduce

  1. Clone the reproduction, then run npm install, npm run build and npm start.
  2. Request the dynamic route with a malformed percent-encoded segment:
bash
curl -o /dev/null -w '%{http_code}\n' http://localhost:3000/items/%E0%A4
curl -o /dev/null -w '%{http_code}\n' http://localhost:3000/items/100%

The app has one dynamic route, app/items/[slug]/page.tsx, and no pages directory.

Current vs. Expected behavior

Both requests return 500 Internal Server Error with a plain text body, and nothing is logged.

I expected 400 Bad Request. next dev returns 400 for the same URLs, and so does next start once the project has any Pages Router route. A valid slug such as /items/hello returns 200, and a malformed static path such as /%E0%A4 returns 404, so only dynamic segments are affected.

Provide environment information

bash
Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:12 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6050
  Available memory (MB): 24576
  Available CPU cores: 15
Binaries:
  Node: 26.3.0
  npm: 11.16.0
  Yarn: N/A
  pnpm: 12.4.1
Relevant Packages:
  next: 16.4.0-canary.51 // Latest available version is detected (16.4.0-canary.51).
  eslint-config-next: N/A
  react: 19.3.0
  react-dom: 19.3.0
  typescript: 7.0.2
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 start (local), Other (Deployed)

Additional context

This also reproduces on 16.3.6 and 16.3.5. We hit it in production on a self-hosted output: "standalone" deployment running 16.3.5.

For these URLs, getRouteMatcher throws DecodeError('failed to decode param'), error code E528. base-server catches DecodeError and renders /_error with status 400, and router-server maps it to a 400 as well. An App Router-only build has no _error page, though. Its .next/server/pages directory contains only 404.html and 500.html. After adding one Pages Router route, the build includes _error.js and the same requests return 400. My guess is that the 400 response depends on the Pages Router error page and ends in a bare 500 when the build doesn't include one.

This looks like a regression of #43303. The test added for that issue in test/integration/dynamic-routing only covers the Pages Router.