Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1750,7 +1750,7 @@ export default class NextNodeServer extends BaseServer<
})
} finally {
if (hasRequestBody) {
requestData.body.finalize()
await requestData.body.finalize()
}
}
} else {
Expand Down
13 changes: 13 additions & 0 deletions test/e2e/app-dir/actions/app/body-finalize/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use server'

/**
* @param {number[]} largeJson
*/
export async function submitLargePayload(largeJson) {
return {
success: true,
count: largeJson.length,
firstId: largeJson[0],
lastId: largeJson[largeJson.length - 1],
}
}
27 changes: 27 additions & 0 deletions test/e2e/app-dir/actions/app/body-finalize/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client'

import { useMemo, useState } from 'react'
import { submitLargePayload } from './actions'

export default function Page() {
const [result, setResult] = useState(null)

const largePayload = useMemo(
() => new Array(10 * 1024).fill(null).map((_, idx) => idx),
[]
)

const handleSubmit = async () => {
const res = await submitLargePayload(largePayload)
setResult(res)
}

return (
<div>
<button onClick={handleSubmit} id="submit-large">
Submit Large Payload
</button>
{result && <div id="result">{JSON.stringify(result)}</div>}
</div>
)
}
14 changes: 14 additions & 0 deletions test/e2e/app-dir/actions/components/DefaultLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'

export default function DefaultLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<head />
<body>{children}</body>
</html>
)
}
9 changes: 9 additions & 0 deletions test/e2e/app-dir/actions/middleware-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ export async function middleware(req) {
return NextResponse.rewrite(req.nextUrl)
}

if (req.method === 'POST' && req.nextUrl.pathname.includes('body-finalize')) {
const body = await req.json()

console.log(
'Middleware - Body length: %d bytes',
new TextEncoder().encode(JSON.stringify(body)).length
)
}

return NextResponse.next()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { nextTestSetup } from 'e2e-utils'
import {
findPort,
getFullUrl,
initNextServerScript,
killApp,
retry,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import path from 'node:path'
import fs from 'fs-extra'
import os from 'os'

describe('app-dir action body finalize with nodejs middleware and output-standalone', () => {
const { next } = nextTestSetup({
files: __dirname,
skipStart: true,
})

let server: any
let appPort: number
let tmpFolder: string

beforeAll(async () => {
tmpFolder = path.join(os.tmpdir(), 'next-standalone-' + Date.now())
await fs.mkdirp(tmpFolder)

await next.build()
await next.patchFile(
'.next/standalone/node_modules/next/dist/server/body-streams.js',
(content) => {
return content.replace(
'async finalize () {',
'async finalize () { \nawait new Promise((resolve) => setTimeout(resolve, (Math.random() * 1000) + 1000));\n'
)
}
)

const distFolder = path.join(tmpFolder, 'test')
await fs.move(path.join(next.testDir, '.next/standalone'), distFolder)
await fs.move(
path.join(next.testDir, '.next/static'),
path.join(distFolder, '.next/static')
)

const testServer = path.join(distFolder, 'server.js')
appPort = await findPort()
server = await initNextServerScript(
testServer,
/- Local:/,
{
...process.env,
PORT: appPort.toString(),
},
undefined,
{
cwd: distFolder,
}
)
})

afterAll(async () => {
if (server) await killApp(server)
if (!process.env.NEXT_TEST_SKIP_CLEANUP) {
await fs.remove(tmpFolder).catch(console.error)
}
})

it('should handle large payload through server action after nodejs middleware with delayed body finalize', async () => {
const browser = await webdriver(getFullUrl(appPort, '/body-finalize'), '')

try {
await browser.elementById('submit-large').click()
await retry(async () => {
const resultText = await browser.elementById('result').text()
const result = JSON.parse(resultText)

expect(result.success).toBe(true)
expect(result.count).toBe(10 * 1024)
expect(result.firstId).toBe(0)
expect(result.lastId).toBe(10 * 1024 - 1)
})
} finally {
await browser.close()
}
})
})
13 changes: 13 additions & 0 deletions test/production/app-dir/actions/app/body-finalize/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use server'

/**
* @param {number[]} largeJson
*/
export async function submitLargePayload(largeJson) {
return {
success: true,
count: largeJson.length,
firstId: largeJson[0],
lastId: largeJson[largeJson.length - 1],
}
}
27 changes: 27 additions & 0 deletions test/production/app-dir/actions/app/body-finalize/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client'

import { useMemo, useState } from 'react'
import { submitLargePayload } from './actions'

export default function Page() {
const [result, setResult] = useState(null)

const largePayload = useMemo(
() => new Array(10 * 1024).fill(null).map((_, idx) => idx),
[]
)

const handleSubmit = async () => {
const res = await submitLargePayload(largePayload)
setResult(res)
}

return (
<div>
<button onClick={handleSubmit} id="submit-large">
Submit Large Payload
</button>
{result && <div id="result">{JSON.stringify(result)}</div>}
</div>
)
}
8 changes: 8 additions & 0 deletions test/production/app-dir/actions/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function RootLayout({ children }) {
return (
<html>
<head />
<body>{children}</body>
</html>
)
}
27 changes: 27 additions & 0 deletions test/production/app-dir/actions/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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)
}

if (req.method === 'POST' && req.nextUrl.pathname.includes('body-finalize')) {
const body = await req.json()

console.log(
'Middleware - Body length: %d bytes',
new TextEncoder().encode(JSON.stringify(body)).length
)
}

return NextResponse.next()
}

/**
* @type {import('next/server').ProxyConfig}
*/
export const config = {
runtime: 'nodejs',
}
11 changes: 11 additions & 0 deletions test/production/app-dir/actions/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @type {import('next').NextConfig} */
module.exports = {
productionBrowserSourceMaps: true,
logging: {
fetches: {},
},
experimental: {
serverActions: { bodySizeLimit: '2mb' },
},
output: 'standalone',
}
2 changes: 0 additions & 2 deletions test/production/next-server-nft/next-server-nft.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ const isReact18 = parseInt(process.env.NEXT_TEST_REACT_VERSION) === 18
"/node_modules/next/dist/compiled/react-is/cjs/react-is.development.js",
"/node_modules/next/dist/compiled/react-is/cjs/react-is.production.js",
"/node_modules/next/dist/compiled/react-is/index.js",
"/node_modules/next/dist/compiled/safe-stable-stringify/index.js",
"/node_modules/next/dist/compiled/send/index.js",
"/node_modules/next/dist/compiled/source-map/source-map.js",
"/node_modules/next/dist/compiled/stacktrace-parser/stack-trace-parser.cjs.js",
Expand Down Expand Up @@ -270,7 +269,6 @@ const isReact18 = parseInt(process.env.NEXT_TEST_REACT_VERSION) === 18
"/node_modules/next/dist/compiled/babel-code-frame/index.js",
"/node_modules/next/dist/compiled/babel/code-frame.js",
"/node_modules/next/dist/compiled/next-server/server.runtime.prod.js",
"/node_modules/next/dist/compiled/safe-stable-stringify/index.js",
"/node_modules/next/dist/compiled/source-map/source-map.js",
"/node_modules/next/dist/compiled/stacktrace-parser/stack-trace-parser.cjs.js",
"/node_modules/next/dist/compiled/ws/index.js",
Expand Down
Loading