nextjs - 💡(How to fix) Fix Malformed Request URL causes 500 response

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…

Error Message

The malformed request should return 400 Bad Request, not 500 Internal Server Error. Error Handling console.error(err); console.error(err2); console.error(err); console.error(err2);

Fix Action

Fix / Workaround

I have a patch that fixes the issue. But I have not read in depth into nextjs internals or made any contributions to next before. If someone guides me a little I'm happy to make the PR.

Here is the patch

diff --git a/dist/esm/server/lib/router-server.js b/dist/esm/server/lib/router-server.js
index 95fb3da7fc5bef160b7e49d3d573d745eb6422a6..f0bb1d030e80fbe1d1e70684cd50b11a92010356 100644
--- a/dist/esm/server/lib/router-server.js
+++ b/dist/esm/server/lib/router-server.js
@@ -461,16 +461,22 @@ async function initialize(opts) {
             try {
                 let invokePath = '/500';
                 let invokeStatus = '500';
                 if (err instanceof DecodeError) {
                     invokePath = '/400';
                     invokeStatus = '400';
                 } else {
                     console.error(err);
                 }
                 res.statusCode = Number(invokeStatus);
                 return await invokeRender(parseUrlUtil(invokePath), invokePath, 0, {
                     invokeStatus: res.statusCode
                 });
             } catch (err2) {
+                if (err instanceof DecodeError) {
+                    res.statusCode = 400;
+                    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+                    res.end('Bad Request');
+                    return;
+                }
                 console.error(err2);
             }
diff --git a/dist/server/lib/router-server.js b/dist/server/lib/router-server.js
index f2b00575c0f5f888f1b2d20ed4f8cb8fd46533c5..2890d68ec4edc3443e490c35ce17f1213f4c0699 100644
--- a/dist/server/lib/router-server.js
+++ b/dist/server/lib/router-server.js
@@ -516,16 +516,22 @@ async function initialize(opts) {
             try {
                 let invokePath = '/500';
                 let invokeStatus = '500';
                 if (err instanceof _utils.DecodeError) {
                     invokePath = '/400';
                     invokeStatus = '400';
                 } else {
                     console.error(err);
                 }
                 res.statusCode = Number(invokeStatus);
                 return await invokeRender((0, _parseurl.parseUrl)(invokePath), invokePath, 0, {
                     invokeStatus: res.statusCode
                 });
             } catch (err2) {
+                if (err instanceof _utils.DecodeError) {
+                    res.statusCode = 400;
+                    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+                    res.end('Bad Request');
+                    return;
+                }
                 console.error(err2);
             }

Code Example

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:06 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6030
  Available memory (MB): 36864
  Available CPU cores: 12
Binaries:
  Node: 25.8.1
  npm: 11.11.0
  Yarn: N/A
  pnpm: 10.32.1
Relevant Packages:
  next: 16.3.0-canary.35 // Latest available version is detected (16.3.0-canary.35).
  eslint-config-next: N/A
  react: 19.2.6
  react-dom: 19.2.6
  typescript: 5.9.3
Next.js Config:
  output: N/A

---

diff --git a/dist/esm/server/lib/router-server.js b/dist/esm/server/lib/router-server.js
index 95fb3da7fc5bef160b7e49d3d573d745eb6422a6..f0bb1d030e80fbe1d1e70684cd50b11a92010356 100644
--- a/dist/esm/server/lib/router-server.js
+++ b/dist/esm/server/lib/router-server.js
@@ -461,16 +461,22 @@ async function initialize(opts) {
             try {
                 let invokePath = '/500';
                 let invokeStatus = '500';
                 if (err instanceof DecodeError) {
                     invokePath = '/400';
                     invokeStatus = '400';
                 } else {
                     console.error(err);
                 }
                 res.statusCode = Number(invokeStatus);
                 return await invokeRender(parseUrlUtil(invokePath), invokePath, 0, {
                     invokeStatus: res.statusCode
                 });
             } catch (err2) {
+                if (err instanceof DecodeError) {
+                    res.statusCode = 400;
+                    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+                    res.end('Bad Request');
+                    return;
+                }
                 console.error(err2);
             }
diff --git a/dist/server/lib/router-server.js b/dist/server/lib/router-server.js
index f2b00575c0f5f888f1b2d20ed4f8cb8fd46533c5..2890d68ec4edc3443e490c35ce17f1213f4c0699 100644
--- a/dist/server/lib/router-server.js
+++ b/dist/server/lib/router-server.js
@@ -516,16 +516,22 @@ async function initialize(opts) {
             try {
                 let invokePath = '/500';
                 let invokeStatus = '500';
                 if (err instanceof _utils.DecodeError) {
                     invokePath = '/400';
                     invokeStatus = '400';
                 } else {
                     console.error(err);
                 }
                 res.statusCode = Number(invokeStatus);
                 return await invokeRender((0, _parseurl.parseUrl)(invokePath), invokePath, 0, {
                     invokeStatus: res.statusCode
                 });
             } catch (err2) {
+                if (err instanceof _utils.DecodeError) {
+                    res.statusCode = 400;
+                    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+                    res.end('Bad Request');
+                    return;
+                }
                 console.error(err2);
             }
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/Oudwins/nextjs-bugs/tree/bug/malformed-url-casuses-500

To Reproduce

npm run build npm run start curl --path-as-is -i 'http://localhost:3000/50%%2050%%' curl --path-as-is -i 'http://localhost:3000/50%25%2050%25'

Requirements: you must have a catch all route

Current vs. Expected behavior

The malformed request should return 400 Bad Request, not 500 Internal Server Error.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.5.0: Mon Apr 27 20:41:06 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6030
  Available memory (MB): 36864
  Available CPU cores: 12
Binaries:
  Node: 25.8.1
  npm: 11.11.0
  Yarn: N/A
  pnpm: 10.32.1
Relevant Packages:
  next: 16.3.0-canary.35 // Latest available version is detected (16.3.0-canary.35).
  eslint-config-next: N/A
  react: 19.2.6
  react-dom: 19.2.6
  typescript: 5.9.3
Next.js Config:
  output: N/A

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

Error Handling

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

Other (Deployed), next start (local)

Additional context

I have a patch that fixes the issue. But I have not read in depth into nextjs internals or made any contributions to next before. If someone guides me a little I'm happy to make the PR.

Here is the patch

diff --git a/dist/esm/server/lib/router-server.js b/dist/esm/server/lib/router-server.js
index 95fb3da7fc5bef160b7e49d3d573d745eb6422a6..f0bb1d030e80fbe1d1e70684cd50b11a92010356 100644
--- a/dist/esm/server/lib/router-server.js
+++ b/dist/esm/server/lib/router-server.js
@@ -461,16 +461,22 @@ async function initialize(opts) {
             try {
                 let invokePath = '/500';
                 let invokeStatus = '500';
                 if (err instanceof DecodeError) {
                     invokePath = '/400';
                     invokeStatus = '400';
                 } else {
                     console.error(err);
                 }
                 res.statusCode = Number(invokeStatus);
                 return await invokeRender(parseUrlUtil(invokePath), invokePath, 0, {
                     invokeStatus: res.statusCode
                 });
             } catch (err2) {
+                if (err instanceof DecodeError) {
+                    res.statusCode = 400;
+                    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+                    res.end('Bad Request');
+                    return;
+                }
                 console.error(err2);
             }
diff --git a/dist/server/lib/router-server.js b/dist/server/lib/router-server.js
index f2b00575c0f5f888f1b2d20ed4f8cb8fd46533c5..2890d68ec4edc3443e490c35ce17f1213f4c0699 100644
--- a/dist/server/lib/router-server.js
+++ b/dist/server/lib/router-server.js
@@ -516,16 +516,22 @@ async function initialize(opts) {
             try {
                 let invokePath = '/500';
                 let invokeStatus = '500';
                 if (err instanceof _utils.DecodeError) {
                     invokePath = '/400';
                     invokeStatus = '400';
                 } else {
                     console.error(err);
                 }
                 res.statusCode = Number(invokeStatus);
                 return await invokeRender((0, _parseurl.parseUrl)(invokePath), invokePath, 0, {
                     invokeStatus: res.statusCode
                 });
             } catch (err2) {
+                if (err instanceof _utils.DecodeError) {
+                    res.statusCode = 400;
+                    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+                    res.end('Bad Request');
+                    return;
+                }
                 console.error(err2);
             }

I accidentally created this same issue but with the repo being private. Fixed now... See: https://github.com/vercel/next.js/issues/94272#issuecomment-4584078567

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