From bda5dc3ba3f146b1835502c1287225e9652921a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20M=C3=A5rtensson?= Date: Thu, 1 May 2025 00:38:05 +0200 Subject: [PATCH] fix: replace original request body after middleware execution (#77662) In https://github.com/vercel/next.js/pull/77553 we fixed reading the request body in middleware using the `nodejs` runtime. However, this caused issues with subsequent reads like in server actions. In sandbox, [we run](https://github.com/vercel/next.js/blob/1e62ce2c61048ddc0297f1a4f268894541975521/packages/next/src/server/web/sandbox/sandbox.ts#L146-L148) `.finalize()` after middleware is executed so we should do the same here. Fixes https://github.com/vercel/next.js/issues/77646 --------- Co-authored-by: JJ Kasper --- packages/next/src/server/next-server.ts | 25 +++++++++++++++---- ...-action-form-state-node-middleware.test.ts | 3 +++ .../actions/app-action-form-state.test.ts | 8 +++++- .../app-action-node-middleware.test.ts | 3 +++ ...size-limit-invalid-node-middleware.test.ts | 3 +++ .../app-action-size-limit-invalid.test.ts | 17 ++++++++++--- test/e2e/app-dir/actions/app-action.test.ts | 7 +++++- test/e2e/app-dir/actions/middleware-node.js | 18 +++++++++++++ test/e2e/app-dir/actions/middleware.js | 9 ------- test/e2e/app-dir/actions/next.config.js | 1 + 10 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 test/e2e/app-dir/actions/app-action-form-state-node-middleware.test.ts create mode 100644 test/e2e/app-dir/actions/app-action-node-middleware.test.ts create mode 100644 test/e2e/app-dir/actions/app-action-size-limit-invalid-node-middleware.test.ts create mode 100644 test/e2e/app-dir/actions/middleware-node.js diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 1ed750f94a5814..f94867c89a5f48 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -1687,11 +1687,26 @@ export default class NextNodeServer extends BaseServer< const adapterFn: typeof import('./web/adapter').adapter = middlewareModule.default || middlewareModule - result = await adapterFn({ - handler: middlewareModule.middleware || middlewareModule, - request: requestData, - page: 'middleware', - }) + const hasRequestBody = + !['HEAD', 'GET'].includes(params.request.method) && + Boolean(requestData.body) + + try { + result = await adapterFn({ + handler: middlewareModule.middleware || middlewareModule, + request: { + ...requestData, + body: hasRequestBody + ? requestData.body.cloneBodyStream() + : undefined, + }, + page: 'middleware', + }) + } finally { + if (hasRequestBody) { + requestData.body.finalize() + } + } } else { const { run } = require('./web/sandbox') as typeof import('./web/sandbox') diff --git a/test/e2e/app-dir/actions/app-action-form-state-node-middleware.test.ts b/test/e2e/app-dir/actions/app-action-form-state-node-middleware.test.ts new file mode 100644 index 00000000000000..e8888a25eb18a5 --- /dev/null +++ b/test/e2e/app-dir/actions/app-action-form-state-node-middleware.test.ts @@ -0,0 +1,3 @@ +process.env.TEST_NODE_MIDDLEWARE = 'true' + +require('./app-action-form-state.test') diff --git a/test/e2e/app-dir/actions/app-action-form-state.test.ts b/test/e2e/app-dir/actions/app-action-form-state.test.ts index d81aee0e60bc6c..afb51638a64c51 100644 --- a/test/e2e/app-dir/actions/app-action-form-state.test.ts +++ b/test/e2e/app-dir/actions/app-action-form-state.test.ts @@ -1,10 +1,16 @@ /* eslint-disable jest/no-standalone-expect */ -import { nextTestSetup } from 'e2e-utils' +import { FileRef, nextTestSetup } from 'e2e-utils' import { check } from 'next-test-utils' +import { join } from 'path' describe('app-dir action useActionState', () => { const { next } = nextTestSetup({ files: __dirname, + overrideFiles: process.env.TEST_NODE_MIDDLEWARE + ? { + 'middleware.js': new FileRef(join(__dirname, 'middleware-node.js')), + } + : {}, dependencies: { nanoid: '4.0.1', }, diff --git a/test/e2e/app-dir/actions/app-action-node-middleware.test.ts b/test/e2e/app-dir/actions/app-action-node-middleware.test.ts new file mode 100644 index 00000000000000..f9144de45fcada --- /dev/null +++ b/test/e2e/app-dir/actions/app-action-node-middleware.test.ts @@ -0,0 +1,3 @@ +process.env.TEST_NODE_MIDDLEWARE = 'true' + +require('./app-action.test') diff --git a/test/e2e/app-dir/actions/app-action-size-limit-invalid-node-middleware.test.ts b/test/e2e/app-dir/actions/app-action-size-limit-invalid-node-middleware.test.ts new file mode 100644 index 00000000000000..860c0f38715788 --- /dev/null +++ b/test/e2e/app-dir/actions/app-action-size-limit-invalid-node-middleware.test.ts @@ -0,0 +1,3 @@ +process.env.TEST_NODE_MIDDLEWARE = 'true' + +require('./app-action-size-limit-invalid.test') diff --git a/test/e2e/app-dir/actions/app-action-size-limit-invalid.test.ts b/test/e2e/app-dir/actions/app-action-size-limit-invalid.test.ts index 5375cfa24c5c82..8f43b2d2008cda 100644 --- a/test/e2e/app-dir/actions/app-action-size-limit-invalid.test.ts +++ b/test/e2e/app-dir/actions/app-action-size-limit-invalid.test.ts @@ -1,8 +1,9 @@ -import { NextInstance, nextTestSetup } from 'e2e-utils' +import { FileRef, NextInstance, nextTestSetup } from 'e2e-utils' import { retry } from 'next-test-utils' import { createRequestTracker } from 'e2e-utils/request-tracker' import stripAnsi from 'strip-ansi' import { accountForOverhead } from './account-for-overhead' +import { join } from 'path' const CONFIG_ERROR = 'Server Actions Size Limit must be a valid number or filesize format larger than 1MB' @@ -10,6 +11,11 @@ const CONFIG_ERROR = describe('app-dir action size limit invalid config', () => { const { next, isNextStart, isNextDeploy, skipped } = nextTestSetup({ files: __dirname, + overrideFiles: process.env.TEST_NODE_MIDDLEWARE + ? { + 'middleware.js': new FileRef(join(__dirname, 'middleware-node.js')), + } + : {}, skipStart: true, dependencies: { nanoid: '4.0.1', @@ -43,7 +49,8 @@ describe('app-dir action size limit invalid config', () => { ` module.exports = { experimental: { - serverActions: { bodySizeLimit: -3000 } + serverActions: { bodySizeLimit: -3000 }, + nodeMiddleware: true }, } ` @@ -61,7 +68,8 @@ describe('app-dir action size limit invalid config', () => { ` module.exports = { experimental: { - serverActions: { bodySizeLimit: 'testmb' } + serverActions: { bodySizeLimit: 'testmb' }, + nodeMiddleware: true }, } ` @@ -79,7 +87,8 @@ describe('app-dir action size limit invalid config', () => { ` module.exports = { experimental: { - serverActions: { bodySizeLimit: '-3000mb' } + serverActions: { bodySizeLimit: '-3000mb' }, + nodeMiddleware: true }, } ` diff --git a/test/e2e/app-dir/actions/app-action.test.ts b/test/e2e/app-dir/actions/app-action.test.ts index e9ec0872cbe1c3..8cc4de2bdfb602 100644 --- a/test/e2e/app-dir/actions/app-action.test.ts +++ b/test/e2e/app-dir/actions/app-action.test.ts @@ -1,5 +1,5 @@ /* eslint-disable jest/no-standalone-expect */ -import { nextTestSetup } from 'e2e-utils' +import { FileRef, nextTestSetup } from 'e2e-utils' import { assertHasRedbox, retry, @@ -20,6 +20,11 @@ describe('app-dir action handling', () => { const { next, isNextDev, isNextStart, isNextDeploy, isTurbopack } = nextTestSetup({ files: __dirname, + overrideFiles: process.env.TEST_NODE_MIDDLEWARE + ? { + 'middleware.js': new FileRef(join(__dirname, 'middleware-node.js')), + } + : {}, dependencies: { nanoid: '4.0.1', 'server-only': 'latest', diff --git a/test/e2e/app-dir/actions/middleware-node.js b/test/e2e/app-dir/actions/middleware-node.js new file mode 100644 index 00000000000000..e146afd9b04fbf --- /dev/null +++ b/test/e2e/app-dir/actions/middleware-node.js @@ -0,0 +1,18 @@ +// Ensure that https://github.com/vercel/next.js/issues/56286 is fixed. +import { NextResponse } from 'next/server' + +export async function middleware(req) { + if (req.nextUrl.pathname.includes('rewrite-to-static-first')) { + req.nextUrl.pathname = '/static/first' + return NextResponse.rewrite(req.nextUrl) + } + + return NextResponse.next() +} + +/** + * @type {import('next/server').MiddlewareConfig} + */ +export const config = { + runtime: 'nodejs', +} diff --git a/test/e2e/app-dir/actions/middleware.js b/test/e2e/app-dir/actions/middleware.js index 33382ffb7d93c0..c79a0effb25ccf 100644 --- a/test/e2e/app-dir/actions/middleware.js +++ b/test/e2e/app-dir/actions/middleware.js @@ -9,12 +9,3 @@ export async function middleware(req) { return NextResponse.next() } - -/** - * @type {import('next/server').MiddlewareConfig} - */ -export const config = { - // Ensure that middleware doesn't interfere with the request body parsing for - // this test fixture. - matcher: ['/((?!decode-req-body).*)'], -} diff --git a/test/e2e/app-dir/actions/next.config.js b/test/e2e/app-dir/actions/next.config.js index 1e2e7c9e67e718..25c9ec9d464a70 100644 --- a/test/e2e/app-dir/actions/next.config.js +++ b/test/e2e/app-dir/actions/next.config.js @@ -5,6 +5,7 @@ module.exports = { fetches: {}, }, experimental: { + nodeMiddleware: true, serverActions: { bodySizeLimit: '2mb' }, }, }