From 8a62e4b15074e09f321b271ce621e42a899c5014 Mon Sep 17 00:00:00 2001 From: Janka Uryga Date: Mon, 30 Sep 2024 13:24:27 +0200 Subject: [PATCH 1/5] remove leftover console.log introduced in #70532 (#70615) --- .../components/router-reducer/aliased-prefetch-navigations.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next/src/client/components/router-reducer/aliased-prefetch-navigations.ts b/packages/next/src/client/components/router-reducer/aliased-prefetch-navigations.ts index 71e44eaf53d2f0..10e9dd3f8cc74b 100644 --- a/packages/next/src/client/components/router-reducer/aliased-prefetch-navigations.ts +++ b/packages/next/src/client/components/router-reducer/aliased-prefetch-navigations.ts @@ -223,7 +223,6 @@ export function addSearchParamsToPageSegments( // If it's a page segment, modify the segment by adding search params if (segment.includes(PAGE_SEGMENT_KEY)) { const newSegment = addSearchParamsIfPageSegment(segment, searchParams) - console.log({ existingSegment: segment, newSegment }) return [newSegment, parallelRoutes, ...rest] } From ee3211ea2ab8f1f73704c1fef64638e0a63224c2 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Mon, 30 Sep 2024 15:14:04 +0200 Subject: [PATCH 2/5] test: disable sa period hash tests for turbopack build (#70616) --- test/turbopack-build-tests-manifest.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/turbopack-build-tests-manifest.json b/test/turbopack-build-tests-manifest.json index 1b766dfa481b4e..10833894ed9376 100644 --- a/test/turbopack-build-tests-manifest.json +++ b/test/turbopack-build-tests-manifest.json @@ -15856,6 +15856,26 @@ "flakey": [], "runtimeError": false }, + "test/production/app-dir/server-action-period-hash/server-action-period-hash.test.ts": { + "passed": [], + "failed": [ + "app-dir - server-action-period-hash should have same manifest between continuous two builds", + "app-dir - server-action-period-hash should have different manifest between two builds with period hash" + ], + "pending": [], + "flakey": [], + "runtimeError": false + }, + "test/production/app-dir/server-action-period-hash/server-action-period-hash-custom-key.test.ts": { + "passed": [], + "failed": [ + "app-dir - server-action-period-hash-custom-key should have different manifest if the encryption key from process env is changed", + "app-dir - server-action-period-hash-custom-key should have different manifest if the encryption key from process env is same" + ], + "pending": [], + "flakey": [], + "runtimeError": false + }, "test/production/app-dir/ssg-single-pass/ssg-single-pass.test.ts": { "passed": [ "ssg-single-pass should only render the page once during an ISR revalidation", From 86abd1c9695e53dbc784e9155fc580048cbd3756 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Mon, 30 Sep 2024 08:12:45 -0700 Subject: [PATCH 3/5] Add `connection()` as a new dynamic API (#69949) In https://github.com/vercel/next.js/pull/68812 I updated most dynamic APIs to be async. One API that was not udpated was `unstable_noStore()`. This API is marked as unstable and doesn't quite fit the semantics we're exploring with dynamicIO and partial prerendering and so rather than converting it to be async we're going to deprecate it and replace it with an entirely new API. This PR doesn't actually deprecate anything yet but it does introduce `connection()`. The idea with `connection` is that you are waiting until there is a real user Request before proceeding. In the context of prerendering no Request will ever happen so the page cannot produce a static result. (this is similar to how `unstable_noStore()` works today). In a PPR context the currently rendering component won't resolve but a parent Suspense boundary can still statically render a fallback. `connect()` returns a `Promise`. It is tempting to call the API `request()` and return a `Promise` however we have to guard access to the underlying Request carefully to ensure we can maximally prerender pages and to avoid confusion and maybe some frustration we are naming it `connection` since this doesn't imply a specific data set that might be returned. ``` import { connection } from 'next/server' async function MyServerComponent() { await connection() // everthing after this point will be excluded from prerendering const rand = Math.random() return {rand} } ``` --- packages/next/server.d.ts | 1 + packages/next/server.js | 2 + .../next/src/server/request/connection.ts | 72 +++++++++++++++++ packages/next/src/server/web/exports/index.ts | 1 + .../app-dir/dynamic-data/dynamic-data.test.ts | 26 ++++++ .../cache-scoped/app/connection/page.js | 16 ++++ .../fixtures/main/app/force-dynamic/page.js | 8 +- .../fixtures/main/app/force-static/page.js | 8 +- .../fixtures/main/app/top-level/page.js | 2 + .../require-static/app/connection/page.js | 17 ++++ .../static-behavior/boundary/page.tsx | 34 ++++++++ .../static-behavior/pass-deeply/page.tsx | 46 +++++++++++ .../connection/static-behavior/root/page.tsx | 31 ++++++++ .../dynamic-io/dynamic-io.connection.test.ts | 79 +++++++++++++++++++ 14 files changed, 337 insertions(+), 6 deletions(-) create mode 100644 packages/next/src/server/request/connection.ts create mode 100644 test/e2e/app-dir/dynamic-data/fixtures/cache-scoped/app/connection/page.js create mode 100644 test/e2e/app-dir/dynamic-data/fixtures/require-static/app/connection/page.js create mode 100644 test/e2e/app-dir/dynamic-io/app/connection/static-behavior/boundary/page.tsx create mode 100644 test/e2e/app-dir/dynamic-io/app/connection/static-behavior/pass-deeply/page.tsx create mode 100644 test/e2e/app-dir/dynamic-io/app/connection/static-behavior/root/page.tsx create mode 100644 test/e2e/app-dir/dynamic-io/dynamic-io.connection.test.ts diff --git a/packages/next/server.d.ts b/packages/next/server.d.ts index e3ba1fa950e076..2b94d798ff84ef 100644 --- a/packages/next/server.d.ts +++ b/packages/next/server.d.ts @@ -14,5 +14,6 @@ export { URLPattern } from 'next/dist/compiled/@edge-runtime/primitives/url' export { ImageResponse } from 'next/dist/server/web/spec-extension/image-response' export type { ImageResponseOptions } from 'next/dist/compiled/@vercel/og/types' export { unstable_after } from 'next/dist/server/after' +export { connection } from 'next/dist/server/request/connection' export type { UnsafeUnwrappedSearchParams } from 'next/dist/server/request/search-params' export type { UnsafeUnwrappedParams } from 'next/dist/server/request/params' diff --git a/packages/next/server.js b/packages/next/server.js index 589a789dfe66b1..ff224a2bb5a93e 100644 --- a/packages/next/server.js +++ b/packages/next/server.js @@ -12,6 +12,7 @@ const serverExports = { URLPattern: require('next/dist/server/web/spec-extension/url-pattern') .URLPattern, unstable_after: require('next/dist/server/after').unstable_after, + connection: require('next/dist/server/request/connection').connection, } // https://nodejs.org/api/esm.html#commonjs-namespaces @@ -26,3 +27,4 @@ exports.userAgentFromString = serverExports.userAgentFromString exports.userAgent = serverExports.userAgent exports.URLPattern = serverExports.URLPattern exports.unstable_after = serverExports.unstable_after +exports.connection = serverExports.connection diff --git a/packages/next/src/server/request/connection.ts b/packages/next/src/server/request/connection.ts new file mode 100644 index 00000000000000..8ea2af93ac9c4c --- /dev/null +++ b/packages/next/src/server/request/connection.ts @@ -0,0 +1,72 @@ +import { staticGenerationAsyncStorage } from '../../client/components/static-generation-async-storage.external' +import { + isDynamicIOPrerender, + prerenderAsyncStorage, +} from '../app-render/prerender-async-storage.external' +import { + postponeWithTracking, + throwToInterruptStaticGeneration, + trackDynamicDataInDynamicRender, +} from '../app-render/dynamic-rendering' +import { StaticGenBailoutError } from '../../client/components/static-generation-bailout' +import { makeHangingPromise } from '../dynamic-rendering-utils' + +/** + * This function allows you to indicate that you require an actual user Request before continuing. + * + * During prerendering it will never resolve and during rendering it resolves immediately. + */ +export function connection(): Promise { + const staticGenerationStore = staticGenerationAsyncStorage.getStore() + const prerenderStore = prerenderAsyncStorage.getStore() + + if (staticGenerationStore) { + if (staticGenerationStore.forceStatic) { + // When using forceStatic we override all other logic and always just return an empty + // headers object without tracking + return Promise.resolve(undefined) + } + + if (staticGenerationStore.isUnstableCacheCallback) { + throw new Error( + `Route ${staticGenerationStore.route} used "connection" inside a function cached with "unstable_cache(...)". The \`connection()\` function is used to indicate the subsequent code must only run when there is an actual Request, but caches must be able to be produced before a Request so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache` + ) + } else if (staticGenerationStore.dynamicShouldError) { + throw new StaticGenBailoutError( + `Route ${staticGenerationStore.route} with \`dynamic = "error"\` couldn't be rendered statically because it used \`connection\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering` + ) + } + + if (prerenderStore) { + // We are in PPR and/or dynamicIO mode and prerendering + + if (isDynamicIOPrerender(prerenderStore)) { + // We use the controller and cacheSignal as an indication we are in dynamicIO mode. + // When resolving headers for a prerender with dynamic IO we return a forever promise + // along with property access tracked synchronous headers. + + // We don't track dynamic access here because access will be tracked when you access + // one of the properties of the headers object. + return makeHangingPromise() + } else { + // We are prerendering with PPR. We need track dynamic access here eagerly + // to keep continuity with how headers has worked in PPR without dynamicIO. + // TODO consider switching the semantic to throw on property access intead + postponeWithTracking( + staticGenerationStore.route, + 'connection', + prerenderStore.dynamicTracking + ) + } + } else if (staticGenerationStore.isStaticGeneration) { + // We are in a legacy static generation mode while prerendering + // We treat this function call as a bailout of static generation + throwToInterruptStaticGeneration('connection', staticGenerationStore) + } + // We fall through to the dynamic context below but we still track dynamic access + // because in dev we can still error for things like using headers inside a cache context + trackDynamicDataInDynamicRender(staticGenerationStore) + } + + return Promise.resolve(undefined) +} diff --git a/packages/next/src/server/web/exports/index.ts b/packages/next/src/server/web/exports/index.ts index 942bc5a9caf9ac..fc5fc2bbb48831 100644 --- a/packages/next/src/server/web/exports/index.ts +++ b/packages/next/src/server/web/exports/index.ts @@ -6,3 +6,4 @@ export { NextResponse } from '../spec-extension/response' export { userAgent, userAgentFromString } from '../spec-extension/user-agent' export { URLPattern } from '../spec-extension/url-pattern' export { unstable_after } from '../../after' +export { connection } from '../../request/connection' diff --git a/test/e2e/app-dir/dynamic-data/dynamic-data.test.ts b/test/e2e/app-dir/dynamic-data/dynamic-data.test.ts index 9b627b2480234f..8ba6ec4a9a7f2a 100644 --- a/test/e2e/app-dir/dynamic-data/dynamic-data.test.ts +++ b/test/e2e/app-dir/dynamic-data/dynamic-data.test.ts @@ -199,6 +199,16 @@ describe('dynamic-data with dynamic = "error"', () => { await browser.close() } + browser = await next.browser('/connection') + try { + await assertHasRedbox(browser) + expect(await getRedboxHeader(browser)).toMatch( + 'Error: Route /connection with `dynamic = "error"` couldn\'t be rendered statically because it used `connection`' + ) + } finally { + await browser.close() + } + browser = await next.browser('/headers?foo=foosearch') try { await assertHasRedbox(browser) @@ -230,6 +240,9 @@ describe('dynamic-data with dynamic = "error"', () => { expect(next.cliOutput).toMatch( 'Error: Route /cookies with `dynamic = "error"` couldn\'t be rendered statically because it used `cookies`' ) + expect(next.cliOutput).toMatch( + 'Error: Route /connection with `dynamic = "error"` couldn\'t be rendered statically because it used `connection`' + ) expect(next.cliOutput).toMatch( 'Error: Route /headers with `dynamic = "error"` couldn\'t be rendered statically because it used `headers`' ) @@ -277,6 +290,16 @@ describe('dynamic-data inside cache scope', () => { await browser.close() } + browser = await next.browser('/connection') + try { + await assertHasRedbox(browser) + expect(await getRedboxHeader(browser)).toMatch( + 'Error: Route /connection used "connection" inside a function cached with "unstable_cache(...)".' + ) + } finally { + await browser.close() + } + browser = await next.browser('/headers') try { await assertHasRedbox(browser) @@ -297,6 +320,9 @@ describe('dynamic-data inside cache scope', () => { expect(next.cliOutput).toMatch( 'Error: Route /cookies used "cookies" inside a function cached with "unstable_cache(...)".' ) + expect(next.cliOutput).toMatch( + 'Error: Route /connection used "connection" inside a function cached with "unstable_cache(...)".' + ) expect(next.cliOutput).toMatch( 'Error: Route /headers used "headers" inside a function cached with "unstable_cache(...)".' ) diff --git a/test/e2e/app-dir/dynamic-data/fixtures/cache-scoped/app/connection/page.js b/test/e2e/app-dir/dynamic-data/fixtures/cache-scoped/app/connection/page.js new file mode 100644 index 00000000000000..d15afc39b167ae --- /dev/null +++ b/test/e2e/app-dir/dynamic-data/fixtures/cache-scoped/app/connection/page.js @@ -0,0 +1,16 @@ +import { connection } from 'next/server' +import { unstable_cache as cache } from 'next/cache' + +const cachedConnection = cache(async () => connection()) + +export default async function Page({ searchParams }) { + await cachedConnection() + return ( +
+
+ This example uses `connection()` inside `unstable_cache` which should + cause the build to fail +
+
+ ) +} diff --git a/test/e2e/app-dir/dynamic-data/fixtures/main/app/force-dynamic/page.js b/test/e2e/app-dir/dynamic-data/fixtures/main/app/force-dynamic/page.js index 6cf7cbf9607607..cfee26df62328c 100644 --- a/test/e2e/app-dir/dynamic-data/fixtures/main/app/force-dynamic/page.js +++ b/test/e2e/app-dir/dynamic-data/fixtures/main/app/force-dynamic/page.js @@ -1,17 +1,19 @@ import { headers, cookies } from 'next/headers' +import { connection } from 'next/server' import { PageSentinel } from '../getSentinelValue' export const dynamic = 'force-dynamic' export default async function Page({ searchParams }) { + await connection() return (
- This example uses headers/cookies/searchParams directly in a Page - configured with `dynamic = 'force-dynamic'`. This should cause the page - to always render dynamically regardless of dynamic APIs used + This example uses headers/cookies/connection/searchParams directly in a + Page configured with `dynamic = 'force-dynamic'`. This should cause the + page to always render dynamically regardless of dynamic APIs used

headers

diff --git a/test/e2e/app-dir/dynamic-data/fixtures/main/app/force-static/page.js b/test/e2e/app-dir/dynamic-data/fixtures/main/app/force-static/page.js index 1137df1923dddf..6974704ef9b895 100644 --- a/test/e2e/app-dir/dynamic-data/fixtures/main/app/force-static/page.js +++ b/test/e2e/app-dir/dynamic-data/fixtures/main/app/force-static/page.js @@ -1,17 +1,19 @@ import { headers, cookies } from 'next/headers' +import { connection } from 'next/server' import { PageSentinel } from '../getSentinelValue' export const dynamic = 'force-static' export default async function Page({ searchParams }) { + await connection() return (
- This example uses headers/cookies/searchParams directly in a Page - configured with `dynamic = 'force-static'`. This should cause the page - to always statically render but without exposing dynamic data + This example uses headers/cookies/connection/searchParams directly in a + Page configured with `dynamic = 'force-static'`. This should cause the + page to always statically render but without exposing dynamic data

headers

diff --git a/test/e2e/app-dir/dynamic-data/fixtures/main/app/top-level/page.js b/test/e2e/app-dir/dynamic-data/fixtures/main/app/top-level/page.js index 73e446eb15aa48..a99598bd4dfa50 100644 --- a/test/e2e/app-dir/dynamic-data/fixtures/main/app/top-level/page.js +++ b/test/e2e/app-dir/dynamic-data/fixtures/main/app/top-level/page.js @@ -1,8 +1,10 @@ import { headers, cookies } from 'next/headers' +import { connection } from 'next/server' import { PageSentinel } from '../getSentinelValue' export default async function Page({ searchParams }) { + await connection() return (
diff --git a/test/e2e/app-dir/dynamic-data/fixtures/require-static/app/connection/page.js b/test/e2e/app-dir/dynamic-data/fixtures/require-static/app/connection/page.js new file mode 100644 index 00000000000000..c45ca779f1200b --- /dev/null +++ b/test/e2e/app-dir/dynamic-data/fixtures/require-static/app/connection/page.js @@ -0,0 +1,17 @@ +import Server, { connection } from 'next/server' + +console.log('Server', Server) + +export const dynamic = 'error' + +export default async function Page({ searchParams }) { + await connection() + return ( +
+
+ This example uses `connection()` but is configured with `dynamic = + 'error'` which should cause the page to fail to build +
+
+ ) +} diff --git a/test/e2e/app-dir/dynamic-io/app/connection/static-behavior/boundary/page.tsx b/test/e2e/app-dir/dynamic-io/app/connection/static-behavior/boundary/page.tsx new file mode 100644 index 00000000000000..187ac9a0cb7b17 --- /dev/null +++ b/test/e2e/app-dir/dynamic-io/app/connection/static-behavior/boundary/page.tsx @@ -0,0 +1,34 @@ +import { Suspense } from 'react' +import { connection } from 'next/server' + +import { getSentinelValue } from '../../../getSentinelValue' +/** + * This test case is constructed to demonstrate how using the async form of cookies can lead to a better + * prerender with dynamic IO when PPR is on. There is no difference when PPR is off. When PPR is on the second component + * can finish rendering before the prerender completes and so we can produce a static shell where the Fallback closest + * to Cookies access is read + */ +export default async function Page() { + return ( + <> + + + + +
{getSentinelValue()}
+ + ) +} + +async function Component() { + await connection() + return ( +
+ cookie foo +
+ ) +} + +function ComponentTwo() { + return

footer

+} diff --git a/test/e2e/app-dir/dynamic-io/app/connection/static-behavior/pass-deeply/page.tsx b/test/e2e/app-dir/dynamic-io/app/connection/static-behavior/pass-deeply/page.tsx new file mode 100644 index 00000000000000..84a13e6876f6cd --- /dev/null +++ b/test/e2e/app-dir/dynamic-io/app/connection/static-behavior/pass-deeply/page.tsx @@ -0,0 +1,46 @@ +import { Suspense } from 'react' +import { connection } from 'next/server' + +import { getSentinelValue } from '../../../getSentinelValue' + +export default async function Page() { + const pendingConnection = connection() + return ( +
+

Deep Connection Reader

+

+ This component was passed the connection promise returned by + `connection()`. It is rendered inside a Suspense boundary. +

+

+ If dynamicIO is turned off the `connection()` call would trigger a + dynamic point at the callsite and the suspense boundary would also be + blocked for over one second +

+ +

loading connection...

+
{getSentinelValue()}
+ + } + > + +
+
+ ) +} + +async function DeepConnectionReader({ + pendingConnection, +}: { + pendingConnection: ReturnType +}) { + await pendingConnection + return ( + <> +

The connection was awaited

+
{getSentinelValue()}
+ + ) +} diff --git a/test/e2e/app-dir/dynamic-io/app/connection/static-behavior/root/page.tsx b/test/e2e/app-dir/dynamic-io/app/connection/static-behavior/root/page.tsx new file mode 100644 index 00000000000000..f5ad949a2e3d3c --- /dev/null +++ b/test/e2e/app-dir/dynamic-io/app/connection/static-behavior/root/page.tsx @@ -0,0 +1,31 @@ +import { connection } from 'next/server' + +import { getSentinelValue } from '../../../getSentinelValue' +/** + * This test case is constructed to demonstrate how using the async form of cookies can lead to a better + * prerender with dynamic IO when PPR is on. There is no difference when PPR is off. When PPR is on the second component + * can finish rendering before the prerender completes and so we can produce a static shell where the Fallback closest + * to Cookies access is read + */ +export default async function Page() { + return ( + <> + + +
{getSentinelValue()}
+ + ) +} + +async function Component() { + await connection() + return ( +
+ cookie foo +
+ ) +} + +function ComponentTwo() { + return

footer

+} diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.connection.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.connection.test.ts new file mode 100644 index 00000000000000..b859bf8eedf41c --- /dev/null +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.connection.test.ts @@ -0,0 +1,79 @@ +import { nextTestSetup } from 'e2e-utils' + +const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR + +describe('dynamic-io', () => { + const { next, isNextDev, skipped } = nextTestSetup({ + files: __dirname, + skipDeployment: true, + }) + + if (skipped) { + return + } + + if (WITH_PPR) { + it('should partially prerender pages that use connection', async () => { + let $ = await next.render$('/connection/static-behavior/boundary', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#foo').text()).toBe('foo') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#foo').text()).toBe('foo') + } + + $ = await next.render$('/connection/static-behavior/root', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#foo').text()).toBe('foo') + } else { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#foo').text()).toBe('foo') + } + }) + } else { + it('should produce dynamic pages when using connection', async () => { + let $ = await next.render$('/connection/static-behavior/boundary', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#foo').text()).toBe('foo') + } else { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#foo').text()).toBe('foo') + } + + $ = await next.render$('/connection/static-behavior/root', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#foo').text()).toBe('foo') + } else { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#foo').text()).toBe('foo') + } + }) + } + + if (WITH_PPR) { + it('should be able to pass connection as a promise to another component and trigger an intermediate Suspense boundary', async () => { + const $ = await next.render$('/connection/static-behavior/pass-deeply') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#fallback').length).toBe(0) + expect($('#page').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#fallback').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at runtime') + } + }) + } +}) From 7fddec2d169634df4b2b73209b9440ce4af0cb74 Mon Sep 17 00:00:00 2001 From: jaredhan Date: Mon, 30 Sep 2024 23:29:43 +0800 Subject: [PATCH 4/5] Fix: Set busboy defParamCharset to utf8 (#70348) ### Fixing a bug Fix https://github.com/vercel/next.js/issues/70335 ### What? see https://github.com/vercel/next.js/issues/70335 ### Why? ### How? As for fetch standard, I think it should make filename decode with utf-8 by default https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set Co-authored-by: JJ Kasper --- packages/next/src/server/app-render/action-handler.ts | 1 + test/e2e/app-dir/actions/app-action.test.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index b2e4f86df9c6e5..68423cea8b924d 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -725,6 +725,7 @@ export async function handleAction({ if (isMultipartAction) { if (isFetchAction) { const busboy = (require('busboy') as typeof import('busboy'))({ + defParamCharset: 'utf8', headers: req.headers, limits: { fieldSize: bodySizeLimitBytes }, }) diff --git a/test/e2e/app-dir/actions/app-action.test.ts b/test/e2e/app-dir/actions/app-action.test.ts index 58d1c2dcf363b5..016dc365bc597d 100644 --- a/test/e2e/app-dir/actions/app-action.test.ts +++ b/test/e2e/app-dir/actions/app-action.test.ts @@ -336,7 +336,7 @@ describe('app-dir action handling', () => { // Fake a file to upload await browser.eval(` - const file = new File(['hello'], 'hello.txt', { type: 'text/plain' }); + const file = new File(['hello'], 'hello你好テスト.txt', { type: 'text/plain' }); const list = new DataTransfer(); list.items.add(file); document.getElementById('file').files = list.files; @@ -347,7 +347,9 @@ describe('app-dir action handling', () => { // we don't have access to runtime logs on deploy if (!isNextDeploy) { await check(() => { - return logs.some((log) => log.includes('File name: hello.txt size: 5')) + return logs.some((log) => + log.includes('File name: hello你好テスト.txt size: 5') + ) ? 'yes' : '' }, 'yes') From 0b7f7d5f14991091db4d1591a4d6fc27831eb1c6 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Mon, 30 Sep 2024 17:40:32 +0200 Subject: [PATCH 5/5] Turbopack build: Fix favicon-short-circuit (#70617) This test relied on `layout.js` being automatically written but that isn't the case with Turbopack (as documented). Fixed the test by adding the file that was magically generated. --- test/e2e/favicon-short-circuit/app/layout.js | 7 +++++++ test/turbopack-build-tests-manifest.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 test/e2e/favicon-short-circuit/app/layout.js diff --git a/test/e2e/favicon-short-circuit/app/layout.js b/test/e2e/favicon-short-circuit/app/layout.js new file mode 100644 index 00000000000000..803f17d863c8ad --- /dev/null +++ b/test/e2e/favicon-short-circuit/app/layout.js @@ -0,0 +1,7 @@ +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} diff --git a/test/turbopack-build-tests-manifest.json b/test/turbopack-build-tests-manifest.json index 10833894ed9376..c92cf247f45064 100644 --- a/test/turbopack-build-tests-manifest.json +++ b/test/turbopack-build-tests-manifest.json @@ -4732,10 +4732,10 @@ "runtimeError": false }, "test/e2e/favicon-short-circuit/favicon-short-circuit.test.ts": { - "passed": [], - "failed": [ + "passed": [ "favicon-short-circuit should not short circuit the favicon in production" ], + "failed": [], "pending": [], "flakey": [], "runtimeError": false