From 54a963b666656b1a7a3152e607b09f543880e4e2 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 30 Jun 2023 13:31:19 +0200 Subject: [PATCH 1/4] Update displayed error message for rsc case (#52004) We show the "Application error: a client-side exception has occurred (see the browser console for more information)" error incorrectly when a server-side error occurs (a digest is present) when we should be showing an error saying it is in fact a server side error and should check the logs there. This change will make the error message more accurate for users to look up Fixes NEXT-1263 --- .../next/src/client/components/error-boundary.tsx | 12 +++++++----- test/e2e/app-dir/app/index.test.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/next/src/client/components/error-boundary.tsx b/packages/next/src/client/components/error-boundary.tsx index d785d5fc66a0d..ded99a39acddd 100644 --- a/packages/next/src/client/components/error-boundary.tsx +++ b/packages/next/src/client/components/error-boundary.tsx @@ -100,6 +100,7 @@ export class ErrorBoundaryHandler extends React.Component< } export default function GlobalError({ error }: { error: any }) { + const digest: string | undefined = error?.digest return ( @@ -107,12 +108,13 @@ export default function GlobalError({ error }: { error: any }) {

- Application error: a client-side exception has occurred (see the - browser console for more information). + {`Application error: a ${ + digest ? 'server' : 'client' + }-side exception has occurred (see the ${ + digest ? 'server logs' : 'browser console' + } for more information).`}

- {error?.digest && ( -

{`Digest: ${error.digest}`}

- )} + {digest ?

{`Digest: ${digest}`}

: null}
diff --git a/test/e2e/app-dir/app/index.test.ts b/test/e2e/app-dir/app/index.test.ts index cea3d44a7433a..09b3b08912157 100644 --- a/test/e2e/app-dir/app/index.test.ts +++ b/test/e2e/app-dir/app/index.test.ts @@ -1546,7 +1546,7 @@ createNextDescribe( expect( await browser.waitForElementByCss('body').elementByCss('h2').text() ).toBe( - 'Application error: a client-side exception has occurred (see the browser console for more information).' + 'Application error: a server-side exception has occurred (see the server logs for more information).' ) expect( await browser.waitForElementByCss('body').elementByCss('p').text() From 61ac92995af67be46163fb9b73cc2ca7eb04befa Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 30 Jun 2023 14:19:58 +0200 Subject: [PATCH 2/4] Refactor some loaders to be synchronous (#51997) This PR changes some Webpack loaders to be synchronous as they don't have async code inside. Some of them will scale quiet a lot such as `next-flight-client-module-loader` and we don't want to waste some extra ticks there, as well as got potentially queued after some other events like file I/O. --- .../webpack/loaders/next-edge-app-route-loader/index.ts | 2 +- .../src/build/webpack/loaders/next-edge-ssr-loader/index.ts | 2 +- .../webpack/loaders/next-flight-client-entry-loader.ts | 2 +- .../webpack/loaders/next-flight-client-module-loader.ts | 6 ++---- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/next/src/build/webpack/loaders/next-edge-app-route-loader/index.ts b/packages/next/src/build/webpack/loaders/next-edge-app-route-loader/index.ts index 60b82966e3ced..e68e0573184d1 100644 --- a/packages/next/src/build/webpack/loaders/next-edge-app-route-loader/index.ts +++ b/packages/next/src/build/webpack/loaders/next-edge-app-route-loader/index.ts @@ -15,7 +15,7 @@ export type EdgeAppRouteLoaderQuery = { } const EdgeAppRouteLoader: webpack.LoaderDefinitionFunction = - async function (this) { + function (this) { const { page, absolutePagePath, diff --git a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/index.ts b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/index.ts index e9b34ad67b17d..f4f0e247f35d6 100644 --- a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/index.ts +++ b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/index.ts @@ -39,7 +39,7 @@ function swapDistFolderWithEsmDistFolder(path: string) { } const edgeSSRLoader: webpack.LoaderDefinitionFunction = - async function edgeSSRLoader(this) { + function edgeSSRLoader(this) { const { dev, page, diff --git a/packages/next/src/build/webpack/loaders/next-flight-client-entry-loader.ts b/packages/next/src/build/webpack/loaders/next-flight-client-entry-loader.ts index 25ab2cf9d7be0..0be3b6fa2b75c 100644 --- a/packages/next/src/build/webpack/loaders/next-flight-client-entry-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-flight-client-entry-loader.ts @@ -11,7 +11,7 @@ export type NextFlightClientEntryLoaderOptions = { server: boolean | 'true' | 'false' } -export default async function transformSource(this: any): Promise { +export default function transformSource(this: any) { let { modules, server }: NextFlightClientEntryLoaderOptions = this.getOptions() const isServer = server === 'true' diff --git a/packages/next/src/build/webpack/loaders/next-flight-client-module-loader.ts b/packages/next/src/build/webpack/loaders/next-flight-client-module-loader.ts index a827a567d2b5e..3a5001697e062 100644 --- a/packages/next/src/build/webpack/loaders/next-flight-client-module-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-flight-client-module-loader.ts @@ -1,7 +1,7 @@ import { getRSCModuleInformation } from '../../analysis/get-page-static-info' import { getModuleBuildInfo } from './get-module-build-info' -export default async function transformSource( +export default function transformSource( this: any, source: string, sourceMap: any @@ -11,8 +11,6 @@ export default async function transformSource( throw new Error('Expected source to have been transformed to a string.') } - const callback = this.async() - // Assign the RSC meta information to buildInfo. const buildInfo = getModuleBuildInfo(this._module) buildInfo.rsc = getRSCModuleInformation(source, false) @@ -31,5 +29,5 @@ ${source} ` } - return callback(null, source, sourceMap) + return this.callback(null, source, sourceMap) } From 0dd225b1285d5ca31060d807d3f7077ece62e9c4 Mon Sep 17 00:00:00 2001 From: vercel-release-bot Date: Fri, 30 Jun 2023 12:25:06 +0000 Subject: [PATCH 3/4] v13.4.8-canary.13 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-next/package.json | 2 +- packages/font/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 14 +++++++------- packages/react-dev-overlay/package.json | 2 +- packages/react-refresh-utils/package.json | 2 +- pnpm-lock.yaml | 14 +++++++------- 17 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lerna.json b/lerna.json index 65eadaf0f4a91..6141a49f12336 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.4.8-canary.12" + "version": "13.4.8-canary.13" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index c3a969f045b6d..54b584e9f8d00 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 30044323b4378..7d3029459dc8a 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -10,7 +10,7 @@ }, "homepage": "https://nextjs.org/docs/app/building-your-application/configuring/eslint#eslint-config", "dependencies": { - "@next/eslint-plugin-next": "13.4.8-canary.12", + "@next/eslint-plugin-next": "13.4.8-canary.13", "@rushstack/eslint-patch": "^1.1.3", "@typescript-eslint/parser": "^5.42.0", "eslint-import-resolver-node": "^0.3.6", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index bc2f63aa82dc0..ad986a4bdf2a7 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 4b54476fbdde6..3509845eaf799 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 7075db7f80628..ca3fde0c1a5a9 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index c610ea144bab2..682f5f84c2829 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 2c9e10a1c4d63..a225802ae0d3d 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 1c8232e6cad9c..18580cad37d97 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index a41f2500111be..75421a333209f 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index f473f01dcbcca..a1d7118c8b2c0 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index 67d9f0e2af5b2..0ae7379dc2806 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 1859fcc702f73..d37a396ab5f47 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index 86863ce7a4f84..82cbdda1941c2 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -83,7 +83,7 @@ ] }, "dependencies": { - "@next/env": "13.4.8-canary.12", + "@next/env": "13.4.8-canary.13", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -141,11 +141,11 @@ "@jest/types": "29.5.0", "@napi-rs/cli": "2.14.7", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.4.8-canary.12", - "@next/polyfill-nomodule": "13.4.8-canary.12", - "@next/react-dev-overlay": "13.4.8-canary.12", - "@next/react-refresh-utils": "13.4.8-canary.12", - "@next/swc": "13.4.8-canary.12", + "@next/polyfill-module": "13.4.8-canary.13", + "@next/polyfill-nomodule": "13.4.8-canary.13", + "@next/react-dev-overlay": "13.4.8-canary.13", + "@next/react-refresh-utils": "13.4.8-canary.13", + "@next/swc": "13.4.8-canary.13", "@opentelemetry/api": "1.4.1", "@segment/ajv-human-errors": "2.1.2", "@taskr/clear": "1.1.0", diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 071baace4f2be..4b45ea99c0607 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 4c3b391045468..71cfb9359e0f4 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "13.4.8-canary.12", + "version": "13.4.8-canary.13", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a901e2f23d7b..49e0263f17278 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -436,7 +436,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.4.8-canary.12 + '@next/eslint-plugin-next': 13.4.8-canary.13 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -513,12 +513,12 @@ importers: '@jest/types': 29.5.0 '@napi-rs/cli': 2.14.7 '@napi-rs/triples': 1.1.0 - '@next/env': 13.4.8-canary.12 - '@next/polyfill-module': 13.4.8-canary.12 - '@next/polyfill-nomodule': 13.4.8-canary.12 - '@next/react-dev-overlay': 13.4.8-canary.12 - '@next/react-refresh-utils': 13.4.8-canary.12 - '@next/swc': 13.4.8-canary.12 + '@next/env': 13.4.8-canary.13 + '@next/polyfill-module': 13.4.8-canary.13 + '@next/polyfill-nomodule': 13.4.8-canary.13 + '@next/react-dev-overlay': 13.4.8-canary.13 + '@next/react-refresh-utils': 13.4.8-canary.13 + '@next/swc': 13.4.8-canary.13 '@opentelemetry/api': 1.4.1 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.5.1 From 0123a9d5c9a9a77a86f135b7ae30b46ca986d761 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 30 Jun 2023 15:27:38 +0200 Subject: [PATCH 4/4] Optimize inlined Flight data array format (#52028) When looking at [some sites](https://rsc-llm-on-the-edge.vercel.app/) with a large amount of chunks streamed, I noticed that the inlined Flight data array can be optimized quite a lot. Currently we do: ```js self.__next_f.push([1,"d5:[\"4\",[\"$\",\"$a\",null,..."]) ``` 1. The `self.` isn't needed (except for the initial bootstrap tag) as React itself has `` too. 2. After the bootstrap script tag, all items are an array with `[1, flight_data]` and `flight_data` is always a string. We can just push only these strings. 3. We use `JSON.stringify(flight_payload)` to inline the payload where the payload itself is a string with a lot of double quotes (`"`), this results in a huge amount of backslashes (`\`). Here we can instead replace it to use a pair of single quotes on the outside and un-escape the double quotes inside. Here's a side-by-side comparison of a small page: CleanShot 2023-06-30 at 11 41 02@2x For a real production page I saw the HTML payload reduced by 11,031 bytes, a 3% improvement. Note that all the tests are not considering gzip here, so the actual traffic impact will be smaller. --- .../crates/next-core/js/src/entry/app/hydrate.tsx | 12 ++++++------ .../next-swc/crates/next-core/js/types/globals.d.ts | 8 ++++---- packages/next/src/client/app-index.tsx | 12 ++++++------ .../src/server/app-render/use-flight-response.tsx | 12 +++++++++--- test/e2e/app-dir/not-found/not-found.test.ts | 7 +++++-- 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/packages/next-swc/crates/next-core/js/src/entry/app/hydrate.tsx b/packages/next-swc/crates/next-core/js/src/entry/app/hydrate.tsx index d713ab7e31570..19fecaa520259 100644 --- a/packages/next-swc/crates/next-core/js/src/entry/app/hydrate.tsx +++ b/packages/next-swc/crates/next-core/js/src/entry/app/hydrate.tsx @@ -41,19 +41,19 @@ let initialServerDataWriter: ReadableStreamDefaultController | undefined = let initialServerDataLoaded = false let initialServerDataFlushed = false -function nextServerDataCallback( - seg: [isBootStrap: 0] | [isNotBootstrap: 1, responsePartial: string] -): number { - if (seg[0] === 0) { +type IsBootStrap = 0 +type ResponsePartial = string +function nextServerDataCallback(seg: IsBootStrap | ResponsePartial): number { + if (seg === 0) { initialServerDataBuffer = [] } else { if (!initialServerDataBuffer) throw new Error('Unexpected server data: missing bootstrap script.') if (initialServerDataWriter) { - initialServerDataWriter.enqueue(encoder.encode(seg[1])) + initialServerDataWriter.enqueue(encoder.encode(seg)) } else { - initialServerDataBuffer.push(seg[1]) + initialServerDataBuffer.push(seg) } } return 0 diff --git a/packages/next-swc/crates/next-core/js/types/globals.d.ts b/packages/next-swc/crates/next-core/js/types/globals.d.ts index ecc0cb85966b7..83caf06694107 100644 --- a/packages/next-swc/crates/next-core/js/types/globals.d.ts +++ b/packages/next-swc/crates/next-core/js/types/globals.d.ts @@ -10,10 +10,10 @@ declare global { var __next_require__: (id: string) => any var __next_chunk_load__: (id: string) => Promise - var __next_f: ( - | [isBootStrap: 0] - | [isNotBootstrap: 1, responsePartial: string] - )[] + + type isBootStrap = 0 + type responsePartial = string + var __next_f: (isBootStrap | responsePartial)[] var next: { version: string appDir?: boolean diff --git a/packages/next/src/client/app-index.tsx b/packages/next/src/client/app-index.tsx index 3bc8fb325f46a..ec53f25905b7c 100644 --- a/packages/next/src/client/app-index.tsx +++ b/packages/next/src/client/app-index.tsx @@ -116,19 +116,19 @@ let initialServerDataWriter: ReadableStreamDefaultController | undefined = let initialServerDataLoaded = false let initialServerDataFlushed = false -function nextServerDataCallback( - seg: [isBootStrap: 0] | [isNotBootstrap: 1, responsePartial: string] -): void { - if (seg[0] === 0) { +type IsBootStrap = 0 +type ResponsePartial = string +function nextServerDataCallback(seg: IsBootStrap | ResponsePartial): void { + if (seg === 0) { initialServerDataBuffer = [] } else { if (!initialServerDataBuffer) throw new Error('Unexpected server data: missing bootstrap script.') if (initialServerDataWriter) { - initialServerDataWriter.enqueue(encoder.encode(seg[1])) + initialServerDataWriter.enqueue(encoder.encode(seg)) } else { - initialServerDataBuffer.push(seg[1]) + initialServerDataBuffer.push(seg) } } } diff --git a/packages/next/src/server/app-render/use-flight-response.tsx b/packages/next/src/server/app-render/use-flight-response.tsx index 1cc5b67f62d00..af11bef595d39 100644 --- a/packages/next/src/server/app-render/use-flight-response.tsx +++ b/packages/next/src/server/app-render/use-flight-response.tsx @@ -53,7 +53,7 @@ export function useFlightResponse( writer.write( encodeText( `${startScriptTag}(self.__next_f=self.__next_f||[]).push(${htmlEscapeJsonString( - JSON.stringify([0]) + JSON.stringify(0) )})` ) ) @@ -63,8 +63,14 @@ export function useFlightResponse( writer.close() } else { const responsePartial = decodeText(value, textDecoder) - const scripts = `${startScriptTag}self.__next_f.push(${htmlEscapeJsonString( - JSON.stringify([1, responsePartial]) + const scripts = `${startScriptTag}__next_f.push(${htmlEscapeJsonString( + // Since the inlined payload is always a JSON-ish encoded string with + // many double quotes, we can safely un-escape these quotes and use + // a single quote to wrap the string. This saves a lot of bytes. + JSON.stringify(responsePartial) + .replace(/\\"/g, '"') + .replace(/'/g, "\\'") + .replace(/(^")|("$)/g, "'") )})` writer.write(encodeText(scripts)) diff --git a/test/e2e/app-dir/not-found/not-found.test.ts b/test/e2e/app-dir/not-found/not-found.test.ts index 49df6a937924e..e5f21d4f707e2 100644 --- a/test/e2e/app-dir/not-found/not-found.test.ts +++ b/test/e2e/app-dir/not-found/not-found.test.ts @@ -15,8 +15,11 @@ createNextDescribe( }) it('should allow to have a valid /not-found route', async () => { - const html = await next.render('/not-found') - expect(html).toContain("I'm still a valid page") + const browser = await next.browser('/not-found') + await check( + () => browser.elementByCss('h1').text(), + `I'm still a valid page` + ) }) if (isNextDev) {