From 0f03ef14f0088eaf869f4ae5d6a7272dc7f9eb54 Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 13 Jun 2023 17:44:53 -0400 Subject: [PATCH 01/54] chore(docs): codemod docs should mention `.` for cwd (#51264) These docs were wrong because `./pages` is not sufficient to run a codemod. Imagine a project that has `./components` and `./pages` and many directories for example. Also its important to run at the root of the Next.js project so that the path has a `next.config.js` because some codemods modify that config. --- .../08-upgrading/01-codemods.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/02-app/01-building-your-application/08-upgrading/01-codemods.mdx b/docs/02-app/01-building-your-application/08-upgrading/01-codemods.mdx index 55c91ef14674..63b4579526ca 100644 --- a/docs/02-app/01-building-your-application/08-upgrading/01-codemods.mdx +++ b/docs/02-app/01-building-your-application/08-upgrading/01-codemods.mdx @@ -31,7 +31,7 @@ Replacing `` and `` with appropriate values. ##### `built-in-next-font` ```bash filename="Terminal" -npx @next/codemod@latest built-in-next-font +npx @next/codemod@latest built-in-next-font . ``` This codemod uninstalls the `@next/font` package and transforms `@next/font` imports into the built-in `next/font`. @@ -55,7 +55,7 @@ import { Inter } from 'next/font/google' ##### `next-image-to-legacy-image` ```bash filename="Terminal" -npx @next/codemod@latest next-image-to-legacy-image ./pages +npx @next/codemod@latest next-image-to-legacy-image . ``` Safely renames `next/image` imports in existing Next.js 10, 11, or 12 applications to `next/legacy/image` in Next.js 13. Also renames `next/future/image` to `next/image`. @@ -99,7 +99,7 @@ export default function Home() { ##### `next-image-experimental` ```bash filename="Terminal" -npx @next/codemod@latest next-image-experimental ./pages +npx @next/codemod@latest next-image-experimental . ``` Dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props. @@ -115,7 +115,7 @@ Dangerously migrates from `next/legacy/image` to the new `next/image` by adding ##### `new-link` ```bash filename="Terminal" -npx @next/codemod@latest new-link ./pages +npx @next/codemod@latest new-link . ``` Remove `` tags inside [Link Components](/docs/app/api-reference/components/link), or add a `legacyBehavior` prop to Links that cannot be auto-fixed. From 85ea7cde3378b71192ace80e1397f601218373ea Mon Sep 17 00:00:00 2001 From: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com> Date: Tue, 13 Jun 2023 23:12:48 +0100 Subject: [PATCH 02/54] Remove related-links (#51258) --- docs/05-community/01-contribution-guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/05-community/01-contribution-guide.mdx b/docs/05-community/01-contribution-guide.mdx index bf2c1bdcc5df..4f5e4de7663c 100644 --- a/docs/05-community/01-contribution-guide.mdx +++ b/docs/05-community/01-contribution-guide.mdx @@ -126,7 +126,7 @@ The following fields are **optional**: | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `nav_title` | Overrides the page's title in the navigation. This is useful when the page's title is too long to fit. If not provided, the `title` field is used. | | `source` | Pulls content into a shared page. See [Shared Pages](#shared-pages). | -| `related` | A list of related pages at the bottom of the document. These will automatically be turned into cards. See [Related Links](#related-links). | +| `related` | A list of related pages at the bottom of the document. These will automatically be turned into cards. | ```yaml filename="optional-fields.mdx" --- From 543dcfec6fc7b1d6b533f8701aca92f411d4c25e Mon Sep 17 00:00:00 2001 From: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com> Date: Tue, 13 Jun 2023 23:15:55 +0100 Subject: [PATCH 03/54] Add leerob as an optional global codeowner (#51257) Add @leerob as an optional global code owner. This means he won't be tagged for reviews on every PR, but he can approve PRs. This allows the @vercel/devex to make changes more quickly, without having to distract the Next.js team. Co-authored-by: JJ Kasper --- .vercel.approvers | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vercel.approvers b/.vercel.approvers index 934c1249bb6f..7cac08a87245 100644 --- a/.vercel.approvers +++ b/.vercel.approvers @@ -4,6 +4,7 @@ @shuding @huozhi @feedthejim +@leerob:optional @vercel/next.js:optional # Tooling & Telemetry @@ -15,4 +16,4 @@ Cargo.lock @vercel/web-tooling # Catch any markdown files outside docs (e.g. examples) **/*.md @vercel/devex -**/*.mdx @vercel/devex \ No newline at end of file +**/*.mdx @vercel/devex From fe2c0cbc5daf2ed309ea3feafd4ac5346bdb4baa Mon Sep 17 00:00:00 2001 From: Yishay Hazan <50710472+yishayhaz@users.noreply.github.com> Date: Wed, 14 Jun 2023 04:45:27 +0300 Subject: [PATCH 04/54] Update fast-refresh-reload.md (#49953) Full refresh happens when component's name is camelCase instead of PascalCase --- errors/fast-refresh-reload.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/errors/fast-refresh-reload.md b/errors/fast-refresh-reload.md index 14023d575046..3a1875d6b6e1 100644 --- a/errors/fast-refresh-reload.md +++ b/errors/fast-refresh-reload.md @@ -6,11 +6,13 @@ Fast Refresh had to perform a full reload when you edited a file. It may be beca - The file you're editing might have other exports in addition to a React component. - Your React component is an anonymous function. +- The component name is in camelCase and not PascalCase, for example `textField` instead of `TextField`. #### Possible Ways to Fix It - Move your other exports to a separate file. - Use a named function for your React component. +- Rename your component name to pascal case. ### Useful Links From 0a9148901b4d23ec2361137dcbf4e05955ebc95a Mon Sep 17 00:00:00 2001 From: Jan Cizmar Date: Wed, 14 Jun 2023 03:50:11 +0200 Subject: [PATCH 05/54] docs: Add Tolgee to internationalization section (#49754) It would be nice to have Tolgee listed in internationalization libs supporting Next.JS Co-authored-by: JJ Kasper --- .../01-routing/08-internationalization.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/03-pages/01-building-your-application/01-routing/08-internationalization.mdx b/docs/03-pages/01-building-your-application/01-routing/08-internationalization.mdx index aa599e570654..b7152e862e36 100644 --- a/docs/03-pages/01-building-your-application/01-routing/08-internationalization.mdx +++ b/docs/03-pages/01-building-your-application/01-routing/08-internationalization.mdx @@ -13,7 +13,7 @@ description: Next.js has built-in support for internationalized routing and lang Next.js has built-in support for internationalized ([i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization#Naming)) routing since `v10.0.0`. You can provide a list of locales, the default locale, and domain-specific locales and Next.js will automatically handle the routing. -The i18n routing support is currently meant to complement existing i18n library solutions like [`react-intl`](https://formatjs.io/docs/getting-started/installation), [`react-i18next`](https://react.i18next.com/), [`lingui`](https://lingui.dev/), [`rosetta`](https://github.com/lukeed/rosetta), [`next-intl`](https://github.com/amannn/next-intl), [`next-translate`](https://github.com/aralroca/next-translate), [`next-multilingual`](https://github.com/Avansai/next-multilingual), [`typesafe-i18n`](https://github.com/ivanhofer/typesafe-i18n), and others by streamlining the routes and locale parsing. +The i18n routing support is currently meant to complement existing i18n library solutions like [`react-intl`](https://formatjs.io/docs/getting-started/installation), [`react-i18next`](https://react.i18next.com/), [`lingui`](https://lingui.dev/), [`rosetta`](https://github.com/lukeed/rosetta), [`next-intl`](https://github.com/amannn/next-intl), [`next-translate`](https://github.com/aralroca/next-translate), [`next-multilingual`](https://github.com/Avansai/next-multilingual), [`typesafe-i18n`](https://github.com/ivanhofer/typesafe-i18n), [`tolgee`](https://tolgee.io/integrations/next), and others by streamlining the routes and locale parsing. ## Getting started From ac32452d1c47c5f1300f49b64aba5eb1f523dfe5 Mon Sep 17 00:00:00 2001 From: Joe Marty Date: Tue, 13 Jun 2023 21:01:44 -0500 Subject: [PATCH 06/54] Clarify the limitations of NEXT_PUBLIC_ (#49105) It's not totally clear from the docs that using `NEXT_PUBLIC_` env vars will be a problem for pipelines that deploy the same image to multiple environments (this bit us in a production incident). This PR is an attempt to make it clear. Open to feedback/suggestions! --------- Co-authored-by: JJ Kasper --- .../06-configuring/03-environment-variables.mdx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/02-app/01-building-your-application/06-configuring/03-environment-variables.mdx b/docs/02-app/01-building-your-application/06-configuring/03-environment-variables.mdx index 5e4e0a60b3e6..572668e56e22 100644 --- a/docs/02-app/01-building-your-application/06-configuring/03-environment-variables.mdx +++ b/docs/02-app/01-building-your-application/06-configuring/03-environment-variables.mdx @@ -13,7 +13,7 @@ description: Learn to add and access environment variables in your Next.js appli Next.js comes with built-in support for environment variables, which allows you to do the following: - [Use `.env.local` to load environment variables](#loading-environment-variables) -- [Expose environment variables to the browser by prefixing with `NEXT_PUBLIC_`](#exposing-environment-variables-to-the-browser) +- [Bundle environment variables for the browser by prefixing with `NEXT_PUBLIC_`](#bundling-environment-variables-for-the-browser) ## Loading Environment Variables @@ -63,17 +63,19 @@ In the above example, `process.env.TWITTER_URL` would be set to `http://twitter. > **Good to know**: If you need to use variable with a `$` in the actual value, it needs to be escaped e.g. `\$`. -## Exposing Environment Variables to the Browser +## Bundling Environment Variables for the Browser -By default environment variables are only available in the Node.js environment, meaning they won't be exposed to the browser. +Non-`NEXT_PUBLIC_` environment variables are only available in the Node.js environment, meaning they aren't accessible to the browser (the client runs in a different _environment_). -In order to expose a variable to the browser you have to prefix the variable with `NEXT_PUBLIC_`. For example: +In order to make the value of an environment variable accessible in the browser, Next.js can "inline" a value, at build time, into the js bundle that is delivered to the client, replacing all references to `process.env.[variable]` with a hard-coded value. To tell it to do this, you just have to prefix the variable with `NEXT_PUBLIC_`. For example: ```txt filename="Terminal" NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk ``` -This loads `process.env.NEXT_PUBLIC_ANALYTICS_ID` into the Node.js environment automatically, allowing you to use it anywhere in your code. The value will be inlined into JavaScript sent to the browser because of the `NEXT_PUBLIC_` prefix. This inlining occurs at build time, so your various `NEXT_PUBLIC_` envs need to be set when the project is built. +This will tell Next.js to replace all references to `process.env.NEXT_PUBLIC_ANALYTICS_ID` in the Node.js environment with the value from the environment in which you run `next build`, allowing you to use it anywhere in your code. It will be inlined into any JavaScript sent to the browser. + +> **Note**: After being built, your app will no longer respond to changes to these environment variables. For instance, if you use a Heroku pipeline to promote slugs built in one environment to another environment, or if you build and deploy a single Docker image to multiple environments, all `NEXT_PUBLIC_` variables will be frozen with the value evaluated at build time, so these values need to be set appropriately when the project is built. If you need access to runtime environment values, you'll have to setup your own API to provide them to the client (either on demand or during initialization). ```js filename="pages/index.js" import setupAnalyticsService from '../lib/my-analytics-service' From 99142b5897efa207ff42af31bbde278ac71de149 Mon Sep 17 00:00:00 2001 From: William Wilder <76077183+WilderDev@users.noreply.github.com> Date: Tue, 13 Jun 2023 21:06:32 -0500 Subject: [PATCH 07/54] Update 02-fonts.mdx (#51249) Add link to font api instead of internal reference to same page. Co-authored-by: JJ Kasper --- .../01-building-your-application/05-optimizing/02-fonts.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-app/01-building-your-application/05-optimizing/02-fonts.mdx b/docs/02-app/01-building-your-application/05-optimizing/02-fonts.mdx index 2e5bbb14f052..67b7eddf4b49 100644 --- a/docs/02-app/01-building-your-application/05-optimizing/02-fonts.mdx +++ b/docs/02-app/01-building-your-application/05-optimizing/02-fonts.mdx @@ -233,7 +233,7 @@ const inter = Inter({ subsets: ['latin'] }) -View the [Font API Reference](#google-fonts) for more information. +View the [Font API Reference](/docs/02-app/02-api-reference/01-components/font.mdx) for more information. ### Using Multiple Fonts From 691598ecee1b832373df9aa9d6c0c30f2048d77a Mon Sep 17 00:00:00 2001 From: Guilleo <43964957+Guilleo03@users.noreply.github.com> Date: Tue, 13 Jun 2023 23:18:11 -0300 Subject: [PATCH 08/54] Fix middleware documentation (#48876) In the actual documentation, `value` does not exist. I removed `value` and work correctly --------- Co-authored-by: JJ Kasper --- .../01-building-your-application/01-routing/11-middleware.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/02-app/01-building-your-application/01-routing/11-middleware.mdx b/docs/02-app/01-building-your-application/01-routing/11-middleware.mdx index be0195e5de1f..03edf864d006 100644 --- a/docs/02-app/01-building-your-application/01-routing/11-middleware.mdx +++ b/docs/02-app/01-building-your-application/01-routing/11-middleware.mdx @@ -140,8 +140,8 @@ import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { // Assume a "Cookie:nextjs=fast" header to be present on the incoming request // Getting cookies from the request using the `RequestCookies` API - let cookie = request.cookies.get('nextjs')?.value - console.log(cookie) // => 'fast' + let cookie = request.cookies.get('nextjs') + console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' } const allCookies = request.cookies.getAll() console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }] From 0bda6287b638f49345bb31480acea8c73207246f Mon Sep 17 00:00:00 2001 From: Willem-Jaap <67187467+Willem-Jaap@users.noreply.github.com> Date: Wed, 14 Jun 2023 04:36:08 +0200 Subject: [PATCH 09/54] fix(cli): add all available options to cli commands, format them consistently (#48708) ### What? Add all available options to cli commands Consistent formatting for all options ### Why? Options were not consistent across all commands, some were missing Co-authored-by: Steven <229881+styfle@users.noreply.github.com> Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- errors/invalid-i18n-config.md | 2 +- packages/next/src/cli/next-build.ts | 10 ++++++---- packages/next/src/cli/next-export.ts | 17 ++++++++++++----- packages/next/src/cli/next-info.ts | 3 +++ packages/next/src/cli/next-start.ts | 6 +++--- packages/next/src/cli/next-telemetry.ts | 7 ++++++- test/integration/cli/test/index.test.js | 4 ++-- 7 files changed, 33 insertions(+), 16 deletions(-) diff --git a/errors/invalid-i18n-config.md b/errors/invalid-i18n-config.md index b840d14a61f9..07511dd95732 100644 --- a/errors/invalid-i18n-config.md +++ b/errors/invalid-i18n-config.md @@ -1,6 +1,6 @@ # Invalid i18n config -#### Why This Error +#### Why This Error Occurred In your `next.config.js` file you provided an invalid config for the `i18n` field. This could mean the limit for 100 locale items was exceeded. diff --git a/packages/next/src/cli/next-build.ts b/packages/next/src/cli/next-build.ts index d84c5c3371bb..043c49f445e4 100755 --- a/packages/next/src/cli/next-build.ts +++ b/packages/next/src/cli/next-build.ts @@ -46,10 +46,12 @@ const nextBuild: CliCommand = (argv) => { If no directory is provided, the current directory will be used. Options - --profile Can be used to enable React Production Profiling - --no-lint Disable linting - --no-mangling Disable mangling - --experimental-app-only Only build 'app' routes + --profile Can be used to enable React Production Profiling + --no-lint Disable linting + --no-mangling Disable mangling + --experimental-app-only Only build 'app' routes + --experimental-turbo Enable experimental turbo mode + --help, -h Displays this message `, 0 ) diff --git a/packages/next/src/cli/next-export.ts b/packages/next/src/cli/next-export.ts index 973b84b397f6..0d4f6ddfbbad 100755 --- a/packages/next/src/cli/next-export.ts +++ b/packages/next/src/cli/next-export.ts @@ -2,6 +2,7 @@ import { resolve, join } from 'path' import { existsSync } from 'fs' import arg from 'next/dist/compiled/arg/index.js' +import chalk from 'next/dist/compiled/chalk' import exportApp, { ExportError, ExportOptions } from '../export' import * as Log from '../build/output/log' import { printAndExit } from '../server/lib/utils' @@ -21,8 +22,8 @@ const nextExport: CliCommand = (argv) => { // Aliases '-h': '--help', - '-s': '--silent', '-o': '--outdir', + '-s': '--silent', } let args: arg.Result try { @@ -36,7 +37,7 @@ const nextExport: CliCommand = (argv) => { if (args['--help']) { console.log(` Description - Exports the application for production deployment + [DEPRECATED] Exports a static version of the application for production deployment Usage $ next export [options] @@ -45,9 +46,15 @@ const nextExport: CliCommand = (argv) => { If no directory is provided, the current directory will be used. Options - -h - list this help - -o - set the output dir (defaults to 'out') - -s - do not print any messages to console + --outdir, -o Set the output dir (defaults to 'out') + --silent, -s Do not print any messages to console + --threads Max number of threads to use + --help, -h List this help + + The "next export" command is deprecated in favor of "output: export" in next.config.js + Learn more: ${chalk.cyan( + 'https://nextjs.org/docs/advanced-features/static-html-export' + )} `) process.exit(0) } diff --git a/packages/next/src/cli/next-info.ts b/packages/next/src/cli/next-info.ts index 6cec344a78d2..f3e2b3a3eb89 100755 --- a/packages/next/src/cli/next-info.ts +++ b/packages/next/src/cli/next-info.ts @@ -56,6 +56,9 @@ const nextInfo: CliCommand = async (argv) => { Usage $ next info + Options + --help, -h Displays this message + Learn more: ${chalk.cyan( 'https://nextjs.org/docs/api-reference/cli#info' )}` diff --git a/packages/next/src/cli/next-start.ts b/packages/next/src/cli/next-start.ts index 3cdbbf9ce2cf..3db7b44e9e41 100755 --- a/packages/next/src/cli/next-start.ts +++ b/packages/next/src/cli/next-start.ts @@ -45,10 +45,10 @@ const nextStart: CliCommand = async (argv) => { If no directory is provided, the current directory will be used. Options - --port, -p A port number on which to start the application - --hostname, -H Hostname on which to start the application (default: 0.0.0.0) + --port, -p A port number on which to start the application + --hostname, -H Hostname on which to start the application (default: 0.0.0.0) --keepAliveTimeout Max milliseconds to wait before closing inactive connections - --help, -h Displays this message + --help, -h Displays this message `) process.exit(0) } diff --git a/packages/next/src/cli/next-telemetry.ts b/packages/next/src/cli/next-telemetry.ts index 70f4fa482e00..e83b7b1dd451 100755 --- a/packages/next/src/cli/next-telemetry.ts +++ b/packages/next/src/cli/next-telemetry.ts @@ -9,9 +9,9 @@ import isError from '../lib/is-error' const nextTelemetry: CliCommand = (argv) => { const validArgs: arg.Spec = { // Types - '--help': Boolean, '--enable': Boolean, '--disable': Boolean, + '--help': Boolean, // Aliases '-h': '--help', } @@ -36,6 +36,11 @@ const nextTelemetry: CliCommand = (argv) => { You may pass the 'enable' or 'disable' argument to turn Next.js' telemetry collection on or off. + Options + --enable Enables Next.js' telemetry collection + --disable Disables Next.js' telemetry collection + --help, -h Displays this message + Learn more: ${chalk.cyan('https://nextjs.org/telemetry')} ` ) diff --git a/test/integration/cli/test/index.test.js b/test/integration/cli/test/index.test.js index bf539a9e289d..61ded2719cce 100644 --- a/test/integration/cli/test/index.test.js +++ b/test/integration/cli/test/index.test.js @@ -580,14 +580,14 @@ describe('CLI Usage', () => { const help = await runNextCommand(['export', '--help'], { stdout: true, }) - expect(help.stdout).toMatch(/Exports the application/) + expect(help.stdout).toMatch(/Exports a static version of the application/) }) test('-h', async () => { const help = await runNextCommand(['export', '-h'], { stdout: true, }) - expect(help.stdout).toMatch(/Exports the application/) + expect(help.stdout).toMatch(/Exports a static version of the application/) }) test('should warn when unknown argument provided', async () => { From 18b133e6a355e6ad40a2c596601ac151ad5c1356 Mon Sep 17 00:00:00 2001 From: J <124119483+escwxyz@users.noreply.github.com> Date: Wed, 14 Jun 2023 04:45:48 +0200 Subject: [PATCH 10/54] docs: fixed a typo in parallel routes (#51267) `allow` => `allows` --- .../01-routing/08-parallel-routes.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-app/01-building-your-application/01-routing/08-parallel-routes.mdx b/docs/02-app/01-building-your-application/01-routing/08-parallel-routes.mdx index 53a13cac62f6..a896c9516d6d 100644 --- a/docs/02-app/01-building-your-application/01-routing/08-parallel-routes.mdx +++ b/docs/02-app/01-building-your-application/01-routing/08-parallel-routes.mdx @@ -25,7 +25,7 @@ Parallel Routing allows you to define independent error and loading states for e height="1218" /> -Parallel Routing also allow you to conditionally render a slot based on certain conditions, such as authentication state. This enables fully separated code on the same URL. +Parallel Routing also allows you to conditionally render a slot based on certain conditions, such as authentication state. This enables fully separated code on the same URL. Conditional routes diagram Date: Tue, 13 Jun 2023 20:53:17 -0600 Subject: [PATCH 11/54] Update module-path-aliases.md (#43592) ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] [e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see [`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md) ## Documentation / Examples - [X ] Make sure the linting passes by running `pnpm build && pnpm lint` - [X ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) --------- Co-authored-by: JJ Kasper --- ...04-absolute-imports-and-module-aliases.mdx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/02-app/01-building-your-application/06-configuring/04-absolute-imports-and-module-aliases.mdx b/docs/02-app/01-building-your-application/06-configuring/04-absolute-imports-and-module-aliases.mdx index a670d60a6e6d..278b7e8adcd9 100644 --- a/docs/02-app/01-building-your-application/06-configuring/04-absolute-imports-and-module-aliases.mdx +++ b/docs/02-app/01-building-your-application/06-configuring/04-absolute-imports-and-module-aliases.mdx @@ -130,3 +130,34 @@ export default function HomePage() { ) } ``` + +Each of the `"paths"` are relative to the `baseUrl` location. For example: + +```json +// tsconfig.json or jsconfig.json +{ + "compilerOptions": { + "baseUrl": "src/", + "paths": { + "@/styles/*": ["styles/*"], + "@/components/*": ["components/*"] + } + } +} +``` + +```jsx +// pages/index.js +import Button from '@/components/button' +import '@/styles/styles.css' +import Helper from 'utils/helper' + +export default function HomePage() { + return ( + +

Hello World

+
+ ) +} diff --git a/examples/with-storybook/stories/Header.stories.ts b/examples/with-storybook/stories/Header.stories.ts new file mode 100644 index 000000000000..d7511df2c2a1 --- /dev/null +++ b/examples/with-storybook/stories/Header.stories.ts @@ -0,0 +1,26 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { Header } from './Header' + +const meta: Meta = { + title: 'Example/Header', + component: Header, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + tags: ['autodocs'], + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: 'fullscreen', + }, +} + +export default meta +type Story = StoryObj + +export const LoggedIn: Story = { + args: { + user: { + name: 'Jane Doe', + }, + }, +} + +export const LoggedOut: Story = {} diff --git a/examples/with-storybook/stories/Header.tsx b/examples/with-storybook/stories/Header.tsx new file mode 100644 index 000000000000..f1b3d1c06f37 --- /dev/null +++ b/examples/with-storybook/stories/Header.tsx @@ -0,0 +1,71 @@ +import React from 'react' + +import { Button } from './Button' +import './header.css' + +type User = { + name: string +} + +interface HeaderProps { + user?: User + onLogin: () => void + onLogout: () => void + onCreateAccount: () => void +} + +export const Header = ({ + user, + onLogin, + onLogout, + onCreateAccount, +}: HeaderProps) => ( +
+
+
+ + + + + + + +

Acme

+
+
+ {user ? ( + <> + + Welcome, {user.name}! + +
+
+
+) diff --git a/examples/with-storybook/stories/Introduction.mdx b/examples/with-storybook/stories/Introduction.mdx new file mode 100644 index 000000000000..9f120228791d --- /dev/null +++ b/examples/with-storybook/stories/Introduction.mdx @@ -0,0 +1,239 @@ +import { Meta } from '@storybook/blocks' +import Image from 'next/image' + +import Code from './assets/code-brackets.svg' +import Colors from './assets/colors.svg' +import Comments from './assets/comments.svg' +import Direction from './assets/direction.svg' +import Flow from './assets/flow.svg' +import Plugin from './assets/plugin.svg' +import Repo from './assets/repo.svg' +import StackAlt from './assets/stackalt.svg' + + + + + +# Welcome to Storybook + +Storybook helps you build UI components in isolation from your app's business logic, data, and context. +That makes it easy to develop hard-to-reach states. Save these UI states as **stories** to revisit during development, testing, or QA. + +Browse example stories now by navigating to them in the sidebar. +View their code in the `stories` directory to learn how they work. +We recommend building UIs with a [**component-driven**](https://componentdriven.org) process starting with atomic components and ending with pages. + +
Configure
+ + + +
Learn
+ + + +
+ TipEdit the Markdown in{' '} + stories/Introduction.mdx +
diff --git a/examples/with-storybook/stories/Page.stories.ts b/examples/with-storybook/stories/Page.stories.ts new file mode 100644 index 000000000000..308c2b119762 --- /dev/null +++ b/examples/with-storybook/stories/Page.stories.ts @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { within, userEvent } from '@storybook/testing-library' + +import { Page } from './Page' + +const meta: Meta = { + title: 'Example/Page', + component: Page, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: 'fullscreen', + }, +} + +export default meta +type Story = StoryObj + +export const LoggedOut: Story = {} + +// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing +export const LoggedIn: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement) + const loginButton = await canvas.getByRole('button', { + name: /Log in/i, + }) + await userEvent.click(loginButton) + }, +} diff --git a/examples/with-storybook/stories/Page.tsx b/examples/with-storybook/stories/Page.tsx new file mode 100644 index 000000000000..85f01f37cb1a --- /dev/null +++ b/examples/with-storybook/stories/Page.tsx @@ -0,0 +1,91 @@ +import React from 'react' + +import { Header } from './Header' +import './page.css' + +type User = { + name: string +} + +export const Page: React.FC = () => { + const [user, setUser] = React.useState() + + return ( +
+
setUser({ name: 'Jane Doe' })} + onLogout={() => setUser(undefined)} + onCreateAccount={() => setUser({ name: 'Jane Doe' })} + /> + +
+

Pages in Storybook

+

+ We recommend building UIs with a{' '} + + component-driven + {' '} + process starting with atomic components and ending with pages. +

+

+ Render pages with mock data. This makes it easy to build and review + page states without needing to navigate to them in your app. Here are + some handy patterns for managing page data in Storybook: +

+
    +
  • + Use a higher-level connected component. Storybook helps you compose + such data from the "args" of child component stories +
  • +
  • + Assemble data in the page component from your services. You can mock + these services out using Storybook. +
  • +
+

+ Get a guided tutorial on component-driven development at{' '} + + Storybook tutorials + + . Read more in the{' '} + + docs + + . +

+
+ Tip Adjust the width of the canvas with + the{' '} + + + + + + Viewports addon in the toolbar +
+
+
+ ) +} diff --git a/examples/with-storybook/stories/assets/code-brackets.svg b/examples/with-storybook/stories/assets/code-brackets.svg new file mode 100644 index 000000000000..73de94776001 --- /dev/null +++ b/examples/with-storybook/stories/assets/code-brackets.svg @@ -0,0 +1 @@ +illustration/code-brackets \ No newline at end of file diff --git a/examples/with-storybook/stories/assets/colors.svg b/examples/with-storybook/stories/assets/colors.svg new file mode 100644 index 000000000000..17d58d516e14 --- /dev/null +++ b/examples/with-storybook/stories/assets/colors.svg @@ -0,0 +1 @@ +illustration/colors \ No newline at end of file diff --git a/examples/with-storybook/stories/assets/comments.svg b/examples/with-storybook/stories/assets/comments.svg new file mode 100644 index 000000000000..6493a139f523 --- /dev/null +++ b/examples/with-storybook/stories/assets/comments.svg @@ -0,0 +1 @@ +illustration/comments \ No newline at end of file diff --git a/examples/with-storybook/stories/assets/direction.svg b/examples/with-storybook/stories/assets/direction.svg new file mode 100644 index 000000000000..65676ac27229 --- /dev/null +++ b/examples/with-storybook/stories/assets/direction.svg @@ -0,0 +1 @@ +illustration/direction \ No newline at end of file diff --git a/examples/with-storybook/stories/assets/flow.svg b/examples/with-storybook/stories/assets/flow.svg new file mode 100644 index 000000000000..8ac27db403c2 --- /dev/null +++ b/examples/with-storybook/stories/assets/flow.svg @@ -0,0 +1 @@ +illustration/flow \ No newline at end of file diff --git a/examples/with-storybook/stories/assets/plugin.svg b/examples/with-storybook/stories/assets/plugin.svg new file mode 100644 index 000000000000..29e5c690c0a2 --- /dev/null +++ b/examples/with-storybook/stories/assets/plugin.svg @@ -0,0 +1 @@ +illustration/plugin \ No newline at end of file diff --git a/examples/with-storybook/stories/assets/repo.svg b/examples/with-storybook/stories/assets/repo.svg new file mode 100644 index 000000000000..f386ee902c1f --- /dev/null +++ b/examples/with-storybook/stories/assets/repo.svg @@ -0,0 +1 @@ +illustration/repo \ No newline at end of file diff --git a/examples/with-storybook/stories/assets/stackalt.svg b/examples/with-storybook/stories/assets/stackalt.svg new file mode 100644 index 000000000000..9b7ad2743506 --- /dev/null +++ b/examples/with-storybook/stories/assets/stackalt.svg @@ -0,0 +1 @@ +illustration/stackalt \ No newline at end of file diff --git a/examples/with-storybook/stories/button.css b/examples/with-storybook/stories/button.css new file mode 100644 index 000000000000..dc91dc76370b --- /dev/null +++ b/examples/with-storybook/stories/button.css @@ -0,0 +1,30 @@ +.storybook-button { + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + border: 0; + border-radius: 3em; + cursor: pointer; + display: inline-block; + line-height: 1; +} +.storybook-button--primary { + color: white; + background-color: #1ea7fd; +} +.storybook-button--secondary { + color: #333; + background-color: transparent; + box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; +} +.storybook-button--small { + font-size: 12px; + padding: 10px 16px; +} +.storybook-button--medium { + font-size: 14px; + padding: 11px 20px; +} +.storybook-button--large { + font-size: 16px; + padding: 12px 24px; +} diff --git a/examples/with-storybook/stories/header.css b/examples/with-storybook/stories/header.css new file mode 100644 index 000000000000..44c549da27ce --- /dev/null +++ b/examples/with-storybook/stories/header.css @@ -0,0 +1,32 @@ +.wrapper { + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding: 15px 20px; + display: flex; + align-items: center; + justify-content: space-between; +} + +svg { + display: inline-block; + vertical-align: top; +} + +h1 { + font-weight: 700; + font-size: 20px; + line-height: 1; + margin: 6px 0 6px 10px; + display: inline-block; + vertical-align: top; +} + +button + button { + margin-left: 10px; +} + +.welcome { + color: #333; + font-size: 14px; + margin-right: 10px; +} diff --git a/examples/with-storybook/stories/page.css b/examples/with-storybook/stories/page.css new file mode 100644 index 000000000000..fb64fe462943 --- /dev/null +++ b/examples/with-storybook/stories/page.css @@ -0,0 +1,69 @@ +section { + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 24px; + padding: 48px 20px; + margin: 0 auto; + max-width: 600px; + color: #333; +} + +section h2 { + font-weight: 700; + font-size: 32px; + line-height: 1; + margin: 0 0 4px; + display: inline-block; + vertical-align: top; +} + +section p { + margin: 1em 0; +} + +section a { + text-decoration: none; + color: #1ea7fd; +} + +section ul { + padding-left: 30px; + margin: 1em 0; +} + +section li { + margin-bottom: 8px; +} + +section .tip { + display: inline-block; + border-radius: 1em; + font-size: 11px; + line-height: 12px; + font-weight: 700; + background: #e7fdd8; + color: #66bf3c; + padding: 4px 12px; + margin-right: 10px; + vertical-align: top; +} + +section .tip-wrapper { + font-size: 13px; + line-height: 20px; + margin-top: 40px; + margin-bottom: 40px; +} + +section .tip-wrapper svg { + display: inline-block; + height: 12px; + width: 12px; + margin-right: 4px; + vertical-align: top; + margin-top: 3px; +} + +section .tip-wrapper svg path { + fill: #1ea7fd; +} diff --git a/examples/with-storybook/stories/pages/absoluteImports.stories.jsx b/examples/with-storybook/stories/pages/absoluteImports.stories.jsx deleted file mode 100644 index 6d7014c996b9..000000000000 --- a/examples/with-storybook/stories/pages/absoluteImports.stories.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import AbsoluteImports from '../../pages/absoluteImports' - -export default { - title: 'Pages', - component: AbsoluteImports, -} - -export const AbsoluteImportsPage = () => diff --git a/examples/with-storybook/stories/pages/cssModules.stories.jsx b/examples/with-storybook/stories/pages/cssModules.stories.jsx deleted file mode 100644 index 85487cb6d02e..000000000000 --- a/examples/with-storybook/stories/pages/cssModules.stories.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import CssModules from '../../pages/cssModules' - -export default { - title: 'Pages', - component: CssModules, -} - -export const CssModulesPage = () => diff --git a/examples/with-storybook/stories/pages/globalStyleImports.stories.jsx b/examples/with-storybook/stories/pages/globalStyleImports.stories.jsx deleted file mode 100644 index 2ccd0d101ba5..000000000000 --- a/examples/with-storybook/stories/pages/globalStyleImports.stories.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import GlobalStyleImports from '../../pages/globalStyleImports' - -export default { - title: 'Pages', - component: GlobalStyleImports, -} - -export const GlobalStyleImportsPage = () => diff --git a/examples/with-storybook/stories/pages/home.stories.jsx b/examples/with-storybook/stories/pages/home.stories.jsx deleted file mode 100644 index 0469edb7e614..000000000000 --- a/examples/with-storybook/stories/pages/home.stories.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import Home from '../../pages/index' - -export default { - title: 'Pages', - component: Home, -} - -export const HomePage = () => diff --git a/examples/with-storybook/stories/pages/nextjsImages.stories.jsx b/examples/with-storybook/stories/pages/nextjsImages.stories.jsx deleted file mode 100644 index 373f4796f990..000000000000 --- a/examples/with-storybook/stories/pages/nextjsImages.stories.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import NextjsImages from '../../pages/nextjsImages' - -export default { - title: 'Pages', - component: NextjsImages, -} - -export const NextjsImagesPage = () => diff --git a/examples/with-storybook/stories/pages/nextjsRouting.stories.jsx b/examples/with-storybook/stories/pages/nextjsRouting.stories.jsx deleted file mode 100644 index 44a6df44fe0b..000000000000 --- a/examples/with-storybook/stories/pages/nextjsRouting.stories.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import NextjsRouting from '../../pages/nextjsRouting' - -export default { - title: 'Pages', - component: NextjsRouting, -} - -export const NextjsRoutingPage = () => - -NextjsRoutingPage.parameters = { - nextRouter: { - route: 'this-is-a-story-override', - }, -} diff --git a/examples/with-storybook/stories/pages/scssModules.stories.jsx b/examples/with-storybook/stories/pages/scssModules.stories.jsx deleted file mode 100644 index 5de7527f3e99..000000000000 --- a/examples/with-storybook/stories/pages/scssModules.stories.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import ScssModules from '../../pages/scssModules' - -export default { - title: 'Pages', - component: ScssModules, -} - -export const ScssModulesPage = () => diff --git a/examples/with-storybook/stories/pages/styledJsx.stories.jsx b/examples/with-storybook/stories/pages/styledJsx.stories.jsx deleted file mode 100644 index 1450c651082a..000000000000 --- a/examples/with-storybook/stories/pages/styledJsx.stories.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import StyledJsx from '../../pages/styledJsx' - -export default { - title: 'Pages', - component: StyledJsx, -} - -export const StyledJsxPage = () => diff --git a/examples/with-storybook/stories/pages/typescript.stories.tsx b/examples/with-storybook/stories/pages/typescript.stories.tsx deleted file mode 100644 index 40e0219b0399..000000000000 --- a/examples/with-storybook/stories/pages/typescript.stories.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import Typescript from '../../pages/typescript' - -export default { - title: 'Pages', - component: Typescript, -} - -export const TypescriptPage = () => diff --git a/examples/with-storybook/styles/CssModules.module.css b/examples/with-storybook/styles/CssModules.module.css deleted file mode 100644 index 42720e93f033..000000000000 --- a/examples/with-storybook/styles/CssModules.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.main { - padding: 4rem 0; - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.main span { - color: red; -} diff --git a/examples/with-storybook/styles/ScssModules.module.scss b/examples/with-storybook/styles/ScssModules.module.scss deleted file mode 100644 index 76d90a7cbb43..000000000000 --- a/examples/with-storybook/styles/ScssModules.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -.main { - padding: 4rem 0; - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - span { - color: steelblue; - } -} diff --git a/examples/with-storybook/styles/globals.css b/examples/with-storybook/styles/globals.css deleted file mode 100644 index be20427a07a5..000000000000 --- a/examples/with-storybook/styles/globals.css +++ /dev/null @@ -1,3 +0,0 @@ -main { - font-size: 1.25rem; -} diff --git a/examples/with-storybook/styles/globals.scss b/examples/with-storybook/styles/globals.scss deleted file mode 100644 index cafcfc8beb0b..000000000000 --- a/examples/with-storybook/styles/globals.scss +++ /dev/null @@ -1,3 +0,0 @@ -body { - background: azure; -} diff --git a/examples/with-storybook/tsconfig.json b/examples/with-storybook/tsconfig.json index cabfca7e4ffc..e06a4454ab06 100644 --- a/examples/with-storybook/tsconfig.json +++ b/examples/with-storybook/tsconfig.json @@ -1,21 +1,28 @@ { "compilerOptions": { "target": "es5", - "module": "esnext", - "moduleResolution": "node", - "isolatedModules": true, - "esModuleInterop": true, + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, "skipLibCheck": true, "strict": true, - "resolveJsonModule": true, - "jsx": "preserve", - "allowJs": true, "forceConsistentCasingInFileNames": true, "noEmit": true, - "lib": ["dom", "dom.iterable", "esnext"], + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", "incremental": true, - "baseUrl": "." + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } }, - "exclude": ["node_modules", ".next", "out", "storybook-static"], - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/examples/with-storybook/vercel.json b/examples/with-storybook/vercel.json new file mode 100644 index 000000000000..6ed087628029 --- /dev/null +++ b/examples/with-storybook/vercel.json @@ -0,0 +1,4 @@ +{ + "framework": "storybook", + "buildCommand": "storybook build" +} From 4ef3982f62655a9215361bf3c79a6f6a33f10d50 Mon Sep 17 00:00:00 2001 From: Baffin Lee Date: Wed, 14 Jun 2023 11:38:01 +0800 Subject: [PATCH 13/54] chore: remove useless duplicate code (#50705) ## What? Remove useless duplicate code in `next-server.ts`, there are exactly the same code in next lines. ## Why? That code have no side effect, run it twice won't make any difference, seems like someone forgot to delete it. ![image](https://github.com/vercel/next.js/assets/17738556/848d4b03-78cd-40a4-ae8b-a3126101efac) Co-authored-by: JJ Kasper --- packages/next/src/server/next-server.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index b8f98de808e6..be282da83a62 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -1542,10 +1542,6 @@ export default class NextNodeServer extends BaseServer { } } - if (match) { - addRequestMeta(req, '_nextMatch', match) - } - // Try to handle the given route with the configured handlers. if (match) { // Add the match to the request so we don't have to re-run the matcher From 3427d324d8cb3fc51d6f1a346085b918d929127c Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Tue, 13 Jun 2023 20:50:51 -0700 Subject: [PATCH 14/54] fix: app router hash scrolling should respect scroll-padding (#51268) When navigating to a route with a hash parameter, the layout router jumps to the element by scrolling to the node's `offsetTop` value. However, this will ignore `scroll-padding`, which deviates from browser behavior. It looks like this isn't an issue in the pages router which currently makes use of [`scrollIntoView`](https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/router.ts#L2262). Closes NEXT-1171 Fixes #49612 --------- --- .../src/client/components/layout-router.tsx | 5 ++- .../app/hash-with-scroll-offset/global.css | 8 ++++ .../app/hash-with-scroll-offset/page.js | 41 +++++++++++++++++++ .../e2e/app-dir/navigation/navigation.test.ts | 31 ++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 test/e2e/app-dir/navigation/app/hash-with-scroll-offset/global.css create mode 100644 test/e2e/app-dir/navigation/app/hash-with-scroll-offset/page.js diff --git a/packages/next/src/client/components/layout-router.tsx b/packages/next/src/client/components/layout-router.tsx index f2abe624e599..9a88df6b80e0 100644 --- a/packages/next/src/client/components/layout-router.tsx +++ b/packages/next/src/client/components/layout-router.tsx @@ -216,9 +216,10 @@ class InnerScrollAndFocusHandler extends React.Component { - // In case of hash scroll we need to scroll to the top of the element + // In case of hash scroll, we only need to scroll the element into view if (hashFragment) { - window.scrollTo(0, (domNode as HTMLElement).offsetTop) + ;(domNode as HTMLElement).scrollIntoView() + return } // Store the current viewport height because reading `clientHeight` causes a reflow, diff --git a/test/e2e/app-dir/navigation/app/hash-with-scroll-offset/global.css b/test/e2e/app-dir/navigation/app/hash-with-scroll-offset/global.css new file mode 100644 index 000000000000..4aa1b6b0014c --- /dev/null +++ b/test/e2e/app-dir/navigation/app/hash-with-scroll-offset/global.css @@ -0,0 +1,8 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-size: 14px; + line-height: 1; + scroll-padding-top: 20px; +} diff --git a/test/e2e/app-dir/navigation/app/hash-with-scroll-offset/page.js b/test/e2e/app-dir/navigation/app/hash-with-scroll-offset/page.js new file mode 100644 index 000000000000..a1c0ab4a13f3 --- /dev/null +++ b/test/e2e/app-dir/navigation/app/hash-with-scroll-offset/page.js @@ -0,0 +1,41 @@ +import Link from 'next/link' +import './global.css' + +export default function HashPage() { + // Create list of 5000 items that all have unique id + const items = Array.from({ length: 5000 }, (_, i) => ({ id: i })) + + return ( +
+

Hash Page

+ + To 6 + + + To 50 + + + To 160 + + + To 300 + + + To 500 (hash only) + + + To Top + + + To non-existent + +
+ {items.map((item) => ( +
+
{item.id}
+
+ ))} +
+
+ ) +} diff --git a/test/e2e/app-dir/navigation/navigation.test.ts b/test/e2e/app-dir/navigation/navigation.test.ts index 1a29a5c48135..8aaf93dd0bf3 100644 --- a/test/e2e/app-dir/navigation/navigation.test.ts +++ b/test/e2e/app-dir/navigation/navigation.test.ts @@ -87,6 +87,37 @@ createNextDescribe( }) }) + describe('hash-with-scroll-offset', () => { + it('should scroll to the specified hash', async () => { + const browser = await next.browser('/hash-with-scroll-offset') + + const checkLink = async ( + val: number | string, + expectedScroll: number + ) => { + await browser.elementByCss(`#link-to-${val.toString()}`).click() + await check( + async () => { + const val = await browser.eval('window.pageYOffset') + return val.toString() + }, + expectedScroll.toString(), + true, + // Try maximum of 15 seconds + 15 + ) + } + + await checkLink(6, 94) + await checkLink(50, 710) + await checkLink(160, 2250) + await checkLink(300, 4210) + await checkLink(500, 7010) // this one is hash only (`href="#hash-500"`) + await checkLink('top', 0) + await checkLink('non-existent', 0) + }) + }) + describe('hash-link-back-to-same-page', () => { it('should scroll to the specified hash', async () => { const browser = await next.browser('/hash-link-back-to-same-page') From 2bd76827f09bbd9c60ac87af0d8f48e815272ea3 Mon Sep 17 00:00:00 2001 From: Baffin Lee Date: Wed, 14 Jun 2023 12:04:10 +0800 Subject: [PATCH 15/54] Fix build error about trace file and edge route (#50808) ### What? Error occurs when processing trace file rules of edge route. ### Why? Edge routes have no trace files, so we don't need to process exclude and include rules about it's trace file. ### How? Fixes https://github.com/vercel/next.js/issues/50791 Co-authored-by: JJ Kasper --- packages/next/src/build/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index ee0f2f7f7f3c..de2732227d38 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -1770,6 +1770,12 @@ export default async function build( if (config.outputFileTracing) { for (let page of pageKeys.pages) { + // edge routes have no trace files + const pageInfo = pageInfos.get(page) + if (pageInfo?.runtime === 'edge') { + continue + } + const combinedIncludes = new Set() const combinedExcludes = new Set() From 8ac9b342e3b9699deef29bc50c018b8e2da9bc54 Mon Sep 17 00:00:00 2001 From: Choi Sumin Date: Wed, 14 Jun 2023 13:10:36 +0900 Subject: [PATCH 16/54] chore(router): fix typo in comment (#50581) ### What? fix typo in comment (withRoute -> withRouter) --- packages/next/src/client/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/client/router.ts b/packages/next/src/client/router.ts index 64d906d294d9..1172177c0071 100644 --- a/packages/next/src/client/router.ts +++ b/packages/next/src/client/router.ts @@ -126,7 +126,7 @@ routerEvents.forEach((event) => { // Export the singletonRouter and this is the public API. export default singletonRouter as SingletonRouter -// Reexport the withRoute HOC +// Reexport the withRouter HOC export { default as withRouter } from './with-router' export function useRouter(): NextRouter { From 6dcc4fbc236d177df579364f92864868b69b0705 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 13 Jun 2023 21:31:40 -0700 Subject: [PATCH 17/54] Increase remote cache timeout for swc builds (#51270) These occasionally timeout and start building even when they don't need to so this increases them a bit from the default. --- .github/workflows/build_and_deploy.yml | 20 ++++++++++---------- .github/workflows/build_reusable.yml | 2 +- package.json | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 012c4164bdbc..657fa1386bbb 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -81,17 +81,17 @@ jobs: target: 'x86_64-apple-darwin' build: | npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" && if [ ! -f $(dirname $(which yarn))/pnpm ]; then ln -s $(which yarn) $(dirname $(which yarn))/pnpm;fi - turbo run build-native-release --summarize -- --target x86_64-apple-darwin --release + turbo run build-native-release --remote-cache-timeout 90 --summarize -- --target x86_64-apple-darwin --release strip -x packages/next-swc/native/next-swc.*.node - host: windows-latest build: | npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" "pnpm@${PNPM_VERSION}" - turbo run build-native-release --summarize -- --target x86_64-pc-windows-msvc + turbo run build-native-release --remote-cache-timeout 90 --summarize -- --target x86_64-pc-windows-msvc target: 'x86_64-pc-windows-msvc' - host: windows-latest build: | npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" "pnpm@${PNPM_VERSION}" - turbo run build-native-no-plugin --summarize -- --release --target i686-pc-windows-msvc + turbo run build-native-no-plugin --remote-cache-timeout 90 --summarize -- --release --target i686-pc-windows-msvc target: 'i686-pc-windows-msvc' - host: ubuntu-latest target: 'x86_64-unknown-linux-gnu' @@ -103,7 +103,7 @@ jobs: rustup target add x86_64-unknown-linux-gnu && npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" && if [ ! -f $(dirname $(which yarn))/pnpm ]; then ln -s $(which yarn) $(dirname $(which yarn))/pnpm;fi && unset CC_x86_64_unknown_linux_gnu && unset CC && - turbo run build-native-release --summarize -- --target x86_64-unknown-linux-gnu && + turbo run build-native-release --remote-cache-timeout 90 --summarize -- --target x86_64-unknown-linux-gnu && strip packages/next-swc/native/next-swc.*.node - host: ubuntu-latest target: 'x86_64-unknown-linux-musl' @@ -115,7 +115,7 @@ jobs: rustup default "${RUST_TOOLCHAIN}" && rustup target add x86_64-unknown-linux-musl && npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" && if [ ! -f $(dirname $(which yarn))/pnpm ]; then ln -s $(which yarn) $(dirname $(which yarn))/pnpm;fi && - turbo run build-native-release --summarize -- --target x86_64-unknown-linux-musl && + turbo run build-native-release --remote-cache-timeout 90 --summarize -- --target x86_64-unknown-linux-musl && strip packages/next-swc/native/next-swc.*.node - host: macos-latest target: 'aarch64-apple-darwin' @@ -126,7 +126,7 @@ jobs: SYSROOT=$(xcrun --sdk macosx --show-sdk-path); export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT"; npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" && if [ ! -f $(dirname $(which yarn))/pnpm ]; then ln -s $(which yarn) $(dirname $(which yarn))/pnpm;fi - turbo run build-native-release --summarize -- --target aarch64-apple-darwin + turbo run build-native-release --remote-cache-timeout 90 --summarize -- --target aarch64-apple-darwin strip -x packages/next-swc/native/next-swc.*.node - host: ubuntu-latest target: 'aarch64-unknown-linux-gnu' @@ -139,7 +139,7 @@ jobs: rustup target add aarch64-unknown-linux-gnu && npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" && if [ ! -f $(dirname $(which yarn))/pnpm ]; then ln -s $(which yarn) $(dirname $(which yarn))/pnpm;fi && export CC_aarch64_unknown_linux_gnu=/usr/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-gcc && - turbo run build-native-release --summarize -- --target aarch64-unknown-linux-gnu && + turbo run build-native-release --remote-cache-timeout 90 --summarize -- --target aarch64-unknown-linux-gnu && llvm-strip -x packages/next-swc/native/next-swc.*.node - host: ubuntu-latest target: 'aarch64-unknown-linux-musl' @@ -152,13 +152,13 @@ jobs: rustup toolchain install "${RUST_TOOLCHAIN}" && rustup default "${RUST_TOOLCHAIN}" && rustup target add aarch64-unknown-linux-musl && - turbo run build-native-release --summarize -- --target aarch64-unknown-linux-musl && + turbo run build-native-release --remote-cache-timeout 90 --summarize -- --target aarch64-unknown-linux-musl && llvm-strip -x packages/next-swc/native/next-swc.*.node - host: windows-latest target: 'aarch64-pc-windows-msvc' build: | npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}" "turbo@${TURBO_VERSION}" "pnpm@${PNPM_VERSION}" - turbo run build-native-no-plugin-woa-release --summarize -- --target aarch64-pc-windows-msvc + turbo run build-native-no-plugin-woa-release --remote-cache-timeout 90 --summarize -- --target aarch64-pc-windows-msvc needs: build name: stable - ${{ matrix.settings.target }} - node@16 runs-on: ${{ matrix.settings.host }} @@ -323,7 +323,7 @@ jobs: run: node scripts/normalize-version-bump.js - name: Build - run: turbo run build-wasm --summarize -- --target ${{ matrix.target }} --features tracing/release_max_level_info + run: turbo run build-wasm --remote-cache-timeout 90 --summarize -- --target ${{ matrix.target }} --features tracing/release_max_level_info - name: Add target to folder name run: '[[ -d "packages/next-swc/crates/wasm/pkg" ]] && mv packages/next-swc/crates/wasm/pkg packages/next-swc/crates/wasm/pkg-${{ matrix.target }} || ls packages/next-swc/crates/wasm' diff --git a/.github/workflows/build_reusable.yml b/.github/workflows/build_reusable.yml index 8964d728fb45..67c277f781b3 100644 --- a/.github/workflows/build_reusable.yml +++ b/.github/workflows/build_reusable.yml @@ -107,7 +107,7 @@ jobs: - run: node scripts/normalize-version-bump.js name: normalize versions - - run: turbo run build-native-release --summarize -- --target x86_64-unknown-linux-gnu + - run: turbo run build-native-release --remote-cache-timeout 90 --summarize -- --target x86_64-unknown-linux-gnu if: ${{ inputs.skipNativeBuild != 'yes' }} - name: Upload next-swc artifact diff --git a/package.json b/package.json index 7c142e9fd433..62d7fdd69afb 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "new-error": "plop error", "new-test": "plop test", "clean": "lerna clean -y && lerna bootstrap && lerna run clean && lerna exec 'node ../../scripts/rm.mjs dist'", - "build": "turbo run build --summarize true", + "build": "turbo run build --remote-cache-timeout 60 --summarize true", "lerna": "lerna", "dev": "turbo run dev --parallel", "test-types": "tsc", From a59c1f0d58d75330c6d5d76fc4f79cae8e486582 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 14 Jun 2023 06:41:56 +0200 Subject: [PATCH 18/54] Fix with typescript stripe example fixes #50541 (#50574) ### What? The TypeScript Stripe example has been fixed to address various issues. One of the issues was related to calling a function, which has now been resolved. Additionally, the Stripe API used in the example was outdated, causing an error during the build process. This issue has also been fixed. By making these fixes, the TypeScript Stripe example should now work correctly without any function calling issues or errors related to the outdated Stripe API during the build. ### Why? There's an issue regarding this problem `Fix with typescript stripe example fixes #50541` ```console $ next build - info Linting and checking validity of types ..- error ESLint must be installed in order to run during builds: yarn add --dev eslint - info Linting and checking validity of types ...Failed to compile. ./components/CartSummary.tsx:41:24 Type error: Argument of type '{ sessionId: any; }' is not assignable to parameter of type 'string'. 39 | } 40 | > 41 | redirectToCheckout({ sessionId: response.id }) | ^ 42 | } 43 | 44 | return ( error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. ``` ### How? - Calling the `redirectToCheckout` function the right way https://github.com/dayhaysoos/use-shopping-cart/blob/275cb65f3d2fccb15857f4b9278cbbfca11eaf7a/use-shopping-cart/react/index.d.ts#L65 - Updating Stripe API - Fix some changed / depcrated code since the API has changed a little - Fix a TS error since the used library does not has a correct TS defintion file ![image](https://github.com/vercel/next.js/assets/987947/dd21dbb0-e949-4b3e-a26c-6a6d720c6e37) Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- .../components/CartSummary.tsx | 7 +++---- .../pages/api/checkout_sessions/[id].ts | 3 ++- .../pages/api/checkout_sessions/cart.ts | 10 ++++++---- .../pages/api/checkout_sessions/index.ts | 14 +++++++++----- .../pages/api/payment_intents/index.ts | 6 +++--- .../pages/api/webhooks/index.ts | 7 ++++--- 6 files changed, 27 insertions(+), 20 deletions(-) diff --git a/examples/with-stripe-typescript/components/CartSummary.tsx b/examples/with-stripe-typescript/components/CartSummary.tsx index a1262ae77b3f..c37e4986d5c8 100644 --- a/examples/with-stripe-typescript/components/CartSummary.tsx +++ b/examples/with-stripe-typescript/components/CartSummary.tsx @@ -1,9 +1,8 @@ -import React, { useState, useEffect } from 'react' +import React, { useEffect, useState } from 'react' import StripeTestCards from '../components/StripeTestCards' - -import { useShoppingCart } from 'use-shopping-cart' import { fetchPostJSON } from '../utils/api-helpers' +import { useShoppingCart } from 'use-shopping-cart' const CartSummary = () => { const [loading, setLoading] = useState(false) @@ -38,7 +37,7 @@ const CartSummary = () => { return } - redirectToCheckout({ sessionId: response.id }) + redirectToCheckout(response.id) } return ( diff --git a/examples/with-stripe-typescript/pages/api/checkout_sessions/[id].ts b/examples/with-stripe-typescript/pages/api/checkout_sessions/[id].ts index 7dee01bda1b0..0f2ca37634c3 100644 --- a/examples/with-stripe-typescript/pages/api/checkout_sessions/[id].ts +++ b/examples/with-stripe-typescript/pages/api/checkout_sessions/[id].ts @@ -1,9 +1,10 @@ import { NextApiRequest, NextApiResponse } from 'next' import Stripe from 'stripe' + const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { // https://github.com/stripe/stripe-node#configuration - apiVersion: '2020-08-27', + apiVersion: '2022-08-01', }) export default async function handler( diff --git a/examples/with-stripe-typescript/pages/api/checkout_sessions/cart.ts b/examples/with-stripe-typescript/pages/api/checkout_sessions/cart.ts index 2ad18d699360..b010b8449705 100644 --- a/examples/with-stripe-typescript/pages/api/checkout_sessions/cart.ts +++ b/examples/with-stripe-typescript/pages/api/checkout_sessions/cart.ts @@ -1,5 +1,9 @@ import { NextApiRequest, NextApiResponse } from 'next' +import Stripe from 'stripe' +// @ts-ignore +import { validateCartItems } from 'use-shopping-cart/utilities' + /* * Product data can be loaded from anywhere. In this case, we’re loading it from * a local JSON file, but this could also come from an async call to your @@ -8,13 +12,11 @@ import { NextApiRequest, NextApiResponse } from 'next' * The important thing is that the product info is loaded from somewhere trusted * so you know the pricing information is accurate. */ -import { validateCartItems } from 'use-shopping-cart/utilities/serverless' import inventory from '../../../data/products' -import Stripe from 'stripe' const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { // https://github.com/stripe/stripe-node#configuration - apiVersion: '2020-08-27', + apiVersion: '2022-08-01', }) export default async function handler( @@ -25,7 +27,7 @@ export default async function handler( try { // Validate the cart details that were sent from the client. const line_items = validateCartItems(inventory as any, req.body) - const hasSubscription = line_items.find((item) => { + const hasSubscription = line_items.find((item: any) => { return !!item.price_data.recurring }) // Create Checkout Sessions from body params. diff --git a/examples/with-stripe-typescript/pages/api/checkout_sessions/index.ts b/examples/with-stripe-typescript/pages/api/checkout_sessions/index.ts index bb1707fdbdd4..1ec2f31e6617 100644 --- a/examples/with-stripe-typescript/pages/api/checkout_sessions/index.ts +++ b/examples/with-stripe-typescript/pages/api/checkout_sessions/index.ts @@ -1,12 +1,12 @@ +import { CURRENCY, MAX_AMOUNT, MIN_AMOUNT } from '../../../config' import { NextApiRequest, NextApiResponse } from 'next' -import { CURRENCY, MIN_AMOUNT, MAX_AMOUNT } from '../../../config' +import Stripe from 'stripe' import { formatAmountForStripe } from '../../../utils/stripe-helpers' -import Stripe from 'stripe' const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { // https://github.com/stripe/stripe-node#configuration - apiVersion: '2020-08-27', + apiVersion: '2022-08-01', }) export default async function handler( @@ -26,10 +26,14 @@ export default async function handler( payment_method_types: ['card'], line_items: [ { - name: 'Custom amount donation', amount: formatAmountForStripe(amount, CURRENCY), - currency: CURRENCY, quantity: 1, + price_data: { + currency: CURRENCY, + product_data: { + name: 'Custom amount donation', + }, + }, }, ], success_url: `${req.headers.origin}/result?session_id={CHECKOUT_SESSION_ID}`, diff --git a/examples/with-stripe-typescript/pages/api/payment_intents/index.ts b/examples/with-stripe-typescript/pages/api/payment_intents/index.ts index 0ef19676de94..4a169cbfcbfc 100644 --- a/examples/with-stripe-typescript/pages/api/payment_intents/index.ts +++ b/examples/with-stripe-typescript/pages/api/payment_intents/index.ts @@ -1,12 +1,12 @@ +import { CURRENCY, MAX_AMOUNT, MIN_AMOUNT } from '../../../config' import { NextApiRequest, NextApiResponse } from 'next' -import { CURRENCY, MIN_AMOUNT, MAX_AMOUNT } from '../../../config' +import Stripe from 'stripe' import { formatAmountForStripe } from '../../../utils/stripe-helpers' -import Stripe from 'stripe' const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { // https://github.com/stripe/stripe-node#configuration - apiVersion: '2020-08-27', + apiVersion: '2022-08-01', }) export default async function handler( diff --git a/examples/with-stripe-typescript/pages/api/webhooks/index.ts b/examples/with-stripe-typescript/pages/api/webhooks/index.ts index 72043581b2e3..eba4a7002dad 100644 --- a/examples/with-stripe-typescript/pages/api/webhooks/index.ts +++ b/examples/with-stripe-typescript/pages/api/webhooks/index.ts @@ -1,11 +1,12 @@ -import { buffer } from 'micro' -import Cors from 'micro-cors' import { NextApiRequest, NextApiResponse } from 'next' +import Cors from 'micro-cors' import Stripe from 'stripe' +import { buffer } from 'micro' + const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { // https://github.com/stripe/stripe-node#configuration - apiVersion: '2020-08-27', + apiVersion: '2022-08-01', }) const webhookSecret: string = process.env.STRIPE_WEBHOOK_SECRET! From 6f9c9ad1d8c5739dcf62fe1a6c4525a02fdd6893 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 14 Jun 2023 06:53:50 +0200 Subject: [PATCH 19/54] fix: cypress component testing - fixes #50283 (#50303) ### What? Fixing the cypress component testing example, since it breaks the next.js build process. ### Why? The default example is not working regarding this issue: https://github.com/vercel/next.js/issues/50283 Because there's a cypress test file (`*.cy.tsx`) inside the `/pages` directory. ### How? In this pull request, I made the following changes: - Removed the new Cypress component test file from the /pages directory to resolve conflicts with the build process. - Created a dedicated directory for components, and created a new example component. - Moved the test file to the new component directory to ensure it works as expected. Build works: ![image](https://github.com/vercel/next.js/assets/987947/f79cc585-ad32-4780-ba56-a5f28259c69c) Starting the new component testing works: ![image](https://github.com/vercel/next.js/assets/987947/032cff8f-5d1f-4aa2-b6b6-c95b5739e838) Cypress Component Testing starts correctly: ![image](https://github.com/vercel/next.js/assets/987947/dff446b7-533b-4f6a-8f6c-09066866ba41) The Component Test is there and ready to test: ![image](https://github.com/vercel/next.js/assets/987947/c235e1bb-9097-47b9-a625-a71b2ef5e129) The Component Test passed as expected: ![image](https://github.com/vercel/next.js/assets/987947/fbaeaa18-5b63-4de6-9953-486bd7959703) --------- Co-authored-by: JJ Kasper --- .../about-component.cy.tsx} | 7 +++---- .../with-cypress/components/about-component.tsx | 14 ++++++++++++++ examples/with-cypress/pages/about.tsx | 7 ++----- 3 files changed, 19 insertions(+), 9 deletions(-) rename examples/with-cypress/{pages/about.cy.tsx => components/about-component.cy.tsx} (86%) create mode 100644 examples/with-cypress/components/about-component.tsx diff --git a/examples/with-cypress/pages/about.cy.tsx b/examples/with-cypress/components/about-component.cy.tsx similarity index 86% rename from examples/with-cypress/pages/about.cy.tsx rename to examples/with-cypress/components/about-component.cy.tsx index d5372f74aac8..fe26b103e868 100644 --- a/examples/with-cypress/pages/about.cy.tsx +++ b/examples/with-cypress/components/about-component.cy.tsx @@ -1,15 +1,14 @@ +import AboutComponent from './about-component' /* eslint-disable */ // Disable ESLint to prevent failing linting inside the Next.js repo. // If you're using ESLint on your project, we recommend installing the ESLint Cypress plugin instead: // https://github.com/cypress-io/eslint-plugin-cypress -import About from './about' - // Cypress Component Test -describe('', () => { +describe('', () => { it('should render and display expected content', () => { // Mount the React component for the About page - cy.mount() + cy.mount() // The new page should contain an h1 with "About page" cy.get('h1').contains('About Page') diff --git a/examples/with-cypress/components/about-component.tsx b/examples/with-cypress/components/about-component.tsx new file mode 100644 index 000000000000..7e5170c5bf1c --- /dev/null +++ b/examples/with-cypress/components/about-component.tsx @@ -0,0 +1,14 @@ +import Link from 'next/link' +import React from 'react' +import styles from '../styles/Home.module.css' + +export default function AboutComponent() { + return ( + <> +

About Page

+

+ ← Go Back +

+ + ) +} diff --git a/examples/with-cypress/pages/about.tsx b/examples/with-cypress/pages/about.tsx index c70cc9a8b853..b0f7f499e918 100644 --- a/examples/with-cypress/pages/about.tsx +++ b/examples/with-cypress/pages/about.tsx @@ -1,14 +1,11 @@ +import AboutComponent from '../components/about-component' import styles from '../styles/Home.module.css' -import Link from 'next/link' export default function About() { return (
-

About Page

-

- ← Go Back -

+
) From d080c8ee79da5f50f4a846e8abee074cc54b5c74 Mon Sep 17 00:00:00 2001 From: Akira Kijinami <62680807+kijikunnn@users.noreply.github.com> Date: Wed, 14 Jun 2023 14:00:35 +0900 Subject: [PATCH 20/54] fix deprecated configureStore in with-redux-saga example (#50342) This PR introduces the following changes: - Replaced redux's [deprecated createStore](https://github.com/reduxjs/redux/pull/4336) with configureStore from @reduxjs/toolkit. - Updated packages. - Corrected an error in getStaticProps. This is my first contribution and there may be some shortcomings, I appreciate your understanding and look forward to your feedback. --------- Co-authored-by: JJ Kasper --- examples/with-redux-saga/package.json | 9 +++++---- examples/with-redux-saga/pages/index.js | 19 +++++++++++-------- examples/with-redux-saga/pages/other.js | 9 ++++++--- examples/with-redux-saga/store.js | 10 ++++++++-- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/examples/with-redux-saga/package.json b/examples/with-redux-saga/package.json index ca733c7cc739..979d1cd24129 100644 --- a/examples/with-redux-saga/package.json +++ b/examples/with-redux-saga/package.json @@ -6,13 +6,14 @@ "start": "next start" }, "dependencies": { + "@reduxjs/toolkit": "^1.9.5", "next": "latest", - "next-redux-wrapper": "^6.0.2", + "next-redux-wrapper": "^8.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "7.2.0", - "redux": "4.0.5", - "redux-saga": "1.1.3" + "react-redux": "8.0.5", + "redux": "4.2.1", + "redux-saga": "1.2.3" }, "devDependencies": { "redux-devtools-extension": "^2.13.8" diff --git a/examples/with-redux-saga/pages/index.js b/examples/with-redux-saga/pages/index.js index b83381697344..38356265f1f7 100644 --- a/examples/with-redux-saga/pages/index.js +++ b/examples/with-redux-saga/pages/index.js @@ -15,15 +15,18 @@ const Index = () => { return } -export const getStaticProps = wrapper.getStaticProps(async ({ store }) => { - store.dispatch(tickClock(false)) +export const getStaticProps = wrapper.getStaticProps( + (store) => + async ({ params }) => { + await store.dispatch(tickClock(false)) - if (!store.getState().placeholderData) { - store.dispatch(loadData()) - store.dispatch(END) - } + if (!store.getState().placeholderData) { + await store.dispatch(loadData()) + store.dispatch(END) + } - await store.sagaTask.toPromise() -}) + await store.sagaTask.toPromise() + } +) export default Index diff --git a/examples/with-redux-saga/pages/other.js b/examples/with-redux-saga/pages/other.js index f8a11a750d44..cddd30d96316 100644 --- a/examples/with-redux-saga/pages/other.js +++ b/examples/with-redux-saga/pages/other.js @@ -14,8 +14,11 @@ const Other = () => { return } -export const getStaticProps = wrapper.getStaticProps(async ({ store }) => { - store.dispatch(tickClock(false)) -}) +export const getStaticProps = wrapper.getStaticProps( + (store) => + async ({ params }) => { + await store.dispatch(tickClock(false)) + } +) export default Other diff --git a/examples/with-redux-saga/store.js b/examples/with-redux-saga/store.js index b24670897fc2..909d8be249bf 100644 --- a/examples/with-redux-saga/store.js +++ b/examples/with-redux-saga/store.js @@ -1,9 +1,10 @@ -import { applyMiddleware, createStore } from 'redux' +import { applyMiddleware } from 'redux' import createSagaMiddleware from 'redux-saga' import { createWrapper } from 'next-redux-wrapper' import rootReducer from './reducer' import rootSaga from './saga' +import { configureStore } from '@reduxjs/toolkit' const bindMiddleware = (middleware) => { if (process.env.NODE_ENV !== 'production') { @@ -15,7 +16,12 @@ const bindMiddleware = (middleware) => { export const makeStore = (context) => { const sagaMiddleware = createSagaMiddleware() - const store = createStore(rootReducer, bindMiddleware([sagaMiddleware])) + const store = configureStore({ + reducer: rootReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ thunk: false }).concat(sagaMiddleware), + enhancers: [bindMiddleware([sagaMiddleware])], + }) store.sagaTask = sagaMiddleware.run(rootSaga) From dd61ee44c59ea75f8a25ca4ecbf5f9e8db8cd223 Mon Sep 17 00:00:00 2001 From: Dustin Thurston Date: Wed, 14 Jun 2023 01:42:00 -0400 Subject: [PATCH 21/54] Show how to utilize the provided .env file in the docker-compose file (#50712) ### What? I added a small change to the `with-docker-compose` example to show how to make the provided `.env` file usable. ### Why? I struggled to understand why I could not get environment variables to work as intended and thought this small change might help someone else new to docker in the future. ### How? Added a few comments to the `docker-compose.dev.yml` file, along with showing how to utilize a `.env` file with docker-compose Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- examples/with-docker-compose/docker-compose.dev.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/with-docker-compose/docker-compose.dev.yml b/examples/with-docker-compose/docker-compose.dev.yml index 71d23351e719..1dd01ce48253 100644 --- a/examples/with-docker-compose/docker-compose.dev.yml +++ b/examples/with-docker-compose/docker-compose.dev.yml @@ -6,9 +6,15 @@ services: build: context: ./next-app dockerfile: dev.Dockerfile + + # Set environment variables directly in the docker-compose file environment: ENV_VARIABLE: ${ENV_VARIABLE} NEXT_PUBLIC_ENV_VARIABLE: ${NEXT_PUBLIC_ENV_VARIABLE} + + # Set envrionment variables based on the .env file + env_file: + - .env volumes: - ./next-app/src:/app/src - ./next-app/public:/app/public From 825eefe7c0492c6cdc2d464f2679c40c27512e16 Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Wed, 14 Jun 2023 07:49:50 +0200 Subject: [PATCH 22/54] Add mikroORM to external package list (#50487) ### What? Adds mikroORM to external package list ### Why? To prevent developers using [MedusaJS](https://github.com/medusajs/medusa) modules with NextJS from having to add this in their next config. --------- Co-authored-by: JJ Kasper --- packages/next/src/lib/server-external-packages.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/next/src/lib/server-external-packages.json b/packages/next/src/lib/server-external-packages.json index 2af3637b9267..9fed54504e6c 100644 --- a/packages/next/src/lib/server-external-packages.json +++ b/packages/next/src/lib/server-external-packages.json @@ -1,11 +1,13 @@ [ "@blockfrost/blockfrost-js", "@jpg-store/lucid-cardano", + "@mikro-orm/core", + "@mikro-orm/knex", "@prisma/client", - "@vercel/og", "@sentry/nextjs", "@sentry/node", "@swc/core", + "@vercel/og", "argon2", "autoprefixer", "aws-crt", @@ -25,10 +27,10 @@ "next-mdx-remote", "next-seo", "pg", + "playwright", "postcss", "prettier", "prisma", - "playwright", "puppeteer", "rimraf", "sharp", From 667a90262bdfe522f6637da9311e9eb1ac696a84 Mon Sep 17 00:00:00 2001 From: Joost De Cock Date: Wed, 14 Jun 2023 07:53:04 +0200 Subject: [PATCH 23/54] [docs] Clarify `.md` handling with @next/mdx (#49785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …iles as MDX I got bitten by this passage because it reads as if merely changing the `extension` setting will cause `.md` files to be loaded as MDX. See: https://github.com/mdx-js/mdx/issues/2302 --------- Co-authored-by: JJ Kasper --- packages/next-mdx/readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/next-mdx/readme.md b/packages/next-mdx/readme.md index 250906682e3b..2b7941901d2c 100644 --- a/packages/next-mdx/readme.md +++ b/packages/next-mdx/readme.md @@ -51,7 +51,8 @@ module.exports = withMDX({ }) ``` -Optionally you can match other file extensions for MDX compilation, by default only `.mdx` is supported +By default MDX will only match and compile MDX files with the `.mdx` extension. +However, it can also be optionally configured to handle markdown files with the `.md` extension, as shown below: ```js // next.config.js @@ -61,6 +62,8 @@ const withMDX = require('@next/mdx')({ module.exports = withMDX() ``` +In addition, MDX can be customized with compiler options, see the [mdx documentation](https://mdxjs.com/packages/mdx/#compilefile-options) for details on supported options. + ## Top level .mdx pages Define the `pageExtensions` option to have Next.js handle `.md` and `.mdx` files in the `pages` directory as pages: From 2ce15c4d3c233d3b5bdb66b96d10e9602afdc2b0 Mon Sep 17 00:00:00 2001 From: Lazar Nikolov Date: Wed, 14 Jun 2023 02:10:06 -0400 Subject: [PATCH 24/54] update(examples): Update with-sentry example (#47855) Updating the `with-sentry` example with latest dependencies versions, new feature docs, and also converted it to TypeScript. ## Documentation / Examples - [x] Make sure the linting passes by running `pnpm build && pnpm lint` - [x] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- examples/with-sentry-simple/.gitignore | 11 +- examples/with-sentry/.gitignore | 11 +- examples/with-sentry/README.md | 2 +- examples/with-sentry/next-env.d.ts | 5 + examples/with-sentry/next.config.js | 1 + examples/with-sentry/package.json | 10 +- .../with-sentry/pages/{404.js => 404.tsx} | 0 examples/with-sentry/pages/_app.js | 4 - examples/with-sentry/pages/_app.tsx | 5 + examples/with-sentry/pages/_document.tsx | 13 + .../pages/{_error.js => _error.tsx} | 0 .../pages/api/{test1.js => test1.ts} | 0 .../pages/api/{test2.js => test2.ts} | 0 .../pages/api/{test3.js => test3.ts} | 0 .../pages/api/{test4.js => test4.ts} | 0 .../pages/client/{test1.js => test1.tsx} | 0 .../pages/client/{test2.js => test2.tsx} | 0 .../pages/client/{test3.js => test3.tsx} | 0 .../pages/client/{test4.js => test4.tsx} | 0 .../pages/client/{test5.js => test5.tsx} | 0 examples/with-sentry/pages/index.js | 163 ---------- examples/with-sentry/pages/index.tsx | 285 +++++++++++++++++ .../pages/ssr/{test1.js => test1.tsx} | 0 .../pages/ssr/{test2.js => test2.tsx} | 0 .../pages/ssr/{test3.js => test3.tsx} | 0 .../pages/ssr/{test4.js => test4.tsx} | 0 examples/with-sentry/public/favicon.ico | Bin 0 -> 39535 bytes examples/with-sentry/public/next.svg | 1 + examples/with-sentry/public/sentry.svg | 1 + examples/with-sentry/public/thirteen.svg | 1 + examples/with-sentry/public/vercel.svg | 1 + examples/with-sentry/sentry.client.config.js | 16 + examples/with-sentry/sentry.edge.config.js | 6 +- examples/with-sentry/sentry.server.config.js | 2 + examples/with-sentry/styles/Home.module.css | 301 ++++++++++++++++++ examples/with-sentry/styles/globals.css | 107 +++++++ examples/with-sentry/tsconfig.json | 20 ++ 37 files changed, 788 insertions(+), 178 deletions(-) create mode 100644 examples/with-sentry/next-env.d.ts rename examples/with-sentry/pages/{404.js => 404.tsx} (100%) delete mode 100644 examples/with-sentry/pages/_app.js create mode 100644 examples/with-sentry/pages/_app.tsx create mode 100644 examples/with-sentry/pages/_document.tsx rename examples/with-sentry/pages/{_error.js => _error.tsx} (100%) rename examples/with-sentry/pages/api/{test1.js => test1.ts} (100%) rename examples/with-sentry/pages/api/{test2.js => test2.ts} (100%) rename examples/with-sentry/pages/api/{test3.js => test3.ts} (100%) rename examples/with-sentry/pages/api/{test4.js => test4.ts} (100%) rename examples/with-sentry/pages/client/{test1.js => test1.tsx} (100%) rename examples/with-sentry/pages/client/{test2.js => test2.tsx} (100%) rename examples/with-sentry/pages/client/{test3.js => test3.tsx} (100%) rename examples/with-sentry/pages/client/{test4.js => test4.tsx} (100%) rename examples/with-sentry/pages/client/{test5.js => test5.tsx} (100%) delete mode 100644 examples/with-sentry/pages/index.js create mode 100644 examples/with-sentry/pages/index.tsx rename examples/with-sentry/pages/ssr/{test1.js => test1.tsx} (100%) rename examples/with-sentry/pages/ssr/{test2.js => test2.tsx} (100%) rename examples/with-sentry/pages/ssr/{test3.js => test3.tsx} (100%) rename examples/with-sentry/pages/ssr/{test4.js => test4.tsx} (100%) create mode 100644 examples/with-sentry/public/favicon.ico create mode 100644 examples/with-sentry/public/next.svg create mode 100644 examples/with-sentry/public/sentry.svg create mode 100644 examples/with-sentry/public/thirteen.svg create mode 100644 examples/with-sentry/public/vercel.svg create mode 100644 examples/with-sentry/styles/Home.module.css create mode 100644 examples/with-sentry/styles/globals.css create mode 100644 examples/with-sentry/tsconfig.json diff --git a/examples/with-sentry-simple/.gitignore b/examples/with-sentry-simple/.gitignore index 8f322f0d8f49..4cafb788d92d 100644 --- a/examples/with-sentry-simple/.gitignore +++ b/examples/with-sentry-simple/.gitignore @@ -23,6 +23,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +.pnpm-debug.log* # local env files .env*.local @@ -30,6 +31,10 @@ yarn-error.log* # vercel .vercel -# typescript -*.tsbuildinfo -next-env.d.ts +# Sentry +.sentryclirc + +# Sentry +next.config.original.js +sentry.properties + diff --git a/examples/with-sentry/.gitignore b/examples/with-sentry/.gitignore index 8f322f0d8f49..4cafb788d92d 100644 --- a/examples/with-sentry/.gitignore +++ b/examples/with-sentry/.gitignore @@ -23,6 +23,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +.pnpm-debug.log* # local env files .env*.local @@ -30,6 +31,10 @@ yarn-error.log* # vercel .vercel -# typescript -*.tsbuildinfo -next-env.d.ts +# Sentry +.sentryclirc + +# Sentry +next.config.original.js +sentry.properties + diff --git a/examples/with-sentry/README.md b/examples/with-sentry/README.md index 100ffa8eab57..78d1c307ad62 100644 --- a/examples/with-sentry/README.md +++ b/examples/with-sentry/README.md @@ -14,7 +14,7 @@ Preview the example live on [StackBlitz](http://stackblitz.com/): ## Deploy your own -It only takes a few steps to create and deploy your own version of this example app. Before you begin, make sure you have [linked your Vercel account to GitHub](https://vercel.com/docs/personal-accounts/login-connections), and [set up a project in Sentry](https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/). +It only takes a few steps to create and deploy your own version of this example app. Before you begin, make sure you have [linked your Vercel account to GitHub](https://vercel.com/docs/teams-and-accounts#existing-login-connection), and [set up a project in Sentry](https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/). ### Option 1: Deploy directly to Vercel diff --git a/examples/with-sentry/next-env.d.ts b/examples/with-sentry/next-env.d.ts new file mode 100644 index 000000000000..4f11a03dc6cc --- /dev/null +++ b/examples/with-sentry/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/with-sentry/next.config.js b/examples/with-sentry/next.config.js index ce5e7e3555d4..be28cd7ffd43 100644 --- a/examples/with-sentry/next.config.js +++ b/examples/with-sentry/next.config.js @@ -7,6 +7,7 @@ const { withSentryConfig } = require('@sentry/nextjs') const moduleExports = { // Your existing module.exports + reactStrictMode: true, sentry: { // Use `hidden-source-map` rather than `source-map` as the Webpack `devtool` diff --git a/examples/with-sentry/package.json b/examples/with-sentry/package.json index b52ef0bcdd01..adc0a060991f 100644 --- a/examples/with-sentry/package.json +++ b/examples/with-sentry/package.json @@ -3,12 +3,18 @@ "scripts": { "dev": "next", "build": "next build", - "start": "next start" + "start": "next start", + "lint": "next lint" }, "dependencies": { - "@sentry/nextjs": "^7.30.0", + "@sentry/nextjs": "^7.46.0", "next": "latest", "react": "^18.2.0", "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "^18.15.11", + "@types/react": "^18.0.32", + "typescript": "^4.8.2" } } diff --git a/examples/with-sentry/pages/404.js b/examples/with-sentry/pages/404.tsx similarity index 100% rename from examples/with-sentry/pages/404.js rename to examples/with-sentry/pages/404.tsx diff --git a/examples/with-sentry/pages/_app.js b/examples/with-sentry/pages/_app.js deleted file mode 100644 index 2ff89fd9b6fa..000000000000 --- a/examples/with-sentry/pages/_app.js +++ /dev/null @@ -1,4 +0,0 @@ -export default function App({ Component, pageProps, err }) { - // Workaround for https://github.com/vercel/next.js/issues/8592 - return -} diff --git a/examples/with-sentry/pages/_app.tsx b/examples/with-sentry/pages/_app.tsx new file mode 100644 index 000000000000..048541e25b1d --- /dev/null +++ b/examples/with-sentry/pages/_app.tsx @@ -0,0 +1,5 @@ +import '../styles/globals.css' + +export default function App({ Component, pageProps }) { + return +} diff --git a/examples/with-sentry/pages/_document.tsx b/examples/with-sentry/pages/_document.tsx new file mode 100644 index 000000000000..54e8bf3e2a29 --- /dev/null +++ b/examples/with-sentry/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/examples/with-sentry/pages/_error.js b/examples/with-sentry/pages/_error.tsx similarity index 100% rename from examples/with-sentry/pages/_error.js rename to examples/with-sentry/pages/_error.tsx diff --git a/examples/with-sentry/pages/api/test1.js b/examples/with-sentry/pages/api/test1.ts similarity index 100% rename from examples/with-sentry/pages/api/test1.js rename to examples/with-sentry/pages/api/test1.ts diff --git a/examples/with-sentry/pages/api/test2.js b/examples/with-sentry/pages/api/test2.ts similarity index 100% rename from examples/with-sentry/pages/api/test2.js rename to examples/with-sentry/pages/api/test2.ts diff --git a/examples/with-sentry/pages/api/test3.js b/examples/with-sentry/pages/api/test3.ts similarity index 100% rename from examples/with-sentry/pages/api/test3.js rename to examples/with-sentry/pages/api/test3.ts diff --git a/examples/with-sentry/pages/api/test4.js b/examples/with-sentry/pages/api/test4.ts similarity index 100% rename from examples/with-sentry/pages/api/test4.js rename to examples/with-sentry/pages/api/test4.ts diff --git a/examples/with-sentry/pages/client/test1.js b/examples/with-sentry/pages/client/test1.tsx similarity index 100% rename from examples/with-sentry/pages/client/test1.js rename to examples/with-sentry/pages/client/test1.tsx diff --git a/examples/with-sentry/pages/client/test2.js b/examples/with-sentry/pages/client/test2.tsx similarity index 100% rename from examples/with-sentry/pages/client/test2.js rename to examples/with-sentry/pages/client/test2.tsx diff --git a/examples/with-sentry/pages/client/test3.js b/examples/with-sentry/pages/client/test3.tsx similarity index 100% rename from examples/with-sentry/pages/client/test3.js rename to examples/with-sentry/pages/client/test3.tsx diff --git a/examples/with-sentry/pages/client/test4.js b/examples/with-sentry/pages/client/test4.tsx similarity index 100% rename from examples/with-sentry/pages/client/test4.js rename to examples/with-sentry/pages/client/test4.tsx diff --git a/examples/with-sentry/pages/client/test5.js b/examples/with-sentry/pages/client/test5.tsx similarity index 100% rename from examples/with-sentry/pages/client/test5.js rename to examples/with-sentry/pages/client/test5.tsx diff --git a/examples/with-sentry/pages/index.js b/examples/with-sentry/pages/index.js deleted file mode 100644 index 8ef6127ca99f..000000000000 --- a/examples/with-sentry/pages/index.js +++ /dev/null @@ -1,163 +0,0 @@ -import Link from 'next/link' - -const Index = () => ( -
-

Sentry Simple Example 🚨

-

- This example demonstrates how to record unhandled exceptions in your code - with Sentry. There are several test pages below that result in various - kinds of unhandled exceptions. -

-

- It also demonstrates the performance monitoring the SDK is able to do: -

    -
  1. - A front-end transaction is recorded for each pageload or navigation. -
  2. -
  3. - A backend transaction is recorded for each API or page route. (Note - that currently only API routes are traced on Vercel.) -
  4. -
  5. - Errors which occur during transactions are linked to those - transactions in Sentry and can be found in the [trace - navigator](https://docs.sentry.io/product/sentry-basics/tracing/trace-view/). -
  6. -
  7. - Manual performance instrumentation is demonstrated in the final - example below (throwing an error from an event handler). -
  8. -
-

-

- Important: exceptions in development mode take a - different path than in production. These tests should be run on a - production build (i.e. 'next build').{' '} - - Read more - -

-
    -
  1. API route exceptions/transactions
  2. - Note that 1 and 2 are not expected to work if deployed to Vercel yet. -
      -
    1. - API has a top-of-module Promise that rejects, but its result is not - awaited. Sentry should record Error('API Test 1').{' '} - - Open in a new tab - -
    2. -
    3. - API has a top-of-module exception. Sentry should record Error('API - Test 2').{' '} - - Open in a new tab - -
    4. -
    5. - API has has an exception in its request handler. Sentry should record - Error('API Test 3').{' '} - - Open in a new tab - -
    6. -
    7. - API uses a try/catch to handle an exception and records it. Sentry - should record Error('API Test 4').{' '} - - Open in a new tab - -
    8. -
    -
  3. SSR exceptions/transactions
  4. - Note that there are currently two known bugs with respect to SSR - transactions: they don't get recorded on Vercel, and ones that are - recorded and have an error are grouped in the Sentry UI by the error page - name rather than the requested page name. -
      -
    1. - getServerSideProps throws an Error. This should cause _error.js to - render and record Error('SSR Test 1') in Sentry.{' '} - - Open in a new tab - {' '} - or Perform client side navigation -
    2. -
    3. - getServerSideProps returns a Promise that rejects. This should cause - _error.js to render and record Error('SSR Test 2') in Sentry.{' '} - - Open in a new tab - -
    4. -
    5. - getServerSideProps calls a Promise that rejects, but does not handle - the rejection or await its result (returning synchronously). Sentry - should record Error('SSR Test 3'), but will not when - deployed to Vercel because the serverless function will already have - exited.{' '} - - Open in a new tab - -
    6. -
    7. - getServerSideProps manually captures an exception from a try/catch. - This should record Error('SSR Test 4') in Sentry.{' '} - - Open in a new tab - -
    8. -
    -
  5. Client exceptions
  6. -
      -
    1. - There is a top-of-module Promise that rejects, but its result is not - awaited. Sentry should record Error('Client Test 1').{' '} - Perform client side navigation or{' '} - - Open in a new tab - -
    2. -
    3. - There is a top-of-module exception. _error.js should render and record - ReferenceError('process is not defined') in Sentry.{' '} - Perform client side navigation or{' '} - - Open in a new tab - -
    4. -
    5. - There is an exception during React lifecycle that is caught by - Next.js's React Error Boundary. In this case, when the component - mounts. This should cause _error.js to render and record Error('Client - Test 3') in Sentry.{' '} - Perform client side navigation or{' '} - - Open in a new tab - -
    6. -
    7. - There is an unhandled Promise rejection during React lifecycle. In - this case, when the component mounts. Sentry should record - Error('Client Test 4').{' '} - Perform client side navigation or{' '} - - Open in a new tab - -
    8. -
    9. - An Error is thrown from an event handler. Sentry should record - Error('Client Test 5'). (This page also demonstrates how to manually - instrument your code for performance monitoring.){' '} - Perform client side navigation or{' '} - - Open in a new tab - -
    10. -
    -
-
-) - -export default Index diff --git a/examples/with-sentry/pages/index.tsx b/examples/with-sentry/pages/index.tsx new file mode 100644 index 000000000000..4a001d108886 --- /dev/null +++ b/examples/with-sentry/pages/index.tsx @@ -0,0 +1,285 @@ +import Link from 'next/link' + +import Image from 'next/image' +import { Inter } from 'next/font/google' +import styles from '../styles/Home.module.css' + +const inter = Inter({ subsets: ['latin'] }) + +export default function Home() { + return ( +
+
+

+ New users instrument with:  + npx @sentry/wizard -s -i nextjs +

+ +
+ +
+ Next.js Logo +
+ 13 +
+
+ Sentry Logo +
+

+ See how Sentry records unhandled exceptions in your code. +

+
+

+ Important: exceptions in development mode take a + different path than in production. These tests should be run on a + production build (i.e.{' '} + next build + ).{' '} + + Read more + +

+
+
+
+
+
+

+ API routes -> +

+

+ The following examples are Sentry tests. Note that 1 and 2 are not + expected to work if deployed to Vercel yet. +

+

+ Top-of-module promise that rejects, but its result is not awaited. + {' '} + + + API Test 1 + + +

+

+ API has a top-of-module exception.{' '} + + + API Test 2 + + +

+

+ API with an exception in its request handler.{' '} + + + API Test 3 + + +

+

+ API uses a try/catch to handle an exception and records it.{' '} + + + API Test 4 + + +

+
+
+

+ SSR -> +

+

+ There are currently two known bugs with respect to SSR transactions: + they don't get recorded on Vercel, and ones that are recorded and + have an error are grouped in the Sentry UI by the error page name + rather than the requested page name. +

+ +

+ getServerSideProps +  throws an Error. This should cause _error.js to render and + record and Error in Sentry.{' '} + + + SSR Test 1 + + +

+

+ getServerSideProps + returns a Promise that rejects. This should cause _error.js to + render and record an Error in Sentry.{' '} + + + SSR Test 2 + + +

+

+ getServerSideProps + calls a Promise that rejects, but does not handle the rejection or + await its result (returning synchronously). Sentry records an Error + but will not when deployed to Vercel because the + serverless function will already have exited.{' '} + + + SSR Test 3 + + +

+

+ getServerSideProps + manually captures an exception from a try/catch. This should record + Error in Sentry.{' '} + + + SSR Test 4 + + +

+
+ +
+

+ Client exceptions -> +

+

+ There is a top-of-module Promise that rejects, but its result is not + awaited. Sentry records an Error.{' '} + + + Client Test 1 + + +

+

+ There is a top-of-module exception. _error.js should render and + record ReferenceError('process is not defined') in Sentry.{' '} + + + Client Test 2 + + +

+

+ There is an exception during React lifecycle that is caught by + Next.js's React Error Boundary. In this case, when the component + mounts. This causes _error.js to render and records Error in Sentry.{' '} + + + Client Test 3 + + +

+

+ There is an unhandled Promise rejection during React lifecycle. In + this case, when the component mounts. Sentry records an Error.{' '} + + + {' '} + Client Test 4 + + +

+

+ An Error is thrown from an event handler. Sentry records an Error. + (This page also demonstrates how to manually instrument your code + for performance monitoring.){' '} + + + Client Test 5 + + +

+
+
+

+ NextJS 13 Features -> +

+

+ NextJS 13 continues to bring many new features to developers, + especially those depoying on Vercel. We are trying to keep up, we + promise! +

+
+
+

+ Performance -> +

+

+ Why should I care about Performance? +

+

+ Front-end transactions are recorded for each pageload or navigation. +

+

+ Backend transactions are recorded for each API or page route. +

+

+ Sentry creates links between errors and transactions, and can be + seen in the{' '} + + + trace navigator docs + + + . +

+

+ Manual performance instrumentation is demonstrated in the final + example below (throwing an error from an event handler). +

+

+ Add Edge Function example +
+ + + Trigger Edge Function + + +

+
+
+
+ ) +} diff --git a/examples/with-sentry/pages/ssr/test1.js b/examples/with-sentry/pages/ssr/test1.tsx similarity index 100% rename from examples/with-sentry/pages/ssr/test1.js rename to examples/with-sentry/pages/ssr/test1.tsx diff --git a/examples/with-sentry/pages/ssr/test2.js b/examples/with-sentry/pages/ssr/test2.tsx similarity index 100% rename from examples/with-sentry/pages/ssr/test2.js rename to examples/with-sentry/pages/ssr/test2.tsx diff --git a/examples/with-sentry/pages/ssr/test3.js b/examples/with-sentry/pages/ssr/test3.tsx similarity index 100% rename from examples/with-sentry/pages/ssr/test3.js rename to examples/with-sentry/pages/ssr/test3.tsx diff --git a/examples/with-sentry/pages/ssr/test4.js b/examples/with-sentry/pages/ssr/test4.tsx similarity index 100% rename from examples/with-sentry/pages/ssr/test4.js rename to examples/with-sentry/pages/ssr/test4.tsx diff --git a/examples/with-sentry/public/favicon.ico b/examples/with-sentry/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4570eb8d9269ad58b17fecbec6d630cded56f507 GIT binary patch literal 39535 zcmeHw`Bz=nmF9gsc+9hy5jB1w151fdDZGiScP-lmIaZ#3;EsX?LYkO6+oC zcdDGkvDZqaDp3tdY$sinwH#S?96PaNCzdTaPIp(T)A^x$b@xBe-*@+Ug9%7NAcSPy z_3eH3nfKXypK~woT|m<dO#S`!I@|I?JhGKxc0jfnSEBfEGWE_{%f^i zf7dG>nYG%kx13|yul4r5llB}t3ACbZQ&SU4HVXp-11rXN%*eTqIdYCx@$vDSH95v9 zDk^e}ykf3Z=5}o{GBUD3JzPgyuk7vZeHXJwMn~h=naE?`? z%e9}uV7SFRVgc9otIC{%m+SggmGkoQP+nPCxoYjIBj^Kz-K$;A@i+Sfvg>SyaOW$P zmh5x8Ya=K3?Z$rS>UozmgRCIlLG=sw*$rMM^v?vfgVypf>)3{EH~Y%KfNRaW-g1t$ z(JK}Mtask^w&xsoa~(#f_0F}C_MC4$*901GWWJ5`<=l62-A^4|*LOR8o3I$y_S-?G zPODupDBKSEt)&C?TY0V3cDt3k>djlgcGGVyebQVMU#rcEa*M@+k_QESeSItRzpZy@ z63^SVa*P@m7x$nT1A4Z$wmLMw!!^4hWyu7@&p(Qdi zGabHK*NWD?e%H}!YHGHLEnKzQdij1CAtEB;?!3N{>3y#>o39v+0$@%a>f+y6h{D_58WU^|}k|qx-783JMBPeizT` ztB$r}7FV4qN3W>AlQFik&1ydfQ||}qvj*zR>p}S(N3^}a`rhudCcBRIbi04fXxDQ) ze^zVPk?D5(jN4T=%J=&_fR*|@%0IHeBMUqP7T|*&UIEfco*m%Ufvj+(>8#H zL2Erv~w%d?)K#%_g^bHyK^-WaJ08 zjs3l8SKB7__OI5 z7JVNG>ISNNsKHrdZG6 z1^cItn>cD!Rn=$2GY%_mmi73VEJY-eK4E`YJs={ULM3 zdM$8=?QiuB$X#3Cb?yh<4e~yr>xv#MdB=_&50DFnhK4pJ>b8TAw*NEjCO&iCcdu>F z=hRJqj=Z&<@A+vi`)d0r6+%Y8HuU)|Mz{<6Z~W=&zC0bd zezLluZv*bmYIpY(wZ%_4uF>H7EdfK#wQkGgJAWR!UmDUhE3W(Yoh00{Dz^B^!!tdk z=Q_NW;u|dgEd5S|tEp1&qrdrkHd}r1_lfS+&;I;9sQdBzN~_t>{&ojH&AioT@b*uU z?Vr)DHrAuEM;3Twfkzg2WPwK(cw~V`7IbQ3qm;e&g$Ex#QUOsgn9SNn$>bGeKsYCv zQ%Fo#zX)C@1^m;U{PzJ3{4^NY1=_Le{b!iH`Yc#vR1F6M&h=S;|Bp|I3?36Q>-EOh zAiQ%~%I3!$b*@4?GV_aI>VI%N=2|IoF_KrmOx*;#D)}EaLP5b6PHqIbH_|MI9x%Y_ z3l=bFoJA5`eqQrO%JuTEY?azL#U4;LC>6xU(;$v$HiH3%Jtpv-e*7I0;2ko+3i|0c zTEI2d47qn2X}aD@WzA@0X2!udqaTRQI0_nk=b2i+0V@Xud`hB32wk9si)`lcL@Sq( z2Kw1Q@n!ePc@Dv$=pSW%HW8Rjr?g*Pab(@X7Ix)^AnTJ$I{;<%VBtPs94 z!BeX{NUhlBz(RR)eO0`tpJyYLT>Rxz3rHNGg3GK3A2cU>=bJeKyDsE_y?R;8)*6j- z94CpTf(|77l^@Q^e?kIq5s@5=LjgB`C@DM94;PfWVP<6L)=76T&p6XyP%r*c8N*Ol zxUzwv_ZVH|Zc+a(CEmAom|ae~$JQ=*%?3f_KfC~15^3n;k^z735|~yad8;JFwj5hZ zE|=wB3uYbWJWH8ibO}(;Wn_9zrDt<6>aR8hq)PtRkv5Q96E~+JeF>cVIp7FKoc2gQ zVGcL_;X7aB`rA{$_JUSGppWB*>V*^ z=O>U6lptY%(BoFKy*^_;B?$$LNRHkKa{K+0mwIVOz)%V{pmj@*o?{^>7(^c=m^dzw zX^R2hqu$`$6H(~SZB#-to-}1wfB1E-*n#PC4zQ0_0n$M1$W=Me7kl&a`SB8Z++y6x zMFEBYADZ{IOJM%v^PEC3VyJ!Nd5Fq;YG4$c1ikxT{P!W*lb;Gse*yW^2So6Y1pMEh z{;tSvTnfi#Th`M*TrI$GRIcY)6PmMhRw@iUC;;wb>pa*D3pmoLo(QFuHg7yb`vX-c z3{H#e;xW-OeF&W4Vv19`LFP(0JtqTt(k1O{;IyTZDYc%6bPlE_sA(TTZCEcDPfz7= z=^#8kPg44m-3LIN{?$JR1J2rbrgks0Zdr=Jl_%8xkmCRL4`1O?S@O|y;Js9NMk>^- zoS_O-$=w5JAa4-2KR2s#S2brcadWXBjek_%qFg(dODB%*=OzMtts$Y#MRSBHMLf>| zT8-Z72bF=;A6t#z3AxS!re+NZgKXhO;;aYqZhR+sztWq!odyBi%DY7zHl7iosxw4) zB4as~feN&8Kagk;1GNU&0O+pCixw%uCMi#&ggCHKeN2vfjn?l49ae3uO(&6S1p(I6 zRL$`AOEjs9!*eHC#G3%XFpab~p7v9vD69rm8m-`g?>q&*0{PY?DN$B&Zz#}EP$7l! z$+bK?v_8*4PQZ=oR$c3xG;wZMC=WD%cjija|9#=_pO6wB=o8bUHBBAIA?Zp+YN-$n z#@2z#s33Z;093_p#;GyzjMrZPcc7Rd3@kC~xndwQ8BB|(lMO9k4C3>FVD)A&3XIxH zzkmDUrFNcTGwhez!0?N82m9mS{utc1l|zPv*>#9GW{H}0w`j@PLv)b`dji}`?yp@u zrs7r#xtbnOh-6t;DB|lEy-(7m0?uYFpuhS-oC$DeFE-b%JufAU)0~W8BMqe6gq0xG zAvvLrhX|wI7sNZn{*0_Vo40X{3l3#d?@%z>1$h0Nl_EHJgGjUkqgaj0eg_k!ip5Q8 zV~6U731%yaiGW$Eo0xo>Jp{%&q!y8zh9tFckZ2CbcG2p@vvke!=$P8K13a5?1MFoG zgq=-|y8f5+peLgpm^ufhU4I%}ou!bDG03w_`K$t+0=We$O)_|Eq#T(QK zGZ=lMG*DraF@PdKb^V|J@e(rC`pUSN9Z}c+3`vf5BG2s{1Gf?6z%fYnvuoTL3k);? zw^{7s40YfH@4q0$;8A&I)36N!4I0Z$0x-m8>tjnLS1SoSR)}cG3-V|CBsTP^ENQn4 zGb@;KNP!`EE;vC0R4gp;6x-PjMnAfNjQM*~kO1j%Py#7E)W>l9}r7Mh~Dr5{IjCN7>)Ztu6?$ zo`^EGLT;P}%cOOOL2v#O7*GOI6G7q;&KPjO3dt71P>Nq(0MF`*=A>y&N5Fw!e;#bI zdiJC5N&d!*NJkgn{u;NAE4ZbAYd-=jiBfR_IZ*9{gA6aAy?&UnelB-b^58okJ6{;3 z#l7`hX%}*X9i`yjQp+S2lyJI;-m?t8`#jGcUP+}BK?2O2Y|Mkk3QE>AT9d|Sn?Cgyng72nnc7Xu)r7D8p zq-qFi2STE`1;-heCtW8zORB;1wI1Fqs+;!byN5>}D2Lov z=ct9i*ec-`^(X-=4M+lSn&Ul*i#CIRUvj_{-uJ?#0DQSzKQBzdkn6bf1y2P1Nhs4adTxldIiuS`L^^E?@+PHYG02abvk#_yY7 z@1e7RMHQbT(H0&wD{%Ec(RlBEtLF8uB5eYl{pkDW!ZVLT8dLg1Je|$Cd(B{nXJf$y zx&}0lK(}&XZyzJQ5i}2qh>|+t41#>xVbZk}GRv~AzcM+1G)h%+TMU8J5=!3GtW#wJSC3|~*ed(NE1B=t`XHkM4Zh0sJc!Ss@A~+u;{6T7)Nx6kb(2U4$ zJzMbl%M09$L1p@}qfEnPMkx*7{yZ|YEszPS+9|T|D79=O4J6L)k&fA9o1?FZj{|4T@m5sD1J^FtYasE9Y&t`q%csaoPAMH?)=wt)MBMdaeli`0=Bh zE280pr_{quW1-sc=WafimfWAqe)0a+BS=Ooz-~Ts47{PZ1biWR!tJ=X?K zvjA<~wET2u)~9BUfA!nlRLz@%2gh(Ifa_OhQ5eK|+L_7$Ehi++V_a~$y3AuK^q{iL zQkbtaaNR>vasUZOiVtMa2XZR`AsIivC=z)8J}~Z14`WE!a_*d?f}^_+f>k?;AaH(w zIsyxvIaFjzC-ps`&MGNu8$ik~UbQX#QZ#UeCXA2_IBf{nZ8R>~wB5!ak%XT`(s%ty z4hJYF?T{Q789L8dloOO)BH#E9%6@tI7ta>3AGND#S@__Uy|$WY$iV-3@te;e$py7b zN#BP*=+wWLxPAo%mGo1()PQ$0mK}KWt0K}Aw4ZYFTzoV>0C%*qiliT=5S2@trF4Em zt)BfO*p$w2U?IB)^7}6bGOB^I3u^t6?UUpJk`7RPB{vOYrmQo5ts9WsHYP1;3gEq;NV*@1HT$S!z^6Gg!%#Kf6YDN@vq*{$ zHPc|WuuonZQ}#!;S1+4_k+y*XKylVVj!>0L9T}DY z@4ZtW{ouW4MuM)r+$d$jk0BXWf$P(n#Go@Q^#1pM`B9eHs6vM8@MouNkihuc7j2Mw z=fUP0z|^g_kRH1FH892ya2nK;nh1UhWSacNUl+@LqMe#0_=5o%qs`<|98)m%n{V@} z!+{|S13+y#DIJXB!NL#;J}V`z{AW)gM@yB0<`07DGya;E63lcuM0FBHISO0_%ZVya zlk-T^KX#~;1_@n^ow7yA#q2E^`QUs09A$J&ig;dWWy9xN%9E5qjv7sVC{(`Xu~{Bv~b87b!tR#am66OpK<=imy7$&zkME3mi{Fk1kMDgML-H>d|r^! z(ei2DpckW$zWw*K;baBakXEKzbHkUq1*m&R#4>GT-pvTpl|h=VKY05FL97RHv$*2G zwdZgAS$-~~$_)DX-=!24AT6?j9pYRmAb@-1t#648u9nA9hv_&9T;^jSKhX4Z}6AY}={Z_)HjztGN)|2Be}XwAG?N4Q zbmdVH_qADyTVsQ6{&+@YR`XEg0l<{U1<#>=(477p^!DG;NS>fsI%c>$Fe-ef3Lb7l zYDL$x)$k-&ELh+{JsbJXOGx&eIy%MR2kc$wFa0~G!oBPn19CnbsV#GXTJg?E%d2yZ z-pM-JrVdDCfO1d_N1^tA_{v{{yTW8f&c=yLIgl@#2QvDAUXTw+=^ZYm;QHfI-~vs@ zdU|=k2ge7>RcDB_8E017IX!_gfE@<$Gpuu5DX(flN}B=|A72FDFBz6C(KqKn@BQnd zl!EUAz4mn1JFggvATbO8#+v>CZ;@6W3%Q=Xmd+G{1Nu)KNS$^?#?2$APAztq3Q2=B zzy1YD!Rb#xoi>md;2f3b$AJ%@$p7H088gLBU)R)s%s{2Pcmy%1PJ;jz0W|p@kgtUX zi_%OBUW|vRxak>F)4zTH64#SjA3;s^pN)`~(?o(%>)uRAbiEg&yHtw7U1kcDLaKfU zw8#G4-=V!y;oxqb)B$K=mmUyx{gWkghdE%*DgUp}JvHWACMEyx>3@9z(u8qNKPn;a zV2lAaNx+Zr7#B=SQdn3>?HPl>#BoW-kuZXQ!!4Y2A3L6^kInY1j#C~;TH>A*OmgQq z5Z0KIfsFp{kre*)Z#bQ$hK>NDNnB`7M@hzP3Iq*!_H3k)64yF|%9bXA|AgA<6gvbczdA#f&0{-d~ zhCC$tC*!RHi?j&`jPdqVJE55?bHI>ii*<%NijDDHGqsJQ3IOK-DnYp?tc+7Y)ky|i z`I3DFoULmLjP8N%)znHbZX1En-43SjZ=`BFR3vp+@NHbkX`IWviHO}6*bGq3F5S*UbZRKy;bbjOk=k!@!$vamBsjsb6dc_O-YtxJ#u)EMz_qWp*CQ#_zt``TGLJSc zYE*Y-J|r5hD7}0#7^$+}yjzoz`r5x#t&BoOFs5~lu>V*+S3WC+^hFyFnLcYz1iqx4 zpu%)|4G7>aQA0oWDZe=;pm7EC(!lW+uFCG4G@EBGwR`m&W{w13micu<0CFS&kL{;( zVjOu_;kAp-@ll@8PrL8z1Y0=a+KvH{&5+pvl5mgw-3woLG?`#b6~}|^mNK<-9StCO zgTw}-RS*L!!$y#ls%2XsyCQ+#>9){Z2e^Zpjf|jJcGMSt^DiLLgK5SkR#>@4C}vO& zS>(P5HG+?U0Ieh7;MI$fUb-kn!qpa218Pc9 z_B{K$D;K3r?9J~(=<{Pd14iCS3vE?5KgV8Fn+K2^DDf?BLTsp5&X6Ep;( zT1aw1+)CwINb6E61R#=&phg~&XFV`)9*slN$K?rVaDWlT6A5UG=Ag=o*ZvYRZ~r*H z)B>9hFD0I{F9J19TC6XZS&pL%iQmmHuwjxFtPgaaGlU*|>scf~2zPVH4z8UBO``{# z8yuMq6(06G$ygd)$7nwT;yRqKq96lUhrya?N5GI9C<1uCO!q+{|9i^?FKnu z9QSS7j~vZ4-2}!hvP*vxtQFGxPlE}H8EFm#aJrK`?@T4L)c>d+OZGPqY2<5v1$K%9 z1KblAtks%&iAxHF?>@v0K18DfXFM!TE8E4OJRek;8m-Z`{>{`B_|5Z@zIsv8y)@Oa z$X5NagptV;mAn5KC?wAoW8@<)z%75UwO|y9!8|~VGt>ZUhc(f3=D4kZ^Np6Xw03{~ zI3s#FdT__pOCDz-o}CUdBXNL-z_7L2szx`d6F}-a6C`z;bq2B<-4Y&j0sP&6OlLHj zc-obL=%UVqJyBW+1QngUEZ1X*g?tXw{b2|fhJW%J1`EErX+=1vEyffiaQY z-7r;dPKnU})OT z4$h-SV3;csA(GK2put)|zxe?eHwT+9*l3XsV?put#~}dw7)gm)`q#(25dfjx9{adB z&r{r3z%*#Arom%Q3t}rDcN+yS!~r6DZGFjpma;C!i7o&mVxh<-Cad>{aY0$P7Nj;|jERK;#cP!fgN{lF`_wUK7z|?2T8&3IpNg`B5NQSO#(o}U z&fjW4rdBBn5s2;p(?2z8gfP`L%}&XnRiS@!rGK$vHs)(IS5GQ8MX(03fzrjs*?#4 z54tSti6CfYYKLrpfv?10^RG}_!GBSU_RStbt@{E;oIL<0n4s5Y+~N)PCw{I(f7}_; zEP;I|EEpFh9Xxwo5v_lhjuLG?FwIRF)}1D&SLW#MYcE^?Uj|KrI0|5u#BG^w@>HW5 l#`JGjT2VCQKBy&!+avXpR|1Bm1Aq0ZuBx#Tx&1b<{|~G@=I{Uj literal 0 HcmV?d00001 diff --git a/examples/with-sentry/public/next.svg b/examples/with-sentry/public/next.svg new file mode 100644 index 000000000000..5174b28c565c --- /dev/null +++ b/examples/with-sentry/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/with-sentry/public/sentry.svg b/examples/with-sentry/public/sentry.svg new file mode 100644 index 000000000000..4b0850ab3c07 --- /dev/null +++ b/examples/with-sentry/public/sentry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/with-sentry/public/thirteen.svg b/examples/with-sentry/public/thirteen.svg new file mode 100644 index 000000000000..8977c1bd123c --- /dev/null +++ b/examples/with-sentry/public/thirteen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/with-sentry/public/vercel.svg b/examples/with-sentry/public/vercel.svg new file mode 100644 index 000000000000..d2f84222734f --- /dev/null +++ b/examples/with-sentry/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/with-sentry/sentry.client.config.js b/examples/with-sentry/sentry.client.config.js index 2b45cac0071c..263017bf30c5 100644 --- a/examples/with-sentry/sentry.client.config.js +++ b/examples/with-sentry/sentry.client.config.js @@ -8,8 +8,24 @@ const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN Sentry.init({ dsn: SENTRY_DSN, + // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1.0, + // ... // Note: if you want to override the automatic release value, do not set a // `release` value here - use the environment variable `SENTRY_RELEASE`, so // that it will also get attached to your source maps + // in development and sample at a lower rate in production + replaysSessionSampleRate: 0.1, + + // If the entire session is not sampled, use the below sample rate to sample + // sessions when an error occurs. + replaysOnErrorSampleRate: 1.0, + + integrations: [ + new Sentry.Replay({ + // Additional SDK configuration goes in here, for example: + maskAllText: true, + blockAllMedia: true, + }), + ], }) diff --git a/examples/with-sentry/sentry.edge.config.js b/examples/with-sentry/sentry.edge.config.js index 2b45cac0071c..987ac85d72a7 100644 --- a/examples/with-sentry/sentry.edge.config.js +++ b/examples/with-sentry/sentry.edge.config.js @@ -1,5 +1,5 @@ -// This file configures the initialization of Sentry on the browser. -// The config you add here will be used whenever a page is visited. +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever middleware or an Edge route handles a request. // https://docs.sentry.io/platforms/javascript/guides/nextjs/ import * as Sentry from '@sentry/nextjs' @@ -8,7 +8,9 @@ const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN Sentry.init({ dsn: SENTRY_DSN, + // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1.0, + // ... // Note: if you want to override the automatic release value, do not set a // `release` value here - use the environment variable `SENTRY_RELEASE`, so // that it will also get attached to your source maps diff --git a/examples/with-sentry/sentry.server.config.js b/examples/with-sentry/sentry.server.config.js index 75d73dcf5b8e..4b244f5b52eb 100644 --- a/examples/with-sentry/sentry.server.config.js +++ b/examples/with-sentry/sentry.server.config.js @@ -8,7 +8,9 @@ const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN Sentry.init({ dsn: SENTRY_DSN, + // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1.0, + // ... // Note: if you want to override the automatic release value, do not set a // `release` value here - use the environment variable `SENTRY_RELEASE`, so // that it will also get attached to your source maps diff --git a/examples/with-sentry/styles/Home.module.css b/examples/with-sentry/styles/Home.module.css new file mode 100644 index 000000000000..90a41dcd5ee3 --- /dev/null +++ b/examples/with-sentry/styles/Home.module.css @@ -0,0 +1,301 @@ +.main { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding: 6rem; + min-height: 100vh; +} + +.description { + display: inherit; + justify-content: inherit; + align-items: inherit; + font-size: 0.85rem; + max-width: var(--max-width); + width: 100%; + z-index: 2; + font-family: var(--font-mono); +} + +.description a { + display: flex; + justify-content: center; + align-items: center; + gap: 0.5rem; +} + +.description p { + position: relative; + margin: 0; + padding: 1rem; + background-color: rgba(var(--callout-rgb), 0.5); + border: 1px solid rgba(var(--callout-border-rgb), 0.3); + border-radius: var(--border-radius); +} + +.code { + font-weight: 700; + font-family: var(--font-mono); +} + +.link { + color: #8d5494; + text-decoration: none; + transition: color 200ms; +} + +.grid { + display: grid; + grid-template-columns: repeat(3, minmax(25%, auto)); + width: var(--max-width); + max-width: 100%; +} + +.card { + padding: 1rem 1.2rem; + border-radius: var(--border-radius); + background: rgba(var(--card-rgb), 0); + border: 1px solid rgba(var(--card-border-rgb), 0); + transition: background 200ms, border 200ms; +} + +.card span { + display: inline-block; + transition: transform 200ms; +} + +.card h2 { + font-weight: 600; + margin-bottom: 0.7rem; +} + +.card p { + margin: 0; + opacity: 1; + color: rgba(var(--text-rgb), 0.8); + font-size: 0.9rem; + line-height: 1.5; + margin-top: 16px; +} + +.justCenter { + display: flex; + justify-content: center; + align-items: center; + position: relative; + padding: 4rem 0; + position: static; + z-index: 10; +} + +.center { + display: flex; + justify-content: center; + align-items: center; + position: relative; + padding: 4rem 0; +} + +.center::before { + background: var(--secondary-glow); + border-radius: 50%; + width: 480px; + height: 360px; + margin-left: -400px; + z-index: -1; +} + +.center::after { + background: var(--primary-glow); + width: 240px; + height: 180px; + z-index: -1; +} + +.center::before, +.center::after { + content: ''; + left: 50%; + position: absolute; + filter: blur(45px); + transform: translateZ(0); + pointer-events: none; +} + +.logo, +.thirteen { + position: relative; +} + +.thirteen { + display: flex; + justify-content: center; + align-items: center; + width: 75px; + height: 75px; + padding: 25px 10px; + margin-left: 16px; + transform: translateZ(0); + border-radius: var(--border-radius); + overflow: hidden; + box-shadow: 0px 2px 8px -1px #0000001a; +} + +.thirteen::before, +.thirteen::after { + content: ''; + position: absolute; + z-index: -1; +} + +/* Conic Gradient Animation */ +.thirteen::before { + animation: 6s rotate linear infinite; + width: 200%; + height: 200%; + background: var(--tile-border); +} + +/* Inner Square */ +.thirteen::after { + inset: 0; + padding: 1px; + border-radius: var(--border-radius); + background: linear-gradient( + to bottom right, + rgba(var(--tile-start-rgb), 1), + rgba(var(--tile-end-rgb), 1) + ); + background-clip: content-box; +} + +/* Enable hover only on non-touch devices */ +@media (hover: hover) and (pointer: fine) { + .card:hover { + background: rgba(var(--card-rgb), 0.1); + border: 1px solid rgba(var(--card-border-rgb), 0.15); + } + + .card:hover span { + transform: translateX(4px); + } +} + +@media (prefers-reduced-motion) { + .thirteen::before { + animation: none; + } + + .card:hover span { + transform: none; + } +} + +/* Mobile */ +@media (max-width: 700px) { + .content { + padding: 4rem; + } + + .grid { + grid-template-columns: 1fr; + margin-bottom: 120px; + max-width: 320px; + text-align: center; + } + + .card { + padding: 1rem 2.5rem; + } + + .card h2 { + margin-bottom: 0.5rem; + } + + .center { + padding: 8rem 0 6rem; + } + + .center::before { + transform: none; + height: 300px; + } + + .description { + font-size: 0.8rem; + } + + .description a { + padding: 1rem; + } + + .description p, + .description div { + display: flex; + justify-content: center; + position: fixed; + width: 100%; + } + + .description p { + align-items: center; + inset: 0 0 auto; + padding: 2rem 1rem 1.4rem; + border-radius: 0; + border: none; + border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); + background: linear-gradient( + to bottom, + rgba(var(--background-start-rgb), 1), + rgba(var(--callout-rgb), 0.5) + ); + background-clip: padding-box; + backdrop-filter: blur(24px); + } + + .description div { + align-items: flex-end; + pointer-events: none; + inset: auto 0 0; + padding: 2rem; + height: 200px; + background: linear-gradient( + to bottom, + transparent 0%, + rgb(var(--background-end-rgb)) 40% + ); + z-index: 1; + } +} + +/* Tablet and Smaller Desktop */ +@media (min-width: 701px) and (max-width: 1120px) { + .grid { + grid-template-columns: repeat(2, 50%); + } +} + +@media (prefers-color-scheme: dark) { + .vercelLogo { + filter: invert(1); + } + + .link { + color: #ad6caa; + } + + .logo, + .thirteen img { + filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); + } +} + +@keyframes rotate { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } +} diff --git a/examples/with-sentry/styles/globals.css b/examples/with-sentry/styles/globals.css new file mode 100644 index 000000000000..d4f491e152dd --- /dev/null +++ b/examples/with-sentry/styles/globals.css @@ -0,0 +1,107 @@ +:root { + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', + 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', + 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; + + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; + + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient( + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0) + ); + + --tile-start-rgb: 239, 245, 249; + --tile-end-rgb: 228, 232, 233; + --tile-border: conic-gradient( + #00000080, + #00000040, + #00000030, + #00000020, + #00000010, + #00000010, + #00000080 + ); + + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: 180, 185, 188; + --card-border-rgb: 131, 134, 135; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + + --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); + + --tile-start-rgb: 2, 13, 46; + --tile-end-rgb: 2, 5, 19; + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); + + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); +} + +a { + color: inherit; + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } +} diff --git a/examples/with-sentry/tsconfig.json b/examples/with-sentry/tsconfig.json new file mode 100644 index 000000000000..b8d597880a1a --- /dev/null +++ b/examples/with-sentry/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} From 862f556c69b05b4c5d5701028818ed5707da3b30 Mon Sep 17 00:00:00 2001 From: Brandon Owens Date: Wed, 14 Jun 2023 02:20:28 -0400 Subject: [PATCH 25/54] Update Player.tsx to improve code readability and styling. (#50275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated the demo player component to have more consistent styling and format. I thought this could be some nice spring cleaning for the community 🧹 --------- Co-authored-by: JJ Kasper --- examples/with-videojs/components/Player.tsx | 38 +++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/examples/with-videojs/components/Player.tsx b/examples/with-videojs/components/Player.tsx index c3b1747ae8b0..d345584716f9 100644 --- a/examples/with-videojs/components/Player.tsx +++ b/examples/with-videojs/components/Player.tsx @@ -3,21 +3,51 @@ import videojs from 'video.js' import 'videojs-youtube' interface PlayerProps { + /** + * + */ techOrder: string[] + /** + * Is autoplay enabled for this video? + */ autoplay: boolean + /** + * Should this video have controls? + */ controls: boolean - sources: { src: string; type: string }[] + /** + * A list of video sources. + */ + sources: { + /** + * The source url. + */ + src: string + /** + * The type of source + */ + type: string + }[] } -export default function Player(props: PlayerProps) { +/** + * A simple video player component for displaying videos from external websites. + * @returns A Video.js video player element. + */ +const Player = (props: PlayerProps) => { const [videoEl, setVideoEl] = useState(null) const onVideo = useCallback((el: HTMLVideoElement) => { setVideoEl(el) }, []) useEffect(() => { - if (videoEl == null) return + if (videoEl == null) { + return + } + + // our video.js player const player = videojs(videoEl, props) + return () => { player.dispose() } @@ -32,3 +62,5 @@ export default function Player(props: PlayerProps) { ) } + +export default Player From f676d001b66d1e2d2ef9098ab4f2998e5f10294d Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 13 Jun 2023 23:23:50 -0700 Subject: [PATCH 26/54] Correct build workers perf issue (#51271) This continues off of https://github.com/vercel/next.js/pull/49937 and https://github.com/vercel/next.js/pull/50194 ensuring we don't regress on build perf due to reducing the amount of workers being used in parallel. Fixes: https://github.com/vercel/next.js/issues/51201 Fixes: https://github.com/vercel/next.js/issues/50555 --- packages/next/src/build/index.ts | 6 +----- test/integration/with-router/test/index.test.js | 17 ++++------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index de2732227d38..582d92846db0 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -1141,10 +1141,6 @@ export default async function build( : config.experimental.cpus || 4 function createStaticWorker(type: 'app' | 'pages') { - const numWorkersPerType = isAppDirEnabled - ? Math.max(1, ~~(numWorkers / 2)) - : numWorkers - let infoPrinted = false return new Worker(staticWorkerPath, { @@ -1178,7 +1174,7 @@ export default async function build( infoPrinted = true } }, - numWorkers: numWorkersPerType, + numWorkers, forkOptions: { env: { ...process.env, diff --git a/test/integration/with-router/test/index.test.js b/test/integration/with-router/test/index.test.js index 07ef18afaf3c..506af01dc268 100644 --- a/test/integration/with-router/test/index.test.js +++ b/test/integration/with-router/test/index.test.js @@ -7,9 +7,7 @@ import { killApp, launchApp, nextBuild, - nextServer, - startApp, - stopApp, + nextStart, } from 'next-test-utils' import webdriver from 'next-webdriver' import { join } from 'path' @@ -17,22 +15,15 @@ import { join } from 'path' describe('withRouter', () => { const appDir = join(__dirname, '../') let appPort - let server let app beforeAll(async () => { await nextBuild(appDir) - app = nextServer({ - dir: join(__dirname, '../'), - dev: false, - quiet: true, - }) - - server = await startApp(app) - appPort = server.address().port + appPort = await findPort() + app = await nextStart(appDir, appPort) }) - afterAll(() => stopApp(server)) + afterAll(() => killApp(app)) it('allows observation of navigation events using withRouter', async () => { const browser = await webdriver(appPort, '/a') From 1e796726ee0c40c69213fddf9d8d1ab77a4394b7 Mon Sep 17 00:00:00 2001 From: John Albin Wilkins Date: Wed, 14 Jun 2023 14:31:49 +0800 Subject: [PATCH 27/54] Document CSS concatenation order matches the import order (#39889) Given the amount of CSS ordering problems mentioned in the issue queue (see #16630), it's clear, for most people, that it is NOT obvious that the order of CSS concatenation matches the order of importing CSS and modules inside `_app.js`. We should try to make this fact explicit in the docs. Right now, it's only implied. ## Documentation checklist - [x] Make sure the linting passes by running `pnpm lint` - [x] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) --------- Co-authored-by: JJ Kasper --- .../04-styling/01-css-modules.mdx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/02-app/01-building-your-application/04-styling/01-css-modules.mdx b/docs/02-app/01-building-your-application/04-styling/01-css-modules.mdx index 31688cef9987..41063ff0889a 100644 --- a/docs/02-app/01-building-your-application/04-styling/01-css-modules.mdx +++ b/docs/02-app/01-building-your-application/04-styling/01-css-modules.mdx @@ -183,7 +183,22 @@ Due to the global nature of stylesheets, and to avoid conflicts, you may **only In development, expressing stylesheets this way allows your styles to be hot reloaded as you edit them—meaning you can keep application state. -In production, all CSS files will be automatically concatenated into a single minified `.css` file. +In production, all CSS files will be automatically concatenated into a single minified `.css` file. The order that the CSS is concatenated will match the order the CSS is imported into the `_app.js` file. Pay special attention to imported JS modules that include their own CSS; the JS module's CSS will be concatenated following the same ordering rules as imported CSS files. For example: + +```jsx +import '../styles.css' +// The CSS in ErrorBoundary depends on the global CSS in styles.css, +// so we import it after styles.css. +import ErrorBoundary from '../components/ErrorBoundary' + +export default function MyApp({ Component, pageProps }) { + return ( + + + + ) +} +``` From aa8400c2ca95667cff306fddea09be93b23ecf00 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 13 Jun 2023 23:37:19 -0700 Subject: [PATCH 28/54] Update flakey rsc streaming test (#51274) This is checking for only alpha-numerical chars for the build_id value although they can also contain dashes x-ref: https://github.com/vercel/next.js/actions/runs/5263773644/jobs/9514284188#step:25:454 --- test/e2e/app-dir/rsc-basic/rsc-basic.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts index e0b523c969b5..e6c12979ffdb 100644 --- a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts @@ -399,7 +399,7 @@ createNextDescribe( const result = await resolveStreamResponse(response) expect(result).toContain('component:index.server') expect(result).toMatch( - isNextDev ? /0:\["development",/ : /0:\["\w+",/ + isNextDev ? /0:\["development",/ : /0:\[".*?",/ ) }) }) From 99bdad42746a6547acda967c88447975653b239b Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 13 Jun 2023 23:45:54 -0700 Subject: [PATCH 29/54] fix trigger release step --- .github/workflows/trigger_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 1c36715db4cc..45882d951253 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -50,7 +50,7 @@ jobs: - run: git clone https://github.com/vercel/next.js.git --depth=25 . - - run: git describe || 'echo failed to get tag' + - run: git describe || echo 'failed to get tag' # https://github.com/actions/virtual-environments/issues/1187 - name: tune linux network From c8f65ede874ff8ae6e049a93882e7d2374d8e5fe Mon Sep 17 00:00:00 2001 From: vercel-release-bot Date: Wed, 14 Jun 2023 06:52:30 +0000 Subject: [PATCH 30/54] v13.4.6-canary.3 --- 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 0e1cdeae983d..8aa09635dea1 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.4.6-canary.2" + "version": "13.4.6-canary.3" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 53d01ae92f4c..14af8b2485ce 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.6-canary.2", + "version": "13.4.6-canary.3", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 28eec297098a..02396e9fb8a2 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.6-canary.2", + "version": "13.4.6-canary.3", "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.6-canary.2", + "@next/eslint-plugin-next": "13.4.6-canary.3", "@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 00ba7c6fd9cb..367897aca74a 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.6-canary.2", + "version": "13.4.6-canary.3", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index b67f9600ab75..888706cf3dfa 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.4.6-canary.2", + "version": "13.4.6-canary.3", "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 203611084c26..9be263a2359b 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.6-canary.2", + "version": "13.4.6-canary.3", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index ac95c4ceea42..996462a9e814 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.4.6-canary.2", + "version": "13.4.6-canary.3", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 263a0c0088aa..6a640b799d1c 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.4.6-canary.2", + "version": "13.4.6-canary.3", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 23bf89017514..ce3b5da06a2e 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.4.6-canary.2", + "version": "13.4.6-canary.3", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index bca9f1bb4d77..8bc64b4bb2e5 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.6-canary.2", + "version": "13.4.6-canary.3", "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 918340f1f9e0..c07722c3348d 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.6-canary.2", + "version": "13.4.6-canary.3", "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 e36b7a17195a..5be0aa1fdb99 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.6-canary.2", + "version": "13.4.6-canary.3", "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 3c05c59fed18..755b35bd8a57 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.4.6-canary.2", + "version": "13.4.6-canary.3", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index ca3e2e5e1f0d..7b7b37f7e367 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.4.6-canary.2", + "version": "13.4.6-canary.3", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -83,7 +83,7 @@ ] }, "dependencies": { - "@next/env": "13.4.6-canary.2", + "@next/env": "13.4.6-canary.3", "@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.6-canary.2", - "@next/polyfill-nomodule": "13.4.6-canary.2", - "@next/react-dev-overlay": "13.4.6-canary.2", - "@next/react-refresh-utils": "13.4.6-canary.2", - "@next/swc": "13.4.6-canary.2", + "@next/polyfill-module": "13.4.6-canary.3", + "@next/polyfill-nomodule": "13.4.6-canary.3", + "@next/react-dev-overlay": "13.4.6-canary.3", + "@next/react-refresh-utils": "13.4.6-canary.3", + "@next/swc": "13.4.6-canary.3", "@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 3aa35d6e7ec9..185495ad20ca 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.6-canary.2", + "version": "13.4.6-canary.3", "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 ee5a0a2591b5..3f65b577c13c 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.6-canary.2", + "version": "13.4.6-canary.3", "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 3da8bad95a1a..a01b11e802d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -426,7 +426,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.4.6-canary.2 + '@next/eslint-plugin-next': 13.4.6-canary.3 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -503,12 +503,12 @@ importers: '@jest/types': 29.5.0 '@napi-rs/cli': 2.14.7 '@napi-rs/triples': 1.1.0 - '@next/env': 13.4.6-canary.2 - '@next/polyfill-module': 13.4.6-canary.2 - '@next/polyfill-nomodule': 13.4.6-canary.2 - '@next/react-dev-overlay': 13.4.6-canary.2 - '@next/react-refresh-utils': 13.4.6-canary.2 - '@next/swc': 13.4.6-canary.2 + '@next/env': 13.4.6-canary.3 + '@next/polyfill-module': 13.4.6-canary.3 + '@next/polyfill-nomodule': 13.4.6-canary.3 + '@next/react-dev-overlay': 13.4.6-canary.3 + '@next/react-refresh-utils': 13.4.6-canary.3 + '@next/swc': 13.4.6-canary.3 '@opentelemetry/api': 1.4.1 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.5.1 From dd937bde09060686493040f04f33267ce2789904 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 14 Jun 2023 13:17:16 +0200 Subject: [PATCH 31/54] Remove edge functions tracking in dev server (#51122) ## What? While looking at the CPU profile of booting up Vercel's site I noticed that we call `getPageStaticInfo` a lot. Looking into this more it turns out `getPageStaticInfo` is currently called for all routes, `pages` and `app`, even though the data is only being used to keep track of which routes are edge functions, however, this is already tracked as part of the build output so this didn't make sense to me. First I tried commenting out this entire block and all tests were passing, so I removed it except for middleware as middleware can provide a `matcher` which does need to be known before compiling the file as of right now. After that change a different set of tests started failing, specifically the one that checks if we warn correctly. Interestingly by fixing `getPageStaticInfo` being called early and for all routes it also caused these warnings to no longer trigger as there was a case in the manifest plugin that called `getPageStaticInfo` with less information. Calling `getPageStaticInfo` there was not needed either as we call `getPageStaticInfo` at the beginning of the build for each entrypoint so we already have the information required, it just had to be passed down so that the manifest plugin could use it. For Vercel's site this change improves booting the dev server by over 1 second. Related to #48748 --------- Co-authored-by: Jiachi Liu --- .../build/analysis/get-page-static-info.ts | 8 +- packages/next/src/build/entries.ts | 24 ++++++ .../webpack/loaders/get-module-build-info.ts | 2 + .../build/webpack/loaders/next-app-loader.ts | 7 ++ .../next-edge-app-route-loader/index.ts | 7 ++ .../loaders/next-edge-function-loader.ts | 7 ++ .../loaders/next-edge-ssr-loader/index.ts | 8 ++ .../webpack/loaders/next-middleware-loader.ts | 18 +++- .../loaders/next-route-loader/index.ts | 15 +++- .../webpack/plugins/middleware-plugin.ts | 82 +++++-------------- packages/next/src/server/dev/hot-reloader.ts | 9 ++ .../next/src/server/dev/next-dev-server.ts | 58 +++---------- .../edge-configurable-runtime/index.test.ts | 4 +- 13 files changed, 135 insertions(+), 114 deletions(-) diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index 4d76349cc411..eeaccb4159c2 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -20,9 +20,9 @@ import { RSC_MODULE_TYPES } from '../../shared/lib/constants' import type { RSCMeta } from '../webpack/loaders/get-module-build-info' export interface MiddlewareConfig { - matchers: MiddlewareMatcher[] - unstable_allowDynamicGlobs: string[] - regions: string[] | string + matchers?: MiddlewareMatcher[] + unstable_allowDynamicGlobs?: string[] + regions?: string[] | string } export interface MiddlewareMatcher { @@ -39,7 +39,7 @@ export interface PageStaticInfo { ssg?: boolean ssr?: boolean rsc?: RSCModuleType - middleware?: Partial + middleware?: MiddlewareConfig amp?: boolean | 'hybrid' } diff --git a/packages/next/src/build/entries.ts b/packages/next/src/build/entries.ts index 8ffcf0d3dedd..b11b79846634 100644 --- a/packages/next/src/build/entries.ts +++ b/packages/next/src/build/entries.ts @@ -294,6 +294,7 @@ export function getEdgeServerEntry(opts: { appDirLoader?: string hasInstrumentationHook?: boolean preferredRegion: string | string[] | undefined + middlewareConfig?: MiddlewareConfig }) { if ( opts.pagesType === 'app' && @@ -306,6 +307,9 @@ export function getEdgeServerEntry(opts: { appDirLoader: Buffer.from(opts.appDirLoader || '').toString('base64'), nextConfigOutput: opts.config.output, preferredRegion: opts.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(opts.middlewareConfig || {}) + ).toString('base64'), } return { @@ -321,6 +325,10 @@ export function getEdgeServerEntry(opts: { matchers: opts.middleware?.matchers ? encodeMatchers(opts.middleware.matchers) : '', + preferredRegion: opts.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(opts.middlewareConfig || {}) + ).toString('base64'), } return `next-middleware-loader?${stringify(loaderParams)}!` @@ -332,6 +340,9 @@ export function getEdgeServerEntry(opts: { page: opts.page, rootDir: opts.rootDir, preferredRegion: opts.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(opts.middlewareConfig || {}) + ).toString('base64'), } return `next-edge-function-loader?${stringify(loaderParams)}!` @@ -363,6 +374,9 @@ export function getEdgeServerEntry(opts: { incrementalCacheHandlerPath: opts.config.experimental.incrementalCacheHandlerPath, preferredRegion: opts.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(opts.middlewareConfig || {}) + ).toString('base64'), } return { @@ -569,6 +583,9 @@ export async function createEntrypoints( assetPrefix: config.assetPrefix, nextConfigOutput: config.output, preferredRegion: staticInfo.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(staticInfo.middleware || {}) + ).toString('base64'), }) } else if (isInstrumentationHookFile(page) && pagesType === 'root') { server[serverBundlePath.replace('src/', '')] = { @@ -586,6 +603,9 @@ export async function createEntrypoints( page, absolutePagePath, preferredRegion: staticInfo.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(staticInfo.middleware || {}) + ).toString('base64'), }), ] } else { @@ -609,6 +629,9 @@ export async function createEntrypoints( // This isn't used with edge as it needs to be set on the entry module, which will be the `edgeServerEntry` instead. // Still passing it here for consistency. preferredRegion: staticInfo.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(staticInfo.middleware || {}) + ).toString('base64'), }).import } const normalizedServerBundlePath = @@ -627,6 +650,7 @@ export async function createEntrypoints( pagesType, appDirLoader, preferredRegion: staticInfo.preferredRegion, + middlewareConfig: staticInfo.middleware, }) }, }) diff --git a/packages/next/src/build/webpack/loaders/get-module-build-info.ts b/packages/next/src/build/webpack/loaders/get-module-build-info.ts index c99994d29409..989d49446a02 100644 --- a/packages/next/src/build/webpack/loaders/get-module-build-info.ts +++ b/packages/next/src/build/webpack/loaders/get-module-build-info.ts @@ -1,4 +1,5 @@ import type { + MiddlewareConfig, MiddlewareMatcher, RSCModuleType, } from '../../analysis/get-page-static-info' @@ -56,6 +57,7 @@ export interface RouteMeta { page: string absolutePagePath: string preferredRegion: string | string[] | undefined + middlewareConfig: MiddlewareConfig } export interface EdgeMiddlewareMeta { diff --git a/packages/next/src/build/webpack/loaders/next-app-loader.ts b/packages/next/src/build/webpack/loaders/next-app-loader.ts index f042aad211e9..5a6fee4e8547 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader.ts @@ -22,6 +22,7 @@ import { RouteKind } from '../../../server/future/route-kind' import { AppRouteRouteModuleOptions } from '../../../server/future/route-modules/app-route/module' import { AppBundlePathNormalizer } from '../../../server/future/normalizers/built/app/app-bundle-path-normalizer' import { FileType, fileExists } from '../../../lib/file-exists' +import { MiddlewareConfig } from '../../analysis/get-page-static-info' export type AppLoaderOptions = { name: string @@ -37,6 +38,7 @@ export type AppLoaderOptions = { isDev?: boolean basePath: string nextConfigOutput?: NextConfig['output'] + middlewareConfig: string } type AppLoader = webpack.LoaderDefinitionFunction @@ -435,14 +437,19 @@ const nextAppLoader: AppLoader = async function nextAppLoader() { nextConfigOutput, preferredRegion, basePath, + middlewareConfig: middlewareConfigBase64, } = loaderOptions const buildInfo = getModuleBuildInfo((this as any)._module) const page = name.replace(/^app/, '') + const middlewareConfig: MiddlewareConfig = JSON.parse( + Buffer.from(middlewareConfigBase64, 'base64').toString() + ) buildInfo.route = { page, absolutePagePath: createAbsolutePath(appDir, pagePath), preferredRegion, + middlewareConfig, } const extensions = pageExtensions.map((extension) => `.${extension}`) 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 861f2a954523..60b82966e3ce 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 @@ -3,6 +3,7 @@ import { stringifyRequest } from '../../stringify-request' import { NextConfig } from '../../../../server/config-shared' import { webpack } from 'next/dist/compiled/webpack/webpack' import { WEBPACK_RESOURCE_QUERIES } from '../../../../lib/constants' +import { MiddlewareConfig } from '../../../analysis/get-page-static-info' export type EdgeAppRouteLoaderQuery = { absolutePagePath: string @@ -10,6 +11,7 @@ export type EdgeAppRouteLoaderQuery = { appDirLoader: string preferredRegion: string | string[] | undefined nextConfigOutput: NextConfig['output'] + middlewareConfig: string } const EdgeAppRouteLoader: webpack.LoaderDefinitionFunction = @@ -19,9 +21,13 @@ const EdgeAppRouteLoader: webpack.LoaderDefinitionFunction = @@ -16,13 +18,18 @@ const nextEdgeFunctionLoader: webpack.LoaderDefinitionFunction = sriEnabled, incrementalCacheHandlerPath, preferredRegion, + middlewareConfig: middlewareConfigBase64, } = this.getOptions() + const middlewareConfig: MiddlewareConfig = JSON.parse( + Buffer.from(middlewareConfigBase64, 'base64').toString() + ) + const stringifiedConfig = Buffer.from( stringifiedConfigBase64 || '', 'base64' @@ -74,6 +81,7 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction = page, absolutePagePath, preferredRegion, + middlewareConfig, } const stringifiedPagePath = stringifyRequest(this, absolutePagePath) diff --git a/packages/next/src/build/webpack/loaders/next-middleware-loader.ts b/packages/next/src/build/webpack/loaders/next-middleware-loader.ts index aac64a345df7..39c185774be6 100644 --- a/packages/next/src/build/webpack/loaders/next-middleware-loader.ts +++ b/packages/next/src/build/webpack/loaders/next-middleware-loader.ts @@ -1,4 +1,7 @@ -import type { MiddlewareMatcher } from '../../analysis/get-page-static-info' +import type { + MiddlewareConfig, + MiddlewareMatcher, +} from '../../analysis/get-page-static-info' import { getModuleBuildInfo } from './get-module-build-info' import { stringifyRequest } from '../stringify-request' import { MIDDLEWARE_LOCATION_REGEXP } from '../../../lib/constants' @@ -8,6 +11,8 @@ export type MiddlewareLoaderOptions = { page: string rootDir: string matchers?: string + preferredRegion: string | string[] | undefined + middlewareConfig: string } // matchers can have special characters that break the loader params @@ -28,9 +33,14 @@ export default function middlewareLoader(this: any) { page, rootDir, matchers: encodedMatchers, + preferredRegion, + middlewareConfig: middlewareConfigBase64, }: MiddlewareLoaderOptions = this.getOptions() const matchers = encodedMatchers ? decodeMatchers(encodedMatchers) : undefined const stringifiedPagePath = stringifyRequest(this, absolutePagePath) + const middlewareConfig: MiddlewareConfig = JSON.parse( + Buffer.from(middlewareConfigBase64, 'base64').toString() + ) const buildInfo = getModuleBuildInfo(this._module) buildInfo.nextEdgeMiddleware = { matchers, @@ -38,6 +48,12 @@ export default function middlewareLoader(this: any) { page.replace(new RegExp(`/${MIDDLEWARE_LOCATION_REGEXP}$`), '') || '/', } buildInfo.rootDir = rootDir + buildInfo.route = { + page, + absolutePagePath, + preferredRegion, + middlewareConfig, + } return ` import 'next/dist/esm/server/web/globals' diff --git a/packages/next/src/build/webpack/loaders/next-route-loader/index.ts b/packages/next/src/build/webpack/loaders/next-route-loader/index.ts index d30a60747bc1..8f5bbb5e2a14 100644 --- a/packages/next/src/build/webpack/loaders/next-route-loader/index.ts +++ b/packages/next/src/build/webpack/loaders/next-route-loader/index.ts @@ -5,6 +5,7 @@ import { getModuleBuildInfo } from '../get-module-build-info' import { PagesRouteModuleOptions } from '../../../../server/future/route-modules/pages/module' import { RouteKind } from '../../../../server/future/route-kind' import { normalizePagePath } from '../../../../shared/lib/page-path/normalize-page-path' +import { MiddlewareConfig } from '../../../analysis/get-page-static-info' /** * The options for the route loader. @@ -24,6 +25,8 @@ type RouteLoaderOptions = { * The absolute path to the userland page file. */ absolutePagePath: string + + middlewareConfig: string } /** @@ -42,19 +45,29 @@ export function getRouteLoaderEntry(query: RouteLoaderOptions): string { */ const loader: webpack.LoaderDefinitionFunction = function () { - const { page, preferredRegion, absolutePagePath } = this.getOptions() + const { + page, + preferredRegion, + absolutePagePath, + middlewareConfig: middlewareConfigBase64, + } = this.getOptions() // Ensure we only run this loader for as a module. if (!this._module) { throw new Error('Invariant: expected this to reference a module') } + const middlewareConfig: MiddlewareConfig = JSON.parse( + Buffer.from(middlewareConfigBase64, 'base64').toString() + ) + // Attach build info to the module. const buildInfo = getModuleBuildInfo(this._module) buildInfo.route = { page, absolutePagePath, preferredRegion, + middlewareConfig, } const options: Omit = { diff --git a/packages/next/src/build/webpack/plugins/middleware-plugin.ts b/packages/next/src/build/webpack/plugins/middleware-plugin.ts index a06c388fe40a..1d1466797358 100644 --- a/packages/next/src/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/src/build/webpack/plugins/middleware-plugin.ts @@ -22,10 +22,7 @@ import { NEXT_FONT_MANIFEST, SERVER_REFERENCE_MANIFEST, } from '../../../shared/lib/constants' -import { - getPageStaticInfo, - MiddlewareConfig, -} from '../../analysis/get-page-static-info' +import { MiddlewareConfig } from '../../analysis/get-page-static-info' import { Telemetry } from '../../../telemetry/storage' import { traceGlobals } from '../../../trace/shared' import { EVENT_BUILD_FEATURE_USAGE } from '../../../telemetry/events' @@ -251,11 +248,11 @@ function isNodeJsModule(moduleName: string) { function isDynamicCodeEvaluationAllowed( fileName: string, - edgeFunctionConfig?: Partial, + middlewareConfig?: MiddlewareConfig, rootDir?: string ) { const name = fileName.replace(rootDir ?? '', '') - return isMatch(name, edgeFunctionConfig?.unstable_allowDynamicGlobs ?? []) + return isMatch(name, middlewareConfig?.unstable_allowDynamicGlobs ?? []) } function buildUnsupportedApiError({ @@ -511,47 +508,16 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`, } } -async function findEntryEdgeFunctionConfig( - entryDependency: any, - resolver: webpack.Resolver -) { - if (entryDependency?.request?.startsWith('next-')) { - const absolutePagePath = - new URL(entryDependency.request, 'http://example.org').searchParams.get( - 'absolutePagePath' - ) ?? '' - const pageFilePath = await new Promise((resolve) => - resolver.resolve({}, '/', absolutePagePath, {}, (err, path) => - resolve(err || path) - ) - ) - if (typeof pageFilePath === 'string') { - return { - file: pageFilePath, - config: ( - await getPageStaticInfo({ - nextConfig: {}, - pageFilePath, - isDev: false, - pageType: 'root', - }) - ).middleware, - } - } - } -} - function getExtractMetadata(params: { compilation: webpack.Compilation compiler: webpack.Compiler dev: boolean metadataByEntry: Map -}) { +}): () => Promise { const { dev, compilation, metadataByEntry, compiler } = params const { webpack: wp } = compiler return async () => { metadataByEntry.clear() - const resolver = compilation.resolverFactory.get('normal') const telemetry: Telemetry | undefined = traceGlobals.get('telemetry') for (const [entryName, entry] of compilation.entries) { @@ -560,10 +526,6 @@ function getExtractMetadata(params: { continue } const entryDependency = entry.dependencies?.[0] - const edgeFunctionConfig = await findEntryEdgeFunctionConfig( - entryDependency, - resolver - ) const { rootDir, route } = getModuleBuildInfo( compilation.moduleGraph.getResolvedModule(entryDependency) ) @@ -584,6 +546,20 @@ function getExtractMetadata(params: { wasmBindings: new Map(), assetBindings: new Map(), } + + if (route?.middlewareConfig?.regions) { + entryMetadata.regions = route.middlewareConfig.regions + } + + if (route?.preferredRegion) { + const preferredRegion = route.preferredRegion + entryMetadata.regions = + // Ensures preferredRegion is always an array in the manifest. + typeof preferredRegion === 'string' + ? [preferredRegion] + : preferredRegion + } + let ogImageGenerationCount = 0 for (const module of modules) { @@ -625,13 +601,12 @@ function getExtractMetadata(params: { if (/node_modules[\\/]regenerator-runtime[\\/]runtime\.js/.test(id)) { continue } - - if (edgeFunctionConfig?.config?.unstable_allowDynamicGlobs) { + if (route?.middlewareConfig?.unstable_allowDynamicGlobs) { telemetry?.record({ eventName: 'NEXT_EDGE_ALLOW_DYNAMIC_USED', payload: { - ...edgeFunctionConfig, - file: edgeFunctionConfig.file.replace(rootDir ?? '', ''), + file: route?.absolutePagePath.replace(rootDir ?? '', ''), + config: route?.middlewareConfig, fileWithDynamicCode: module.userRequest.replace( rootDir ?? '', '' @@ -642,7 +617,7 @@ function getExtractMetadata(params: { if ( !isDynamicCodeEvaluationAllowed( module.userRequest, - edgeFunctionConfig?.config, + route?.middlewareConfig, rootDir ) ) { @@ -662,19 +637,6 @@ function getExtractMetadata(params: { } } - if (edgeFunctionConfig?.config?.regions) { - entryMetadata.regions = edgeFunctionConfig.config.regions - } - - if (route?.preferredRegion) { - const preferredRegion = route.preferredRegion - entryMetadata.regions = - // Ensures preferredRegion is always an array in the manifest. - typeof preferredRegion === 'string' - ? [preferredRegion] - : preferredRegion - } - /** * The entry module has to be either a page or a middleware and hold * the corresponding metadata. diff --git a/packages/next/src/server/dev/hot-reloader.ts b/packages/next/src/server/dev/hot-reloader.ts index 77f879f219e2..0f0957b05e61 100644 --- a/packages/next/src/server/dev/hot-reloader.ts +++ b/packages/next/src/server/dev/hot-reloader.ts @@ -793,6 +793,9 @@ export default class HotReloader { assetPrefix: this.config.assetPrefix, nextConfigOutput: this.config.output, preferredRegion: staticInfo.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(staticInfo.middleware || {}) + ).toString('base64'), }).import : undefined @@ -877,6 +880,9 @@ export default class HotReloader { assetPrefix: this.config.assetPrefix, nextConfigOutput: this.config.output, preferredRegion: staticInfo.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(staticInfo.middleware || {}) + ).toString('base64'), }) } else if ( !isAPIRoute(page) && @@ -888,6 +894,9 @@ export default class HotReloader { page, absolutePagePath: relativeRequest, preferredRegion: staticInfo.preferredRegion, + middlewareConfig: Buffer.from( + JSON.stringify(staticInfo.middleware || {}) + ).toString('base64'), }) } else { value = relativeRequest diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 58b5430840b5..e1b2700770ad 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -8,7 +8,7 @@ import type { ParsedUrlQuery } from 'querystring' import type { Server as HTTPServer } from 'http' import type { UrlWithParsedQuery } from 'url' import type { BaseNextRequest, BaseNextResponse } from '../base-http' -import type { MiddlewareRoutingItem, RoutingItem } from '../base-server' +import type { MiddlewareRoutingItem } from '../base-server' import type { MiddlewareMatcher } from '../../build/analysis/get-page-static-info' import type { FunctionComponent } from 'react' import type { RouteMatch } from '../future/route-matches/route-match' @@ -68,10 +68,7 @@ import * as Log from '../../build/output/log' import isError, { getProperError } from '../../lib/is-error' import { getRouteRegex } from '../../shared/lib/router/utils/route-regex' import { getSortedRoutes } from '../../shared/lib/router/utils' -import { - getStaticInfoIncludingLayouts, - runDependingOnPageType, -} from '../../build/entries' +import { getStaticInfoIncludingLayouts } from '../../build/entries' import { NodeNextResponse, NodeNextRequest } from '../base-http/node' import { normalizePathSep } from '../../shared/lib/page-path/normalize-path-sep' import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths' @@ -138,7 +135,6 @@ export default class DevServer extends Server { private actualMiddlewareFile?: string private actualInstrumentationHookFile?: string private middleware?: MiddlewareRoutingItem - private edgeFunctions?: RoutingItem[] private verifyingTypeScript?: boolean private usingTypeScript?: boolean private originalFetch: typeof fetch @@ -437,7 +433,6 @@ export default class DevServer extends Server { const routedPages: string[] = [] const knownFiles = wp.getTimeInfoEntries() const appPaths: Record = {} - const edgeRoutesSet = new Set() const pageNameSet = new Set() const conflictingAppPagePaths = new Set() const appPageFilePaths = new Map() @@ -500,17 +495,16 @@ export default class DevServer extends Server { pagesType: 'root', }) - const staticInfo = await getStaticInfoIncludingLayouts({ - pageFilePath: fileName, - config: this.nextConfig, - appDir: this.appDir, - page: rootFile, - isDev: true, - isInsideAppDir: isAppPath, - pageExtensions: this.nextConfig.pageExtensions, - }) - if (isMiddlewareFile(rootFile)) { + const staticInfo = await getStaticInfoIncludingLayouts({ + pageFilePath: fileName, + config: this.nextConfig, + appDir: this.appDir, + page: rootFile, + isDev: true, + isInsideAppDir: isAppPath, + pageExtensions: this.nextConfig.pageExtensions, + }) if (this.nextConfig.output === 'export') { Log.error( 'Middleware cannot be used with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export' @@ -600,18 +594,7 @@ export default class DevServer extends Server { continue } - await runDependingOnPageType({ - page: pageName, - pageRuntime: staticInfo.runtime, - onClient: () => {}, - onServer: () => { - routedPages.push(pageName) - }, - onEdgeServer: () => { - routedPages.push(pageName) - edgeRoutesSet.add(pageName) - }, - }) + routedPages.push(pageName) } const numConflicting = conflictingAppPagePaths.size @@ -770,19 +753,6 @@ export default class DevServer extends Server { this.appPathRoutes = Object.fromEntries( Object.entries(appPaths).map(([k, v]) => [k, v.sort()]) ) - const edgeRoutes = Array.from(edgeRoutesSet) - this.edgeFunctions = getSortedRoutes(edgeRoutes).map((page) => { - const matchedAppPaths = this.getOriginalAppPaths(page) - if (Array.isArray(matchedAppPaths)) { - page = matchedAppPaths[0] - } - const edgeRegex = getRouteRegex(page) - return { - match: getRouteMatcher(edgeRegex), - page, - re: edgeRegex.re, - } - }) this.middleware = middlewareMatchers ? { @@ -1456,10 +1426,6 @@ export default class DevServer extends Server { return this.middleware } - protected getEdgeFunctionsPages() { - return this.edgeFunctions ? this.edgeFunctions.map(({ page }) => page) : [] - } - protected getServerComponentManifest() { return undefined } diff --git a/test/e2e/edge-configurable-runtime/index.test.ts b/test/e2e/edge-configurable-runtime/index.test.ts index 3f52539a829e..f1c89f565170 100644 --- a/test/e2e/edge-configurable-runtime/index.test.ts +++ b/test/e2e/edge-configurable-runtime/index.test.ts @@ -54,7 +54,7 @@ describe.each([ expect(res.status).toEqual(200) expect(next.cliOutput).not.toInclude('error') expect(stripAnsi(next.cliOutput)).toInclude( - `- warn /pages/api/edge provided runtime 'experimental-edge'. It can be updated to 'edge' instead.` + `- warn /api/edge provided runtime 'experimental-edge'. It can be updated to 'edge' instead.` ) }) it('warns about page using edge runtime', async () => { @@ -86,7 +86,7 @@ describe.each([ const res = await fetchViaHTTP(next.url, `/`) expect(res.status).toEqual(200) expect(stripAnsi(next.cliOutput)).toInclude( - `- error Page /pages provided runtime 'edge', the edge runtime for rendering is currently experimental. Use runtime 'experimental-edge' instead.` + `- error Page / provided runtime 'edge', the edge runtime for rendering is currently experimental. Use runtime 'experimental-edge' instead.` ) expect(next.cliOutput).not.toInclude('warn') }) From cbeecba83bdc7c749806cb1ac08776ddf7f1174a Mon Sep 17 00:00:00 2001 From: vercel-release-bot Date: Wed, 14 Jun 2023 11:21:24 +0000 Subject: [PATCH 32/54] v13.4.6-canary.4 --- 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 8aa09635dea1..7f5c0d1e38e1 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.4.6-canary.3" + "version": "13.4.6-canary.4" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 14af8b2485ce..68fd7850701b 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.6-canary.3", + "version": "13.4.6-canary.4", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 02396e9fb8a2..4b0ec080b2ea 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.6-canary.3", + "version": "13.4.6-canary.4", "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.6-canary.3", + "@next/eslint-plugin-next": "13.4.6-canary.4", "@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 367897aca74a..40bbc477cb22 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.6-canary.3", + "version": "13.4.6-canary.4", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 888706cf3dfa..e610eddc35e2 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.4.6-canary.3", + "version": "13.4.6-canary.4", "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 9be263a2359b..abe44bb20259 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.6-canary.3", + "version": "13.4.6-canary.4", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 996462a9e814..ad0dfc209761 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.4.6-canary.3", + "version": "13.4.6-canary.4", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 6a640b799d1c..81529aa8178e 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.4.6-canary.3", + "version": "13.4.6-canary.4", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index ce3b5da06a2e..a30e07aca730 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.4.6-canary.3", + "version": "13.4.6-canary.4", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 8bc64b4bb2e5..5811d858823e 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.6-canary.3", + "version": "13.4.6-canary.4", "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 c07722c3348d..1dd77a3de843 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.6-canary.3", + "version": "13.4.6-canary.4", "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 5be0aa1fdb99..e34cd929c369 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.6-canary.3", + "version": "13.4.6-canary.4", "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 755b35bd8a57..ca3c3ec24f06 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.4.6-canary.3", + "version": "13.4.6-canary.4", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index 7b7b37f7e367..5d04eccd810e 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.4.6-canary.3", + "version": "13.4.6-canary.4", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -83,7 +83,7 @@ ] }, "dependencies": { - "@next/env": "13.4.6-canary.3", + "@next/env": "13.4.6-canary.4", "@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.6-canary.3", - "@next/polyfill-nomodule": "13.4.6-canary.3", - "@next/react-dev-overlay": "13.4.6-canary.3", - "@next/react-refresh-utils": "13.4.6-canary.3", - "@next/swc": "13.4.6-canary.3", + "@next/polyfill-module": "13.4.6-canary.4", + "@next/polyfill-nomodule": "13.4.6-canary.4", + "@next/react-dev-overlay": "13.4.6-canary.4", + "@next/react-refresh-utils": "13.4.6-canary.4", + "@next/swc": "13.4.6-canary.4", "@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 185495ad20ca..2a355ed5f483 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.6-canary.3", + "version": "13.4.6-canary.4", "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 3f65b577c13c..70e962f27cbf 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.6-canary.3", + "version": "13.4.6-canary.4", "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 a01b11e802d4..ed387495a895 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -426,7 +426,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.4.6-canary.3 + '@next/eslint-plugin-next': 13.4.6-canary.4 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -503,12 +503,12 @@ importers: '@jest/types': 29.5.0 '@napi-rs/cli': 2.14.7 '@napi-rs/triples': 1.1.0 - '@next/env': 13.4.6-canary.3 - '@next/polyfill-module': 13.4.6-canary.3 - '@next/polyfill-nomodule': 13.4.6-canary.3 - '@next/react-dev-overlay': 13.4.6-canary.3 - '@next/react-refresh-utils': 13.4.6-canary.3 - '@next/swc': 13.4.6-canary.3 + '@next/env': 13.4.6-canary.4 + '@next/polyfill-module': 13.4.6-canary.4 + '@next/polyfill-nomodule': 13.4.6-canary.4 + '@next/react-dev-overlay': 13.4.6-canary.4 + '@next/react-refresh-utils': 13.4.6-canary.4 + '@next/swc': 13.4.6-canary.4 '@opentelemetry/api': 1.4.1 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.5.1 From c6c4a3d3ebed2e7e43d5bfc377507019dba8a74d Mon Sep 17 00:00:00 2001 From: Jimmy Lai Date: Wed, 14 Jun 2023 15:42:14 +0200 Subject: [PATCH 33/54] app router: fix double fetch on prefetch={false} (#51292) This PR fixes a few reports that we were double fetching when navigating via a link that had prefetch false. ## Context The bug was happening because we were inadvertently eagerly fetching even if we potentially bailed out of the optimistic navigation, which would then trigger another fetch from going through the regular navigation path. There's potentially another bug here where we should potentially not bail out of optimistic navigation in the cases reported but we can fix that later. fix #49844 link NEXT-1287 --- .../reducers/navigate-reducer.ts | 15 ++++-- .../app-prefetch-false.test.ts | 51 +++++++++++++++++++ .../app/[slugA]/[slugB]/page.tsx | 3 ++ .../app-prefetch-false/app/[slugA]/layout.tsx | 3 ++ .../app-prefetch-false/app/[slugA]/page.tsx | 3 ++ .../app-dir/app-prefetch-false/app/layout.tsx | 22 ++++++++ .../app-dir/app-prefetch-false/app/page.tsx | 3 ++ .../app-dir/app-prefetch-false/next.config.js | 6 +++ 8 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 test/e2e/app-dir/app-prefetch-false/app-prefetch-false.test.ts create mode 100644 test/e2e/app-dir/app-prefetch-false/app/[slugA]/[slugB]/page.tsx create mode 100644 test/e2e/app-dir/app-prefetch-false/app/[slugA]/layout.tsx create mode 100644 test/e2e/app-dir/app-prefetch-false/app/[slugA]/page.tsx create mode 100644 test/e2e/app-dir/app-prefetch-false/app/layout.tsx create mode 100644 test/e2e/app-dir/app-prefetch-false/app/page.tsx create mode 100644 test/e2e/app-dir/app-prefetch-false/next.config.js diff --git a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts index b05b08487161..a5a8336e3f1d 100644 --- a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts @@ -157,9 +157,16 @@ export function navigateReducer( temporaryCacheNode.subTreeData = state.cache.subTreeData temporaryCacheNode.parallelRoutes = new Map(state.cache.parallelRoutes) - const data = createRecordFromThenable( - fetchServerResponse(url, optimisticTree, state.nextUrl, state.buildId) - ) + let data: ReturnType | undefined + + const fetchResponse = () => { + if (!data) { + data = createRecordFromThenable( + fetchServerResponse(url, optimisticTree, state.nextUrl, state.buildId) + ) + } + return data + } // TODO-APP: segments.slice(1) strips '', we can get rid of '' altogether. // TODO-APP: re-evaluate if we need to strip the last segment @@ -174,7 +181,7 @@ export function navigateReducer( temporaryCacheNode, state.cache, optimisticFlightSegmentPath, - () => data, + fetchResponse, true ) diff --git a/test/e2e/app-dir/app-prefetch-false/app-prefetch-false.test.ts b/test/e2e/app-dir/app-prefetch-false/app-prefetch-false.test.ts new file mode 100644 index 000000000000..5ed909ead614 --- /dev/null +++ b/test/e2e/app-dir/app-prefetch-false/app-prefetch-false.test.ts @@ -0,0 +1,51 @@ +import { createNextDescribe } from 'e2e-utils' + +const getPathname = (url: string) => { + const urlObj = new URL(url) + return urlObj.pathname +} + +const createRequestsListener = async (browser: BrowserInterface) => { + // wait for network idle + await browser.waitForIdleNetwork() + + let requests = [] + + browser.on('request', (req: Request) => { + requests.push([req.url(), !!req.headers()['next-router-prefetch']]) + }) + + await browser.refresh() + + return { + getRequests: () => requests, + clearRequests: () => { + requests = [] + }, + } +} + +createNextDescribe( + 'app-prefetch-false', + { + files: __dirname, + }, + ({ next, isNextDev }) => { + if (isNextDev) { + it.skip('should skip test in dev mode', () => {}) + } else { + it('should avoid double-fetching when optimistic navigation fails', async () => { + const browser = await next.browser('/foo') + const { getRequests } = await createRequestsListener(browser) + + await browser.elementByCss('[href="/foo"]').click() + await browser.elementByCss('[href="/foo/bar"]').click() + console.log('getRequests()', getRequests()) + expect( + getRequests().filter(([req]) => getPathname(req) === '/foo/bar') + .length + ).toBe(1) + }) + } + } +) diff --git a/test/e2e/app-dir/app-prefetch-false/app/[slugA]/[slugB]/page.tsx b/test/e2e/app-dir/app-prefetch-false/app/[slugA]/[slugB]/page.tsx new file mode 100644 index 000000000000..ff7159d9149f --- /dev/null +++ b/test/e2e/app-dir/app-prefetch-false/app/[slugA]/[slugB]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

hello world

+} diff --git a/test/e2e/app-dir/app-prefetch-false/app/[slugA]/layout.tsx b/test/e2e/app-dir/app-prefetch-false/app/[slugA]/layout.tsx new file mode 100644 index 000000000000..df3717d33e78 --- /dev/null +++ b/test/e2e/app-dir/app-prefetch-false/app/[slugA]/layout.tsx @@ -0,0 +1,3 @@ +export default function Layout({ children }) { + return children +} diff --git a/test/e2e/app-dir/app-prefetch-false/app/[slugA]/page.tsx b/test/e2e/app-dir/app-prefetch-false/app/[slugA]/page.tsx new file mode 100644 index 000000000000..ff7159d9149f --- /dev/null +++ b/test/e2e/app-dir/app-prefetch-false/app/[slugA]/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

hello world

+} diff --git a/test/e2e/app-dir/app-prefetch-false/app/layout.tsx b/test/e2e/app-dir/app-prefetch-false/app/layout.tsx new file mode 100644 index 000000000000..a4b80fc44a6c --- /dev/null +++ b/test/e2e/app-dir/app-prefetch-false/app/layout.tsx @@ -0,0 +1,22 @@ +import Link from 'next/link' +import React from 'react' + +export default function Root({ children }: { children: React.ReactNode }) { + return ( + + +
+ + foo + +
+
+ + foo/bar + +
+ {children} + + + ) +} diff --git a/test/e2e/app-dir/app-prefetch-false/app/page.tsx b/test/e2e/app-dir/app-prefetch-false/app/page.tsx new file mode 100644 index 000000000000..ff7159d9149f --- /dev/null +++ b/test/e2e/app-dir/app-prefetch-false/app/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

hello world

+} diff --git a/test/e2e/app-dir/app-prefetch-false/next.config.js b/test/e2e/app-dir/app-prefetch-false/next.config.js new file mode 100644 index 000000000000..807126e4cf0b --- /dev/null +++ b/test/e2e/app-dir/app-prefetch-false/next.config.js @@ -0,0 +1,6 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = {} + +module.exports = nextConfig From 419194bb4ba00067a20326f857d75e1259ed2cd7 Mon Sep 17 00:00:00 2001 From: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com> Date: Wed, 14 Jun 2023 14:52:03 +0100 Subject: [PATCH 34/54] [Docs] Fix broken link (#51281) --- .../01-building-your-application/05-optimizing/02-fonts.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-app/01-building-your-application/05-optimizing/02-fonts.mdx b/docs/02-app/01-building-your-application/05-optimizing/02-fonts.mdx index 67b7eddf4b49..a28584a6475d 100644 --- a/docs/02-app/01-building-your-application/05-optimizing/02-fonts.mdx +++ b/docs/02-app/01-building-your-application/05-optimizing/02-fonts.mdx @@ -233,7 +233,7 @@ const inter = Inter({ subsets: ['latin'] }) -View the [Font API Reference](/docs/02-app/02-api-reference/01-components/font.mdx) for more information. +View the [Font API Reference](/docs/app/api-reference/components/font) for more information. ### Using Multiple Fonts From d27cda21d67a147248af6736967c35e6e27de65e Mon Sep 17 00:00:00 2001 From: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:30:35 +0100 Subject: [PATCH 35/54] [Docs] Fix broken links (#51285) Bring back #related-links and fix broken link in fonts page --- docs/05-community/01-contribution-guide.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/05-community/01-contribution-guide.mdx b/docs/05-community/01-contribution-guide.mdx index 4f5e4de7663c..46e277951052 100644 --- a/docs/05-community/01-contribution-guide.mdx +++ b/docs/05-community/01-contribution-guide.mdx @@ -126,7 +126,7 @@ The following fields are **optional**: | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `nav_title` | Overrides the page's title in the navigation. This is useful when the page's title is too long to fit. If not provided, the `title` field is used. | | `source` | Pulls content into a shared page. See [Shared Pages](#shared-pages). | -| `related` | A list of related pages at the bottom of the document. These will automatically be turned into cards. | +| `related` | A list of related pages at the bottom of the document. These will automatically be turned into cards. See [Related Links](#related-links). | ```yaml filename="optional-fields.mdx" --- @@ -326,7 +326,7 @@ Related Links guide the user's learning journey by adding links to logical next Create related links using the `related` field in the page's metadata. -```yaml filename="related.mdx" +```yaml filename="example.mdx" --- related: description: Learn how to quickly get started with your first application. From 754c6480cba7535ef95fb8a6beace53e07c5a601 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 14 Jun 2023 17:26:27 +0200 Subject: [PATCH 36/54] Invalidate client cache when cookies have changed in Server Actions (#51290) Similar to tags and paths, when `cookies().set()` is called we have to invalidate the client router cache as well so routes with `cookies().get()` will not holding the stale data. This is critical for auth and other scenarios. In the future we can have an optimization to only invalidate routes that actually rely on cookies, similar to paths. --- .../reducers/server-action-reducer.ts | 13 ++-- .../src/server/app-render/action-handler.ts | 43 +++++++++-- .../next/src/server/app-render/app-render.tsx | 8 +++ .../adapters/request-cookies.ts | 4 +- test/e2e/app-dir/actions/app-action.test.ts | 72 +++++++++++++++++-- .../app-dir/actions/app/revalidate-2/page.js | 7 ++ .../app-dir/actions/app/revalidate/page.js | 2 +- 7 files changed, 130 insertions(+), 19 deletions(-) diff --git a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts index c0a4c022fa54..7f045adae6e2 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts @@ -32,6 +32,7 @@ type FetchServerActionResult = { actionFlightData?: FlightData | undefined | null revalidatedParts: { tag: boolean + cookie: boolean paths: string[] } } @@ -67,16 +68,18 @@ async function fetchServerAction( let revalidatedParts: FetchServerActionResult['revalidatedParts'] try { const revalidatedHeader = JSON.parse( - res.headers.get('x-action-revalidated') || '[0,[]]' + res.headers.get('x-action-revalidated') || '[[],0,0]' ) revalidatedParts = { - tag: !!revalidatedHeader[0], - paths: revalidatedHeader[1] || [], + paths: revalidatedHeader[0] || [], + tag: !!revalidatedHeader[1], + cookie: revalidatedHeader[2], } } catch (e) { revalidatedParts = { - tag: false, paths: [], + tag: false, + cookie: false, } } @@ -153,7 +156,7 @@ export function serverActionReducer( // Invalidate the cache for the revalidated parts. This has to be done before the // cache is updated with the action's flight data again. - if (revalidatedParts.tag) { + if (revalidatedParts.tag || revalidatedParts.cookie) { // Invalidate everything if the tag is set. state.prefetchCache.clear() } else if (revalidatedParts.paths.length > 0) { diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index 26b807a22de6..cb9ba968c1a8 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -22,7 +22,11 @@ import { FlightRenderResult } from './flight-render-result' import { ActionResult } from './types' import { ActionAsyncStorage } from '../../client/components/action-async-storage' import { filterReqHeaders, forbiddenHeaders } from '../lib/server-ipc/utils' -import { appendMutableCookies } from '../web/spec-extension/adapters/request-cookies' +import { + appendMutableCookies, + getModifiedCookieValues, +} from '../web/spec-extension/adapters/request-cookies' +import { RequestStore } from '../../client/components/request-async-storage' function nodeToWebReadableStream(nodeReadable: import('stream').Readable) { if (process.env.NEXT_RUNTIME !== 'edge') { @@ -129,7 +133,13 @@ function fetchIPv4v6( async function addRevalidationHeader( res: ServerResponse, - staticGenerationStore: StaticGenerationStore + { + staticGenerationStore, + requestStore, + }: { + staticGenerationStore: StaticGenerationStore + requestStore: RequestStore + } ) { await Promise.all(staticGenerationStore.pendingRevalidates || []) @@ -137,7 +147,8 @@ async function addRevalidationHeader( // client router cache as they may be stale. And if a path was revalidated, the // client needs to invalidate all subtrees below that path. - // To keep the header size small, we use a tuple of [isTagRevalidated ? 1 : 0, [paths]] + // To keep the header size small, we use a tuple of + // [[revalidatedPaths], isTagRevalidated ? 1 : 0, isCookieRevalidated ? 1 : 0] // instead of a JSON object. // TODO-APP: Currently the prefetch cache doesn't have subtree information, @@ -145,9 +156,16 @@ async function addRevalidationHeader( // TODO-APP: Currently paths are treated as tags, so the second element of the tuple // is always empty. + const isTagRevalidated = staticGenerationStore.revalidatedTags?.length ? 1 : 0 + const isCookieRevalidated = getModifiedCookieValues( + requestStore.mutableCookies + ).length + ? 1 + : 0 + res.setHeader( 'x-action-revalidated', - JSON.stringify([staticGenerationStore.revalidatedTags?.length ? 1 : 0, []]) + JSON.stringify([[], isTagRevalidated, isCookieRevalidated]) ) } @@ -226,6 +244,7 @@ export async function handleAction({ serverActionsManifest, generateFlight, staticGenerationStore, + requestStore, }: { req: IncomingMessage res: ServerResponse @@ -238,6 +257,7 @@ export async function handleAction({ asNotFound?: boolean }) => Promise staticGenerationStore: StaticGenerationStore + requestStore: RequestStore }): Promise { let actionId = req.headers[ACTION.toLowerCase()] as string const contentType = req.headers['content-type'] @@ -374,7 +394,10 @@ export async function handleAction({ // For form actions, we need to continue rendering the page. if (isFetchAction) { - await addRevalidationHeader(res, staticGenerationStore) + await addRevalidationHeader(res, { + staticGenerationStore, + requestStore, + }) actionResult = await generateFlight({ actionResult: Promise.resolve(returnVal), @@ -391,7 +414,10 @@ export async function handleAction({ // if it's a fetch action, we don't want to mess with the status code // and we'll handle it on the client router - await addRevalidationHeader(res, staticGenerationStore) + await addRevalidationHeader(res, { + staticGenerationStore, + requestStore, + }) if (isFetchAction) { return createRedirectRenderResult( @@ -418,7 +444,10 @@ export async function handleAction({ } else if (isNotFoundError(err)) { res.statusCode = 404 - await addRevalidationHeader(res, staticGenerationStore) + await addRevalidationHeader(res, { + staticGenerationStore, + requestStore, + }) if (isFetchAction) { const promise = Promise.reject(err) diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index ae5d5a3fce42..70b89b867fdc 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -224,6 +224,13 @@ export async function renderToHTMLOrFlight( staticGenerationStore.fetchMetrics = [] ;(renderOpts as any).fetchMetrics = staticGenerationStore.fetchMetrics + const requestStore = requestAsyncStorage.getStore() + if (!requestStore) { + throw new Error( + `Invariant: Render expects to have requestAsyncStorage, none found` + ) + } + // don't modify original query object query = { ...query } stripInternalQueries(query) @@ -1617,6 +1624,7 @@ export async function renderToHTMLOrFlight( serverActionsManifest, generateFlight, staticGenerationStore, + requestStore, }) if (actionRequestResult === 'not-found') { diff --git a/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts b/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts index e9fdfc60482b..d1824f0bd38e 100644 --- a/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts +++ b/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts @@ -49,7 +49,9 @@ export class RequestCookiesAdapter { const SYMBOL_MODIFY_COOKIE_VALUES = Symbol.for('next.mutated.cookies') -function getModifiedCookieValues(cookies: ResponseCookies): ResponseCookie[] { +export function getModifiedCookieValues( + cookies: ResponseCookies +): ResponseCookie[] { const modified: ResponseCookie[] | undefined = (cookies as unknown as any)[ SYMBOL_MODIFY_COOKIE_VALUES ] diff --git a/test/e2e/app-dir/actions/app-action.test.ts b/test/e2e/app-dir/actions/app-action.test.ts index 6c9d9532a5cd..0e0836012060 100644 --- a/test/e2e/app-dir/actions/app-action.test.ts +++ b/test/e2e/app-dir/actions/app-action.test.ts @@ -503,8 +503,65 @@ createNextDescribe( }, 'success') }) + it('should revalidate when cookies.set is called in a client action', async () => { + const browser = await next.browser('/revalidate') + await browser.refresh() + + let randomCookie + await check(async () => { + randomCookie = JSON.parse( + await browser.elementByCss('#random-cookie').text() + ).value + return randomCookie ? 'success' : 'failure' + }, 'success') + + console.log(123, await browser.elementByCss('body').text()) + + await browser.elementByCss('#another').click() + await check(async () => { + return browser.elementByCss('#title').text() + }, 'another route') + + const newRandomCookie = JSON.parse( + await browser.elementByCss('#random-cookie').text() + ).value + + console.log(456, await browser.elementByCss('body').text()) + + // Should be the same value + expect(randomCookie).toEqual(newRandomCookie) + + await browser.elementByCss('#back').click() + + // Modify the cookie + await browser.elementByCss('#set-cookie').click() + + // Should be different + let revalidatedRandomCookie + await check(async () => { + revalidatedRandomCookie = JSON.parse( + await browser.elementByCss('#random-cookie').text() + ).value + return randomCookie !== revalidatedRandomCookie + ? 'success' + : 'failure' + }, 'success') + + await browser.elementByCss('#another').click() + + // The other page should be revalidated too + await check(async () => { + const newRandomCookie = await JSON.parse( + await browser.elementByCss('#random-cookie').text() + ).value + return revalidatedRandomCookie === newRandomCookie + ? 'success' + : 'failure' + }, 'success') + }) + it.each(['tag', 'path'])( - 'should invalidate client cache when %s is revalidate', + 'should invalidate client cache when %s is revalidated', async (type) => { const browser = await next.browser('/revalidate') await browser.refresh() @@ -527,10 +584,15 @@ createNextDescribe( await browser.elementByCss('#back').click() - if (type === 'tag') { - await browser.elementByCss('#revalidate-thankyounext').click() - } else { - await browser.elementByCss('#revalidate-path').click() + switch (type) { + case 'tag': + await browser.elementByCss('#revalidate-thankyounext').click() + break + case 'path': + await browser.elementByCss('#revalidate-path').click() + break + default: + throw new Error(`Invalid type: ${type}`) } // Should be different diff --git a/test/e2e/app-dir/actions/app/revalidate-2/page.js b/test/e2e/app-dir/actions/app/revalidate-2/page.js index 7e57bda9dc1f..cd9641d3aca0 100644 --- a/test/e2e/app-dir/actions/app/revalidate-2/page.js +++ b/test/e2e/app-dir/actions/app/revalidate-2/page.js @@ -1,4 +1,5 @@ import { revalidateTag } from 'next/cache' +import { cookies } from 'next/headers' import Link from 'next/link' export default async function Page() { @@ -30,6 +31,12 @@ export default async function Page() { revalidate thankyounext +

+ random cookie:{' '} + + {JSON.stringify(cookies().get('random'))} + +

) } diff --git a/test/e2e/app-dir/actions/app/revalidate/page.js b/test/e2e/app-dir/actions/app/revalidate/page.js index a76f85297323..4cb957401d2d 100644 --- a/test/e2e/app-dir/actions/app/revalidate/page.js +++ b/test/e2e/app-dir/actions/app/revalidate/page.js @@ -51,7 +51,7 @@ export default async function Page() { {' '} - /revalidate/another-route + /revalidate-2

From 2761cf34fa4a207af9819d61a27ce02bbba6d2e8 Mon Sep 17 00:00:00 2001 From: OJ Kwon <1210596+kwonoj@users.noreply.github.com> Date: Wed, 14 Jun 2023 10:27:04 -0700 Subject: [PATCH 37/54] feat(turbopack): enable sassOptions (#51259) ### What? Minor fix to remove guards to the config since turbopack have this feature enabled. --- packages/next/src/lib/turbopack-warning.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next/src/lib/turbopack-warning.ts b/packages/next/src/lib/turbopack-warning.ts index b7373125227f..5a53b1d979b9 100644 --- a/packages/next/src/lib/turbopack-warning.ts +++ b/packages/next/src/lib/turbopack-warning.ts @@ -19,6 +19,7 @@ const supportedTurbopackNextConfigOptions = [ 'reactStrictMode', 'swcMinify', 'transpilePackages', + 'sassOptions.includePaths', 'experimental.appDir', 'experimental.serverComponentsExternalPackages', 'experimental.turbo', From 616ae108d9193316c65003536c48a0d6ff56d8aa Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 14 Jun 2023 11:54:23 -0600 Subject: [PATCH 38/54] Route Module Cleanup (#50936) This cleans up some setup functions within the route modules, improves logic around headers, and handles the case where the URL is invalid better for routes using the request adapters. --- packages/next/src/server/base-server.ts | 47 +++++++++++------- .../route-handler-manager.ts | 5 -- .../future/route-modules/app-route/module.ts | 21 -------- .../future/route-modules/pages/module.ts | 48 ++++++++++++++----- .../future/route-modules/route-module.ts | 7 --- packages/next/src/server/render-result.ts | 10 +++- packages/next/src/server/render.tsx | 20 ++------ .../server/send-payload/revalidate-headers.ts | 6 +-- packages/next/src/server/send-response.ts | 4 +- .../server/web/edge-route-module-wrapper.ts | 4 -- .../spec-extension/adapters/next-request.ts | 14 ++++-- .../pages/index.js | 6 ++- 12 files changed, 98 insertions(+), 94 deletions(-) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index a61ab5fa29a8..9d3c6a523221 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -11,11 +11,7 @@ import type { NextConfig, NextConfigComplete } from './config-shared' import type { NextParsedUrlQuery, NextUrlWithParsedQuery } from './request-meta' import type { ParsedUrlQuery } from 'querystring' import type { RenderOpts, RenderOptsPartial } from './render' -import type { - ResponseCacheBase, - ResponseCacheEntry, - ResponseCacheValue, -} from './response-cache' +import type { ResponseCacheBase, ResponseCacheEntry } from './response-cache' import type { UrlWithParsedQuery } from 'url' import { NormalizeError, @@ -1787,9 +1783,7 @@ export default abstract class Server { result = await module.render( (req as NodeNextRequest).originalRequest, (res as NodeNextResponse).originalResponse, - pathname, - query, - renderOpts + { page: pathname, params: match.params, query, renderOpts } ) } else { // If we didn't match a page, we should fallback to using the legacy @@ -1841,23 +1835,40 @@ export default abstract class Server { throw err } - let value: ResponseCacheValue | null + // Based on the metadata, we can determine what kind of cache result we + // should return. + + // Handle `isNotFound`. if (metadata.isNotFound) { - value = null - } else if (metadata.isRedirect) { - value = { kind: 'REDIRECT', props: metadata.pageData } - } else { - if (result.isNull()) { - return null + return { value: null, revalidate: metadata.revalidate } + } + + // Handle `isRedirect`. + if (metadata.isRedirect) { + return { + value: { + kind: 'REDIRECT', + props: metadata.pageData, + }, + revalidate: metadata.revalidate, } - value = { + } + + // Handle `isNull`. + if (result.isNull()) { + return { value: null, revalidate: metadata.revalidate } + } + + // We now have a valid HTML result that we can return to the user. + return { + value: { kind: 'PAGE', html: result, pageData: metadata.pageData, headers, - } + }, + revalidate: metadata.revalidate, } - return { revalidate: metadata.revalidate, value } } const cacheEntry = await this.responseCache.get( diff --git a/packages/next/src/server/future/route-handler-managers/route-handler-manager.ts b/packages/next/src/server/future/route-handler-managers/route-handler-manager.ts index 387f6ead4692..de7e8360238c 100644 --- a/packages/next/src/server/future/route-handler-managers/route-handler-manager.ts +++ b/packages/next/src/server/future/route-handler-managers/route-handler-manager.ts @@ -32,11 +32,6 @@ export class RouteHandlerManager { this.moduleLoader ) - // Setup the handler. It is the responsibility of the module to ensure that - // this is only called once. If this is in development mode, the require - // cache will be cleared and the module will be re-created. - module.setup() - // Convert the BaseNextRequest to a NextRequest. const request = NextRequestAdapter.fromBaseNextRequest(req) diff --git a/packages/next/src/server/future/route-modules/app-route/module.ts b/packages/next/src/server/future/route-modules/app-route/module.ts index 24b6fe3eea88..bcc7e19a8095 100644 --- a/packages/next/src/server/future/route-modules/app-route/module.ts +++ b/packages/next/src/server/future/route-modules/app-route/module.ts @@ -187,27 +187,6 @@ export class AppRouteRouteModule extends RouteModule< ) } } - } - - /** - * When true, indicates that the global interfaces have been patched via the - * `patch()` method. - */ - private hasSetup: boolean = false - - /** - * Validates the userland module to ensure the exported methods and properties - * are valid. - */ - public async setup() { - // If we've already setup, then return. - if (this.hasSetup) return - - // Mark the module as setup. The following warnings about the userland - // module will run if we're in development. If the module files are modified - // when in development, then the require cache will be busted for it and - // this method will be called again (resetting the `hasSetup` flag). - this.hasSetup = true // We only warn in development after here, so return if we're not in // development. diff --git a/packages/next/src/server/future/route-modules/pages/module.ts b/packages/next/src/server/future/route-modules/pages/module.ts index 20cc301de6c6..70fd9e3a1216 100644 --- a/packages/next/src/server/future/route-modules/pages/module.ts +++ b/packages/next/src/server/future/route-modules/pages/module.ts @@ -11,7 +11,11 @@ import type { NextParsedUrlQuery } from '../../../request-meta' import type { RenderOpts } from '../../../render' import type RenderResult from '../../../render-result' -import { RouteModule, type RouteModuleOptions } from '../route-module' +import { + RouteModule, + type RouteModuleHandleContext, + type RouteModuleOptions, +} from '../route-module' import { renderToHTML } from '../../../render' /** @@ -46,6 +50,29 @@ export type PagesUserlandModule = { readonly getServerSideProps?: GetServerSideProps } +/** + * AppRouteRouteHandlerContext is the context that is passed to the route + * handler for app routes. + */ +export interface PagesRouteHandlerContext extends RouteModuleHandleContext { + /** + * The page for the given route. + */ + page: string + + /** + * The parsed URL query for the given request. + */ + query: NextParsedUrlQuery + + /** + * The RenderOpts for the given request which include the specific modules to + * use for rendering. + */ + // TODO: (wyattjoh) break this out into smaller parts, it currently includes the userland components + renderOpts: RenderOpts +} + export type PagesRouteModuleOptions = RouteModuleOptions< PagesRouteDefinition, PagesUserlandModule @@ -55,23 +82,22 @@ export class PagesRouteModule extends RouteModule< PagesRouteDefinition, PagesUserlandModule > { - public setup(): Promise { - throw new Error('Method not implemented.') - } - public handle(): Promise { throw new Error('Method not implemented.') } - public async render( + public render( req: IncomingMessage, res: ServerResponse, - pathname: string, - query: NextParsedUrlQuery, - renderOpts: RenderOpts + context: PagesRouteHandlerContext ): Promise { - const result = await renderToHTML(req, res, pathname, query, renderOpts) - return result + return renderToHTML( + req, + res, + context.page, + context.query, + context.renderOpts + ) } } diff --git a/packages/next/src/server/future/route-modules/route-module.ts b/packages/next/src/server/future/route-modules/route-module.ts index 552e5059b099..e9521edda1ff 100644 --- a/packages/next/src/server/future/route-modules/route-module.ts +++ b/packages/next/src/server/future/route-modules/route-module.ts @@ -45,13 +45,6 @@ export abstract class RouteModule< */ public readonly definition: Readonly - /** - * Setup will setup the route handler. This could patch any globals or perform - * validation of the userland module. It is the responsibility of the module - * to ensure that this is only called once. - */ - public abstract setup(): Promise - /** * Handle will handle the request and return a response. */ diff --git a/packages/next/src/server/render-result.ts b/packages/next/src/server/render-result.ts index 5cfb48070ef4..6f8a7bb5517a 100644 --- a/packages/next/src/server/render-result.ts +++ b/packages/next/src/server/render-result.ts @@ -12,13 +12,15 @@ export type RenderResultMetadata = { isRedirect?: boolean } +type RenderResultResponse = string | ReadableStream | null + export default class RenderResult { - private readonly _response: string | ReadableStream | null + private readonly _response: RenderResultResponse private readonly _contentType: ContentTypeOption private readonly _metadata: RenderResultMetadata constructor( - response: string | ReadableStream | null, + response: RenderResultResponse, { contentType, ...metadata @@ -31,6 +33,10 @@ export default class RenderResult { this._metadata = metadata } + get body(): Readonly { + return this._response + } + get metadata(): Readonly { return this._metadata } diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index c14328438c3e..52a285e2d419 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -75,7 +75,6 @@ import { streamFromArray, streamToString, chainStreams, - createBufferedTransformStream, renderToInitialStream, continueFromInitialStream, } from './stream-utils/node-web-streams-helper' @@ -1177,8 +1176,6 @@ export async function renderToHTML( return inAmpMode ? children :
{children}
} - // Always disable streaming for pages rendering - const generateStaticHTML = true const renderDocument = async () => { // For `Document`, there are two cases that we don't support: // 1. Using `Document.getInitialProps` in the Edge runtime. @@ -1316,7 +1313,7 @@ export async function renderToHTML( return continueFromInitialStream(initialStream, { suffix, dataStream: serverComponentsInlinedTransformStream?.readable, - generateStaticHTML, + generateStaticHTML: true, getServerInsertedHTML, serverInsertedHTMLToHead: false, }) @@ -1539,18 +1536,9 @@ export async function renderToHTML( const postOptimize = (html: string) => postProcessHTML(pathname, html, renderOpts, { inAmpMode, hybridAmp }) - if (generateStaticHTML) { - const html = await streamToString(chainStreams(streams)) - const optimizedHtml = await postOptimize(html) - return new RenderResult(optimizedHtml, renderResultMeta) - } - - return new RenderResult( - chainStreams(streams).pipeThrough( - createBufferedTransformStream(postOptimize) - ), - renderResultMeta - ) + const html = await streamToString(chainStreams(streams)) + const optimizedHtml = await postOptimize(html) + return new RenderResult(optimizedHtml, renderResultMeta) } export type RenderToHTMLResult = typeof renderToHTML diff --git a/packages/next/src/server/send-payload/revalidate-headers.ts b/packages/next/src/server/send-payload/revalidate-headers.ts index eeb012e33757..b7be345c3341 100644 --- a/packages/next/src/server/send-payload/revalidate-headers.ts +++ b/packages/next/src/server/send-payload/revalidate-headers.ts @@ -7,10 +7,10 @@ export function setRevalidateHeaders( options: PayloadOptions ) { if (options.private || options.stateful) { - if (options.private || !res.getHeader('Cache-Control')) { + if (options.private || !res.hasHeader('Cache-Control')) { res.setHeader( 'Cache-Control', - `private, no-cache, no-store, max-age=0, must-revalidate` + 'private, no-cache, no-store, max-age=0, must-revalidate' ) } } else if (typeof options.revalidate === 'number') { @@ -25,6 +25,6 @@ export function setRevalidateHeaders( `s-maxage=${options.revalidate}, stale-while-revalidate` ) } else if (options.revalidate === false) { - res.setHeader('Cache-Control', `s-maxage=31536000, stale-while-revalidate`) + res.setHeader('Cache-Control', 's-maxage=31536000, stale-while-revalidate') } } diff --git a/packages/next/src/server/send-response.ts b/packages/next/src/server/send-response.ts index a722f74bcfb4..0ecc4bbadc56 100644 --- a/packages/next/src/server/send-response.ts +++ b/packages/next/src/server/send-response.ts @@ -28,8 +28,10 @@ export async function sendResponse( for (const cookie of splitCookiesString(value)) { res.appendHeader(name, cookie) } - } else { + } else if (res.hasHeader(name)) { res.appendHeader(name, value) + } else { + res.setHeader(name, value) } }) diff --git a/packages/next/src/server/web/edge-route-module-wrapper.ts b/packages/next/src/server/web/edge-route-module-wrapper.ts index e5d53bf718c4..027f49ea2d51 100644 --- a/packages/next/src/server/web/edge-route-module-wrapper.ts +++ b/packages/next/src/server/web/edge-route-module-wrapper.ts @@ -62,10 +62,6 @@ export class EdgeRouteModuleWrapper { } private async handler(request: NextRequest): Promise { - // Setup the handler if it hasn't been setup yet. It is the responsibility - // of the module to ensure that this is only called once. - this.routeModule.setup() - // Get the pathname for the matcher. Pathnames should not have trailing // slashes for matching. const pathname = removeTrailingSlash(new URL(request.url).pathname) diff --git a/packages/next/src/server/web/spec-extension/adapters/next-request.ts b/packages/next/src/server/web/spec-extension/adapters/next-request.ts index aa03f1771fc6..82394932d300 100644 --- a/packages/next/src/server/web/spec-extension/adapters/next-request.ts +++ b/packages/next/src/server/web/spec-extension/adapters/next-request.ts @@ -8,8 +8,7 @@ import { NextRequest } from '../request' export class NextRequestAdapter { public static fromBaseNextRequest(request: BaseNextRequest): NextRequest { - // TODO: look at refining this check - if ('request' in request && (request as WebNextRequest).request) { + if (request.constructor.name === 'WebNextRequest') { return NextRequestAdapter.fromWebNextRequest(request as WebNextRequest) } @@ -30,9 +29,14 @@ export class NextRequestAdapter { } else { // Grab the full URL from the request metadata. const base = getRequestMeta(request, '__NEXT_INIT_URL') - if (!base) throw new Error('Invariant: missing url on request') - - url = new URL(request.url, base) + if (!base || !base.startsWith('http')) { + // Because the URL construction relies on the fact that the URL provided + // is absolute, we need to provide a base URL. We can't use the request + // URL because it's relative, so we use a dummy URL instead. + url = new URL(request.url, 'http://n') + } else { + url = new URL(request.url, base) + } } return new NextRequest(url, { diff --git a/test/integration/app-document-style-fragment/pages/index.js b/test/integration/app-document-style-fragment/pages/index.js index 869b0180608f..461152c5ed25 100644 --- a/test/integration/app-document-style-fragment/pages/index.js +++ b/test/integration/app-document-style-fragment/pages/index.js @@ -11,6 +11,10 @@ function Hi() { ) } -Hi.getInitialProps = () => ({}) +Hi.getInitialProps = () => ({ + // To prevent the warning related to an empty object from getInitialProps, we + // need to return something. + foo: 'bar', +}) export default Hi From 916d2aa4d5fc0f3663086fe43d205cece362dacf Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 14 Jun 2023 11:07:58 -0700 Subject: [PATCH 39/54] Validate i18n locale domain (#50220) The `domain` value is meant to be a hostname and not include `http` or port so this ensures we validate it doesn't contain `:` unexpectedly causing invalid behavior. Closes: https://github.com/vercel/next.js/issues/49656 x-ref: https://github.com/vercel/next.js/issues/24991#issuecomment-870418366 --------- Co-authored-by: Jiachi Liu --- packages/next/src/server/config.ts | 7 +++++ .../i18n-support/test/index.test.js | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts index 908f26732fa3..74354a4634bf 100644 --- a/packages/next/src/server/config.ts +++ b/packages/next/src/server/config.ts @@ -537,6 +537,13 @@ function assignDefaults( if (!item.defaultLocale) return true if (!item.domain || typeof item.domain !== 'string') return true + if (item.domain.includes(':')) { + console.warn( + `i18n domain: "${item.domain}" is invalid it should be a valid domain without protocol (https://) or port (:3000) e.g. example.vercel.sh` + ) + return true + } + const defaultLocaleDuplicate = i18n.domains?.find( (altItem) => altItem.defaultLocale === item.defaultLocale && diff --git a/test/integration/i18n-support/test/index.test.js b/test/integration/i18n-support/test/index.test.js index 8c95dabd6bd8..2ea88db8b206 100644 --- a/test/integration/i18n-support/test/index.test.js +++ b/test/integration/i18n-support/test/index.test.js @@ -550,4 +550,30 @@ describe('i18n Support', () => { ) expect(stderr).toContain(`eN, fr`) }) + + it('should show proper error for invalid locale domain', async () => { + nextConfig.write(` + module.exports = { + i18n: { + locales: ['en', 'fr', 'nl', 'eN', 'fr'], + domains: [ + { + domain: 'hello:3000', + defaultLocale: 'en', + } + ], + defaultLocale: 'en', + } + } + `) + + const { code, stderr } = await nextBuild(appDir, undefined, { + stderr: true, + }) + nextConfig.restore() + expect(code).toBe(1) + expect(stderr).toContain( + `i18n domain: "hello:3000" is invalid it should be a valid domain without protocol (https://) or port (:3000) e.g. example.vercel.sh` + ) + }) }) From 86112a357c99c0bff68eefe8bc2705bf67eee68f Mon Sep 17 00:00:00 2001 From: Gideon <106825924+gidgudgod@users.noreply.github.com> Date: Thu, 15 Jun 2023 01:59:04 +0700 Subject: [PATCH 40/54] Update images.mdx (#51275) fix Fastly and Gumlet heading level --- docs/02-app/02-api-reference/05-next-config-js/images.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/02-app/02-api-reference/05-next-config-js/images.mdx b/docs/02-app/02-api-reference/05-next-config-js/images.mdx index d3fe0524ca45..bf90262d8176 100644 --- a/docs/02-app/02-api-reference/05-next-config-js/images.mdx +++ b/docs/02-app/02-api-reference/05-next-config-js/images.mdx @@ -78,7 +78,7 @@ export default function contentfulLoader({ src, quality, width }) { } ``` -## Fastly +### Fastly ```js // Docs: https://developer.fastly.com/reference/io/ @@ -91,7 +91,7 @@ export default function fastlyLoader({ src, width, quality }) { } ``` -## Gumlet +### Gumlet ```js // Docs: https://docs.gumlet.com/reference/image-transform-size From 95394409656995aac8d56487a42707a006309fea Mon Sep 17 00:00:00 2001 From: Tyler Lutz <66930763+tyler-lutz@users.noreply.github.com> Date: Wed, 14 Jun 2023 14:06:25 -0500 Subject: [PATCH 41/54] Docs: Remove extra word in 03-scripts.mdx (#51304) Removes extra 'the' in the documentation Co-authored-by: JJ Kasper --- .../01-building-your-application/05-optimizing/03-scripts.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-app/01-building-your-application/05-optimizing/03-scripts.mdx b/docs/02-app/01-building-your-application/05-optimizing/03-scripts.mdx index 6fde53d58c01..65c0388c29c5 100644 --- a/docs/02-app/01-building-your-application/05-optimizing/03-scripts.mdx +++ b/docs/02-app/01-building-your-application/05-optimizing/03-scripts.mdx @@ -45,7 +45,7 @@ export default function DashboardLayout({ children }) { } ``` -The third-party script is fetched when the the folder route (e.g. `dashboard/page.js`) or any nested route (e.g. `dashboard/settings/page.js`) is accessed by the user. Next.js will ensure the script will **only load once**, even if a user navigates between multiple routes in the same layout. +The third-party script is fetched when the folder route (e.g. `dashboard/page.js`) or any nested route (e.g. `dashboard/settings/page.js`) is accessed by the user. Next.js will ensure the script will **only load once**, even if a user navigates between multiple routes in the same layout. From c3e2999fa61cbe82b21a38af086a379f82947004 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 14 Jun 2023 12:15:43 -0700 Subject: [PATCH 42/54] Update flakey build output test (#51307) x-ref: https://github.com/vercel/next.js/actions/runs/5270495246/jobs/9530090721 --- test/integration/build-output/test/index.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/build-output/test/index.test.js b/test/integration/build-output/test/index.test.js index 3f95fca6d4d4..a5c3016b1e1c 100644 --- a/test/integration/build-output/test/index.test.js +++ b/test/integration/build-output/test/index.test.js @@ -172,10 +172,10 @@ describe('Build Output', () => { expect.stringMatching(/\/10\/1000 \(\d+ ms\)$/), expect.stringMatching(/\/300\/10 \(\d+ ms\)$/), // kept in original order - expect.stringMatching(/\/5\/5$/), - expect.stringMatching(/\/25\/25$/), - expect.stringMatching(/\/20\/20$/), - expect.stringMatching(/\/10\/10$/), + expect.stringMatching(/\/5\/5/), + expect.stringMatching(/\/25\/25/), + expect.stringMatching(/\/20\/20/), + expect.stringMatching(/\/10\/10/), // max of 7 preview paths ' [+2 more paths]', ]) { From 5e9b5e5d3cb2a5a4d3796e6d59baca7148d0326a Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 14 Jun 2023 21:17:45 +0200 Subject: [PATCH 43/54] Update example of Server Actions HoC (#51299) Currently the Server Action function with `"use server"` must be an async function as it's required by the compiler, even if it returns a promise already. --- .../03-data-fetching/04-server-actions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-app/01-building-your-application/03-data-fetching/04-server-actions.mdx b/docs/02-app/01-building-your-application/03-data-fetching/04-server-actions.mdx index 89e02b1c79b3..6a50699910ae 100644 --- a/docs/02-app/01-building-your-application/03-data-fetching/04-server-actions.mdx +++ b/docs/02-app/01-building-your-application/03-data-fetching/04-server-actions.mdx @@ -480,7 +480,7 @@ export const action = withValidate((data) => { ```js filename="lib/form-validation.js" export function withValidate(action) { - return (formData: FormData) => { + return async (formData: FormData) => { 'use server' const isValidData = verifyData(formData) From 28dca134987867f75390158bed30c665a4465952 Mon Sep 17 00:00:00 2001 From: Max Proske Date: Wed, 14 Jun 2023 13:03:36 -0700 Subject: [PATCH 44/54] Update Docker examples to use Compose v2 command (#51134) Compose v1 has reached EOL, and will no longer be included with Docker after June 2023: https://www.docker.com/blog/docker-desktop-4-19/ --- examples/with-docker-compose/.env | 2 +- examples/with-docker-compose/README.md | 14 ++++++-------- examples/with-temporal/README.md | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/with-docker-compose/.env b/examples/with-docker-compose/.env index e5aefd023575..30083c1a9c59 100644 --- a/examples/with-docker-compose/.env +++ b/examples/with-docker-compose/.env @@ -1,5 +1,5 @@ # DO NOT ADD SECRETS TO THIS FILE. This is a good place for defaults. -# If you want to add secrets use `.env.production.local` instead, which is automatically detected by docker-compose. +# If you want to add secrets use `.env.production.local` instead, which is automatically detected by `docker compose`. ENV_VARIABLE=production_server_only_variable NEXT_PUBLIC_ENV_VARIABLE=production_public_variable diff --git a/examples/with-docker-compose/README.md b/examples/with-docker-compose/README.md index 088d112c816d..7f6e23d111ae 100644 --- a/examples/with-docker-compose/README.md +++ b/examples/with-docker-compose/README.md @@ -46,12 +46,10 @@ First, run the development server: docker network create my_network # Build dev -# Note: Keep v1 command until "Use Docker Compose v2" is enabled by default for Docker Desktop for Linux -# Docker aliases `docker-compose` (v1 command) to `docker compose` (v2 command), but not the other way around -docker-compose -f docker-compose.dev.yml build +docker compose -f docker-compose.dev.yml build # Up dev -docker-compose -f docker-compose.dev.yml up +docker compose -f docker-compose.dev.yml up ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. @@ -70,10 +68,10 @@ First, run the production server (Final image approximately 110 MB). docker network create my_network # Build prod -docker-compose -f docker-compose.prod.yml build +docker compose -f docker-compose.prod.yml build # Up prod in detached mode -docker-compose -f docker-compose.prod.yml up -d +docker compose -f docker-compose.prod.yml up -d ``` Alternatively, run the production server without without multistage builds (Final image approximately 1 GB). @@ -84,10 +82,10 @@ Alternatively, run the production server without without multistage builds (Fina docker network create my_network # Build prod without multistage -docker-compose -f docker-compose.prod-without-multistage.yml build +docker compose -f docker-compose.prod-without-multistage.yml build # Up prod without multistage in detached mode -docker-compose -f docker-compose.prod-without-multistage.yml up -d +docker compose -f docker-compose.prod-without-multistage.yml up -d ``` Open [http://localhost:3000](http://localhost:3000). diff --git a/examples/with-temporal/README.md b/examples/with-temporal/README.md index 0a114e06a486..06593b5ea886 100644 --- a/examples/with-temporal/README.md +++ b/examples/with-temporal/README.md @@ -69,7 +69,7 @@ The Temporal Node SDK requires [Node `>= 14`, `node-gyp`, and Temporal Server](h In the Temporal Server docker directory: ```bash -docker-compose up +docker compose up ``` In the `next-temporal-app/` directory: From d4c2335b0f4e269b0ae62e761bfd61513c3516ea Mon Sep 17 00:00:00 2001 From: tom Date: Wed, 14 Jun 2023 22:33:07 +0200 Subject: [PATCH 45/54] chore: add dark mode for dev overlay (#48601) This PR adds a dark mode to the error overlay which respects the browsers color scheme. I just did this because I was blinded by the dev overlay a lot today and thought this change might be appreciated. Screenshot (dark mode): image Screenshot (retained light mode): image Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- .../src/internal/components/Dialog/styles.ts | 2 +- .../LeftRightDialogHeader/styles.ts | 2 ++ .../internal/components/Overlay/styles.tsx | 2 +- .../src/internal/container/BuildError.tsx | 2 +- .../src/internal/container/RuntimeError.tsx | 7 +++--- .../src/internal/styles/Base.tsx | 22 +++++++++++++++++++ .../src/internal/styles/CssReset.tsx | 2 +- 7 files changed, 31 insertions(+), 8 deletions(-) diff --git a/packages/react-dev-overlay/src/internal/components/Dialog/styles.ts b/packages/react-dev-overlay/src/internal/components/Dialog/styles.ts index d4730a77c85a..ac3544e63c79 100644 --- a/packages/react-dev-overlay/src/internal/components/Dialog/styles.ts +++ b/packages/react-dev-overlay/src/internal/components/Dialog/styles.ts @@ -8,7 +8,7 @@ const styles = css` margin-right: auto; margin-left: auto; outline: none; - background: white; + background: var(--color-background); border-radius: var(--size-gap); box-shadow: 0 var(--size-gap-half) var(--size-gap-double) rgba(0, 0, 0, 0.25); diff --git a/packages/react-dev-overlay/src/internal/components/LeftRightDialogHeader/styles.ts b/packages/react-dev-overlay/src/internal/components/LeftRightDialogHeader/styles.ts index f7368bb31d1e..9523fe31754f 100644 --- a/packages/react-dev-overlay/src/internal/components/LeftRightDialogHeader/styles.ts +++ b/packages/react-dev-overlay/src/internal/components/LeftRightDialogHeader/styles.ts @@ -52,6 +52,8 @@ const styles = css` opacity: 0.4; transition: opacity 0.25s ease; + + color: var(--color-font); } [data-nextjs-dialog-left-right] > button:last-of-type:hover { opacity: 0.7; diff --git a/packages/react-dev-overlay/src/internal/components/Overlay/styles.tsx b/packages/react-dev-overlay/src/internal/components/Overlay/styles.tsx index c1547b3e062a..143e7ef08fd8 100644 --- a/packages/react-dev-overlay/src/internal/components/Overlay/styles.tsx +++ b/packages/react-dev-overlay/src/internal/components/Overlay/styles.tsx @@ -29,7 +29,7 @@ const styles = css` right: 0; bottom: 0; left: 0; - background-color: rgba(17, 17, 17, 0.2); + background-color: var(--color-backdrop); pointer-events: all; z-index: -1; } diff --git a/packages/react-dev-overlay/src/internal/container/BuildError.tsx b/packages/react-dev-overlay/src/internal/container/BuildError.tsx index 854082a64fe4..ddec379ca537 100644 --- a/packages/react-dev-overlay/src/internal/container/BuildError.tsx +++ b/packages/react-dev-overlay/src/internal/container/BuildError.tsx @@ -59,6 +59,6 @@ export const styles = css` } .nextjs-container-build-error-body small { - color: #757575; + color: var(--color-font); } ` diff --git a/packages/react-dev-overlay/src/internal/container/RuntimeError.tsx b/packages/react-dev-overlay/src/internal/container/RuntimeError.tsx index 84b6fe17b03c..f2fce9e83808 100644 --- a/packages/react-dev-overlay/src/internal/container/RuntimeError.tsx +++ b/packages/react-dev-overlay/src/internal/container/RuntimeError.tsx @@ -196,18 +196,17 @@ export const styles = css` margin-top: 0; margin-bottom: var(--size-gap); font-family: var(--font-stack-monospace); - font-size: var(--size-font); - color: #222; + color: var(--color-stack-h6); } [data-nextjs-call-stack-frame] > h3[data-nextjs-frame-expanded='false'] { - color: #666; + color: var(--color-stack-headline); } [data-nextjs-call-stack-frame] > div { display: flex; align-items: center; padding-left: calc(var(--size-gap) + var(--size-gap-half)); font-size: var(--size-font-small); - color: #999; + color: var(--color-stack-subline); } [data-nextjs-call-stack-frame] > div > svg { width: auto; diff --git a/packages/react-dev-overlay/src/internal/styles/Base.tsx b/packages/react-dev-overlay/src/internal/styles/Base.tsx index b48e0a25b2f9..da5cb781c9ef 100644 --- a/packages/react-dev-overlay/src/internal/styles/Base.tsx +++ b/packages/react-dev-overlay/src/internal/styles/Base.tsx @@ -17,6 +17,14 @@ export function Base() { --size-font-big: 20px; --size-font-bigger: 24px; + --color-background: white; + --color-font: #757575; + --color-backdrop: rgba(17, 17, 17, 0.2); + + --color-stack-h6: #222; + --color-stack-headline: #666; + --color-stack-subline: #999; + --color-accents-1: #808080; --color-accents-2: #222222; --color-accents-3: #404040; @@ -46,6 +54,20 @@ export function Base() { --color-ansi-bright-yellow: #ffd966; } + @media (prefers-color-scheme: dark) { + :host { + --color-background: rgb(28, 28, 30); + --color-font: white; + --color-backdrop: rgb(44, 44, 46); + + --color-stack-h6: rgb(200, 200, 204); + --color-stack-headline: rgb(99, 99, 102); + --color-stack-subline: rgba(142, 142, 147); + + --color-accents-3: rgb(118, 118, 118); + } + } + .mono { font-family: var(--font-stack-monospace); } diff --git a/packages/react-dev-overlay/src/internal/styles/CssReset.tsx b/packages/react-dev-overlay/src/internal/styles/CssReset.tsx index b2065abd9d96..eb3e2ca7a645 100644 --- a/packages/react-dev-overlay/src/internal/styles/CssReset.tsx +++ b/packages/react-dev-overlay/src/internal/styles/CssReset.tsx @@ -54,7 +54,7 @@ export function CssReset() { font-size: 16px; font-weight: 400; line-height: 1.5; - color: #212529; + color: var(--color-font); text-align: left; background-color: #fff; } From c76653f2f7c20dd9e2dd6ba561c34bf742f024c2 Mon Sep 17 00:00:00 2001 From: bri <131553872+yyuemii@users.noreply.github.com> Date: Wed, 14 Jun 2023 17:03:44 -0400 Subject: [PATCH 46/54] chore: update next/font/google font list (#49969) Ran script `update-google-fonts`, adding these fonts to `next/font/google`: - [Instrument Sans](https://fonts.google.com/specimen/Instrument+Sans) - [Noto Sans Nag Mundari](https://fonts.google.com/noto/specimen/Noto+Sans+Nag+Mundari) - [Wix Madefor Display](https://fonts.google.com/specimen/Wix+Madefor+Display) - [Wix Madefor Text](https://fonts.google.com/specimen/Wix+Madefor+Text) and updating these fonts: - [Radio Canada](https://fonts.google.com/specimen/Radio+Canada) (added `canadian-aboriginal` subset) Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- packages/font/src/google/font-data.json | 186 ++++++++++++++--- packages/font/src/google/index.ts | 260 ++++++++++++++++++------ 2 files changed, 348 insertions(+), 98 deletions(-) diff --git a/packages/font/src/google/font-data.json b/packages/font/src/google/font-data.json index b2b16f8c569e..2defc776632e 100644 --- a/packages/font/src/google/font-data.json +++ b/packages/font/src/google/font-data.json @@ -1179,6 +1179,11 @@ "styles": ["normal"], "subsets": ["cyrillic", "latin"] }, + "Bagel Fat One": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin", "latin-ext"] + }, "Bahiana": { "weights": ["400"], "styles": ["normal"], @@ -2376,11 +2381,6 @@ "styles": ["normal"], "subsets": ["latin", "latin-ext"] }, - "Coda Caption": { - "weights": ["800"], - "styles": ["normal"], - "subsets": ["latin", "latin-ext"] - }, "Codystar": { "weights": ["300", "400"], "styles": ["normal"], @@ -2751,8 +2751,16 @@ "subsets": ["khmer", "latin"] }, "Darker Grotesque": { - "weights": ["300", "400", "500", "600", "700", "800", "900"], + "weights": ["300", "400", "500", "600", "700", "800", "900", "variable"], "styles": ["normal"], + "axes": [ + { + "tag": "wght", + "min": 300, + "max": 900, + "defaultValue": 400 + } + ], "subsets": ["latin", "latin-ext", "vietnamese"] }, "Darumadrop One": { @@ -2837,6 +2845,11 @@ "latin-ext" ] }, + "Diphylleia": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin", "latin-ext"] + }, "Diplomata": { "weights": ["400"], "styles": ["normal"], @@ -3794,6 +3807,11 @@ ], "subsets": ["latin", "latin-ext"] }, + "Gasoek One": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin", "latin-ext"] + }, "Gayathri": { "weights": ["100", "400", "700"], "styles": ["normal"], @@ -3872,6 +3890,55 @@ "styles": ["normal", "italic"], "subsets": ["latin"] }, + "Geologica": { + "weights": [ + "100", + "200", + "300", + "400", + "500", + "600", + "700", + "800", + "900", + "variable" + ], + "styles": ["normal"], + "axes": [ + { + "tag": "CRSV", + "min": 0, + "max": 1, + "defaultValue": 0 + }, + { + "tag": "SHRP", + "min": 0, + "max": 100, + "defaultValue": 0 + }, + { + "tag": "slnt", + "min": -12, + "max": 0, + "defaultValue": 0 + }, + { + "tag": "wght", + "min": 100, + "max": 900, + "defaultValue": 400 + } + ], + "subsets": [ + "cyrillic", + "cyrillic-ext", + "greek", + "latin", + "latin-ext", + "vietnamese" + ] + }, "Georama": { "weights": [ "100", @@ -4083,6 +4150,11 @@ "styles": ["normal"], "subsets": ["latin", "latin-ext"] }, + "Grandiflora One": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin", "latin-ext"] + }, "Grandstander": { "weights": [ "100", @@ -4983,6 +5055,19 @@ "styles": ["normal", "italic"], "subsets": ["latin", "latin-ext", "thai", "vietnamese"] }, + "Kablammo": { + "weights": ["400", "variable"], + "styles": ["normal"], + "axes": [ + { + "tag": "MORF", + "min": 0, + "max": 60, + "defaultValue": 0 + } + ], + "subsets": ["cyrillic", "cyrillic-ext", "latin", "latin-ext", "vietnamese"] + }, "Kadwa": { "weights": ["400", "700"], "styles": ["normal"], @@ -6334,7 +6419,7 @@ "Michroma": { "weights": ["400"], "styles": ["normal"], - "subsets": ["latin"] + "subsets": ["latin", "latin-ext"] }, "Milonga": { "weights": ["400"], @@ -6424,6 +6509,11 @@ ], "subsets": ["latin", "latin-ext"] }, + "Moirai One": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin", "latin-ext"] + }, "Molengo": { "weights": ["400"], "styles": ["normal"], @@ -8553,8 +8643,33 @@ "subsets": ["latin", "latin-ext", "zanabazar-square"] }, "Noto Serif": { - "weights": ["400", "700"], + "weights": [ + "100", + "200", + "300", + "400", + "500", + "600", + "700", + "800", + "900", + "variable" + ], "styles": ["normal", "italic"], + "axes": [ + { + "tag": "wdth", + "min": 62.5, + "max": 100, + "defaultValue": 100 + }, + { + "tag": "wght", + "min": 100, + "max": 900, + "defaultValue": 400 + } + ], "subsets": [ "cyrillic", "cyrillic-ext", @@ -9435,6 +9550,11 @@ "styles": ["normal"], "subsets": ["cyrillic", "cyrillic-ext", "latin", "latin-ext"] }, + "Orbit": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin", "latin-ext"] + }, "Orbitron": { "weights": ["400", "500", "600", "700", "800", "900", "variable"], "styles": ["normal"], @@ -9640,6 +9760,11 @@ "styles": ["normal"], "subsets": ["devanagari", "latin", "latin-ext"] }, + "Palette Mosaic": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin"] + }, "Pangolin": { "weights": ["400"], "styles": ["normal"], @@ -10738,6 +10863,11 @@ "styles": ["normal"], "subsets": ["latin"] }, + "Rock 3D": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin"] + }, "Rock Salt": { "weights": ["400"], "styles": ["normal"], @@ -11298,6 +11428,11 @@ "styles": ["normal"], "subsets": ["latin", "latin-ext"] }, + "Shizuru": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin"] + }, "Shojumaru": { "weights": ["400"], "styles": ["normal"], @@ -11706,19 +11841,6 @@ "vietnamese" ] }, - "Source Sans Pro": { - "weights": ["200", "300", "400", "600", "700", "900"], - "styles": ["normal", "italic"], - "subsets": [ - "cyrillic", - "cyrillic-ext", - "greek", - "greek-ext", - "latin", - "latin-ext", - "vietnamese" - ] - }, "Source Serif 4": { "weights": [ "200", @@ -11755,18 +11877,6 @@ "vietnamese" ] }, - "Source Serif Pro": { - "weights": ["200", "300", "400", "600", "700", "900"], - "styles": ["normal", "italic"], - "subsets": [ - "cyrillic", - "cyrillic-ext", - "greek", - "latin", - "latin-ext", - "vietnamese" - ] - }, "Space Grotesk": { "weights": ["300", "400", "500", "600", "700", "variable"], "styles": ["normal"], @@ -12920,6 +13030,16 @@ "styles": ["normal"], "subsets": ["cyrillic", "latin", "latin-ext"] }, + "Yuji Hentaigana Akari": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin", "latin-ext"] + }, + "Yuji Hentaigana Akebono": { + "weights": ["400"], + "styles": ["normal"], + "subsets": ["latin", "latin-ext"] + }, "Yuji Mai": { "weights": ["400"], "styles": ["normal"], diff --git a/packages/font/src/google/index.ts b/packages/font/src/google/index.ts index 98c84b834acf..58e3f3def1a0 100644 --- a/packages/font/src/google/index.ts +++ b/packages/font/src/google/index.ts @@ -1934,6 +1934,18 @@ export declare function Bad_Script< adjustFontFallback?: boolean subsets?: Array<'cyrillic' | 'latin'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Bagel_Fat_One< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin' | 'latin-ext'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Bahiana< T extends CssVariable | undefined = undefined >(options: { @@ -4189,18 +4201,6 @@ export declare function Coda< adjustFontFallback?: boolean subsets?: Array<'latin' | 'latin-ext'> }): T extends undefined ? NextFont : NextFontWithVariable -export declare function Coda_Caption< - T extends CssVariable | undefined = undefined ->(options: { - weight: '800' | Array<'800'> - style?: 'normal' | Array<'normal'> - display?: Display - variable?: T - preload?: boolean - fallback?: string[] - adjustFontFallback?: boolean - subsets?: Array<'latin' | 'latin-ext'> -}): T extends undefined ? NextFont : NextFontWithVariable export declare function Codystar< T extends CssVariable | undefined = undefined >(options: { @@ -4897,8 +4897,8 @@ export declare function Dangrek< }): T extends undefined ? NextFont : NextFontWithVariable export declare function Darker_Grotesque< T extends CssVariable | undefined = undefined ->(options: { - weight: +>(options?: { + weight?: | '300' | '400' | '500' @@ -4906,6 +4906,7 @@ export declare function Darker_Grotesque< | '700' | '800' | '900' + | 'variable' | Array<'300' | '400' | '500' | '600' | '700' | '800' | '900'> style?: 'normal' | Array<'normal'> display?: Display @@ -5097,6 +5098,18 @@ export declare function Didact_Gothic< 'cyrillic' | 'cyrillic-ext' | 'greek' | 'greek-ext' | 'latin' | 'latin-ext' > }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Diphylleia< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin' | 'latin-ext'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Diplomata< T extends CssVariable | undefined = undefined >(options: { @@ -6826,6 +6839,18 @@ export declare function Gantari< adjustFontFallback?: boolean subsets?: Array<'latin' | 'latin-ext'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Gasoek_One< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin' | 'latin-ext'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Gayathri< T extends CssVariable | undefined = undefined >(options: { @@ -6948,6 +6973,34 @@ export declare function Geo< adjustFontFallback?: boolean subsets?: Array<'latin'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Geologica< + T extends CssVariable | undefined = undefined +>(options?: { + weight?: + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900' + | 'variable' + | Array< + '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' + > + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array< + 'cyrillic' | 'cyrillic-ext' | 'greek' | 'latin' | 'latin-ext' | 'vietnamese' + > + axes?: ('CRSV' | 'SHRP' | 'slnt')[] +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Georama< T extends CssVariable | undefined = undefined >(options?: { @@ -7330,6 +7383,18 @@ export declare function Grand_Hotel< adjustFontFallback?: boolean subsets?: Array<'latin' | 'latin-ext'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Grandiflora_One< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin' | 'latin-ext'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Grandstander< T extends CssVariable | undefined = undefined >(options?: { @@ -9091,6 +9156,21 @@ export declare function K2D< adjustFontFallback?: boolean subsets?: Array<'latin' | 'latin-ext' | 'thai' | 'vietnamese'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Kablammo< + T extends CssVariable | undefined = undefined +>(options?: { + weight?: '400' | 'variable' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array< + 'cyrillic' | 'cyrillic-ext' | 'latin' | 'latin-ext' | 'vietnamese' + > + axes?: 'MORF'[] +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Kadwa< T extends CssVariable | undefined = undefined >(options: { @@ -11623,7 +11703,7 @@ export declare function Michroma< preload?: boolean fallback?: string[] adjustFontFallback?: boolean - subsets?: Array<'latin'> + subsets?: Array<'latin' | 'latin-ext'> }): T extends undefined ? NextFont : NextFontWithVariable export declare function Milonga< T extends CssVariable | undefined = undefined @@ -11831,6 +11911,18 @@ export declare function Mohave< adjustFontFallback?: boolean subsets?: Array<'latin' | 'latin-ext'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Moirai_One< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin' | 'latin-ext'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Molengo< T extends CssVariable | undefined = undefined >(options: { @@ -15275,8 +15367,21 @@ export declare function Noto_Sans_Zanabazar_Square< }): T extends undefined ? NextFont : NextFontWithVariable export declare function Noto_Serif< T extends CssVariable | undefined = undefined ->(options: { - weight: '400' | '700' | Array<'400' | '700'> +>(options?: { + weight?: + | '100' + | '200' + | '300' + | '400' + | '500' + | '600' + | '700' + | '800' + | '900' + | 'variable' + | Array< + '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' + > style?: 'normal' | 'italic' | Array<'normal' | 'italic'> display?: Display variable?: T @@ -15292,6 +15397,7 @@ export declare function Noto_Serif< | 'latin-ext' | 'vietnamese' > + axes?: 'wdth'[] }): T extends undefined ? NextFont : NextFontWithVariable export declare function Noto_Serif_Ahom< T extends CssVariable | undefined = undefined @@ -16405,6 +16511,18 @@ export declare function Oranienbaum< adjustFontFallback?: boolean subsets?: Array<'cyrillic' | 'cyrillic-ext' | 'latin' | 'latin-ext'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Orbit< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin' | 'latin-ext'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Orbitron< T extends CssVariable | undefined = undefined >(options?: { @@ -16803,6 +16921,18 @@ export declare function Palanquin_Dark< adjustFontFallback?: boolean subsets?: Array<'devanagari' | 'latin' | 'latin-ext'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Palette_Mosaic< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Pangolin< T extends CssVariable | undefined = undefined >(options: { @@ -18572,6 +18702,18 @@ export declare function Rochester< adjustFontFallback?: boolean subsets?: Array<'latin'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Rock_3D< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Rock_Salt< T extends CssVariable | undefined = undefined >(options: { @@ -19807,6 +19949,18 @@ export declare function Shippori_Mincho_B1< adjustFontFallback?: boolean subsets?: Array<'latin' | 'latin-ext'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Shizuru< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Shojumaru< T extends CssVariable | undefined = undefined >(options: { @@ -20487,33 +20641,6 @@ export declare function Source_Sans_3< | 'vietnamese' > }): T extends undefined ? NextFont : NextFontWithVariable -export declare function Source_Sans_Pro< - T extends CssVariable | undefined = undefined ->(options: { - weight: - | '200' - | '300' - | '400' - | '600' - | '700' - | '900' - | Array<'200' | '300' | '400' | '600' | '700' | '900'> - style?: 'normal' | 'italic' | Array<'normal' | 'italic'> - display?: Display - variable?: T - preload?: boolean - fallback?: string[] - adjustFontFallback?: boolean - subsets?: Array< - | 'cyrillic' - | 'cyrillic-ext' - | 'greek' - | 'greek-ext' - | 'latin' - | 'latin-ext' - | 'vietnamese' - > -}): T extends undefined ? NextFont : NextFontWithVariable export declare function Source_Serif_4< T extends CssVariable | undefined = undefined >(options?: { @@ -20539,27 +20666,6 @@ export declare function Source_Serif_4< > axes?: 'opsz'[] }): T extends undefined ? NextFont : NextFontWithVariable -export declare function Source_Serif_Pro< - T extends CssVariable | undefined = undefined ->(options: { - weight: - | '200' - | '300' - | '400' - | '600' - | '700' - | '900' - | Array<'200' | '300' | '400' | '600' | '700' | '900'> - style?: 'normal' | 'italic' | Array<'normal' | 'italic'> - display?: Display - variable?: T - preload?: boolean - fallback?: string[] - adjustFontFallback?: boolean - subsets?: Array< - 'cyrillic' | 'cyrillic-ext' | 'greek' | 'latin' | 'latin-ext' | 'vietnamese' - > -}): T extends undefined ? NextFont : NextFontWithVariable export declare function Space_Grotesk< T extends CssVariable | undefined = undefined >(options?: { @@ -22763,6 +22869,30 @@ export declare function Yuji_Boku< adjustFontFallback?: boolean subsets?: Array<'cyrillic' | 'latin' | 'latin-ext'> }): T extends undefined ? NextFont : NextFontWithVariable +export declare function Yuji_Hentaigana_Akari< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin' | 'latin-ext'> +}): T extends undefined ? NextFont : NextFontWithVariable +export declare function Yuji_Hentaigana_Akebono< + T extends CssVariable | undefined = undefined +>(options: { + weight: '400' | Array<'400'> + style?: 'normal' | Array<'normal'> + display?: Display + variable?: T + preload?: boolean + fallback?: string[] + adjustFontFallback?: boolean + subsets?: Array<'latin' | 'latin-ext'> +}): T extends undefined ? NextFont : NextFontWithVariable export declare function Yuji_Mai< T extends CssVariable | undefined = undefined >(options: { From d492b937e216f0f193bf4d9c233424e808e049d1 Mon Sep 17 00:00:00 2001 From: mPaella <93682696+mPaella@users.noreply.github.com> Date: Wed, 14 Jun 2023 17:24:57 -0400 Subject: [PATCH 47/54] fix: Incorrect build size outputs for app dir (#50768) ### What? Fixes https://github.com/vercel/next.js/issues/50129 ### Why? The current denormalization of app dir paths doesnt account for the normalization done in `normalizeAppPath`, causing build log outputs for certain paths such as anything under a route groups to be incorrect ### How? There's 2 ways this could be fixed: 1.) Denormalize the app dir paths by reading the `app-path-routes-manifest.json` and mapping back to the original file path 2.) (what I chose to do) Normalize the keys of `appBuildManifest` ### Result App dir paths, including route groups, now have their correct output size image --------- Co-authored-by: JJ Kasper --- packages/next/src/build/utils.ts | 30 ++++++++++++++++++------------ test/e2e/app-dir/app/index.test.ts | 6 ++++++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index a47a8c83cfb8..83ae9c6f8e22 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -64,6 +64,7 @@ import { IncrementalCache } from '../server/lib/incremental-cache' import { patchFetch } from '../server/lib/patch-fetch' import { nodeFs } from '../server/lib/node-fs-methods' import * as ciEnvironment from '../telemetry/ci-info' +import { normalizeAppPath } from '../shared/lib/router/utils/app-paths' export type ROUTER_TYPE = 'pages' | 'app' @@ -110,14 +111,6 @@ function sum(a: ReadonlyArray): number { return a.reduce((size, stat) => size + stat, 0) } -function denormalizeAppPagePath(page: string): string { - // `/` is normalized to `/index` and `/index` is normalized to `/index/index` - if (page.endsWith('/index')) { - page = page.replace(/\/index$/, '') - } - return page + '/page' -} - type ComputeFilesGroup = { files: ReadonlyArray size: { @@ -773,6 +766,18 @@ export async function getJsPageSizeInKb( throw new Error('expected appBuildManifest with an "app" pageType') } + // Normalize appBuildManifest keys + if (routerType === 'app') { + pageManifest.pages = Object.entries(pageManifest.pages).reduce( + (acc: Record, [key, value]) => { + const newKey = normalizeAppPath(key) + acc[newKey] = value as string[] + return acc + }, + {} + ) + } + // If stats was not provided, then compute it again. const stats = cachedStats ?? @@ -788,10 +793,11 @@ export async function getJsPageSizeInKb( throw new Error('expected "app" manifest data with an "app" pageType') } - const pagePath = - routerType === 'pages' - ? denormalizePagePath(page) - : denormalizeAppPagePath(page) + // Denormalization is not needed for "app" dir, as we normalize the keys of appBuildManifest instead + let pagePath = page + if (routerType === 'pages') { + pagePath = denormalizePagePath(page) + } const fnFilterJs = (entry: string) => entry.endsWith('.js') diff --git a/test/e2e/app-dir/app/index.test.ts b/test/e2e/app-dir/app/index.test.ts index 271c9b654e72..cea3d44a7433 100644 --- a/test/e2e/app-dir/app/index.test.ts +++ b/test/e2e/app-dir/app/index.test.ts @@ -11,6 +11,12 @@ createNextDescribe( }, ({ next, isNextDev: isDev, isNextStart, isNextDeploy }) => { if (isNextStart) { + it('should have correct size in build output', async () => { + expect(next.cliOutput).toMatch( + /\/dashboard\/another.*? [^0]{1,} [\w]{1,}B/ + ) + }) + it('should have correct preferredRegion values in manifest', async () => { const middlewareManifest = JSON.parse( await next.readFile('.next/server/middleware-manifest.json') From f7c105d371342caa797042d51777a0ea763a4082 Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Wed, 14 Jun 2023 16:33:34 -0500 Subject: [PATCH 48/54] Adds `env` suggestions (#51312) Adds suggestions from #51063, which didn't get applied before merge. --- .../06-configuring/03-environment-variables.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/02-app/01-building-your-application/06-configuring/03-environment-variables.mdx b/docs/02-app/01-building-your-application/06-configuring/03-environment-variables.mdx index 572668e56e22..66704c4ead26 100644 --- a/docs/02-app/01-building-your-application/06-configuring/03-environment-variables.mdx +++ b/docs/02-app/01-building-your-application/06-configuring/03-environment-variables.mdx @@ -55,11 +55,11 @@ This loads `process.env.DB_HOST`, `process.env.DB_USER`, and `process.env.DB_PAS Next.js will automatically expand variables that use `$` to reference other variables e.g. `$VARIABLE` inside of your `.env*` files. This allows you to reference other secrets. For example: ```txt filename=".env" -TWITTER_USER=vercel +TWITTER_USER=nextjs TWITTER_URL=https://twitter.com/$TWITTER_USER ``` -In the above example, `process.env.TWITTER_URL` would be set to `http://twitter.com/vercel`. +In the above example, `process.env.TWITTER_URL` would be set to `https://twitter.com/nextjs`. > **Good to know**: If you need to use variable with a `$` in the actual value, it needs to be escaped e.g. `\$`. From 9a36f337da2d0b2dcf467a96d696e963275e8c09 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 14 Jun 2023 23:43:08 +0200 Subject: [PATCH 49/54] Simplify server CSS handling (#51018) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the Server CSS manifest and related logic, and use the chunkGroup CSS files instead for each entry to inject stylesheet links. Why was that manifest needed in the first place? When implementing CSS collection for RSC and nested layout initially, we collect CSS imports at each layer and create corresponding client entries. But then soon got hit by the problem of duplication and improper tree-shaking. Two layers can have the same CSS imported so we solved it in a way that "if an upper layer imports this module, skip it in child layers". Note that this is deduped by module, so we need to keep the information of "layer (entry) → CSS modules" somewhere, in a manifest. Another reason is that we create the client entry before Webpack optimizes modules, so we can inject the client entry into the same compilation. But that means the collected client modules from the server layer are not properly optimized (DCE). **This is a general issue at the moment that's not specifically related to CSS, although using that manifest to collect DCE'd info and join the original collected CSS files with that info temporarily solved it.** That's why I disabled some tests related to font CSS collection and want to improve it in a more general way. Why is that not needed anymore? Main reason is to keep a good balance between duplication and number of chunks, and delegate the decision to Webpack's splitChunks plugin. It is not possible to get to a point of 0 duplication unless we ship every CSS module as a single chunk. And since in #50406 we made the duplication better but at the chunk asset level, instead of the original module level. Prior work: #50406, #50610. --- .../next-core/js/src/entry/app-renderer.tsx | 40 +++--- packages/next/src/build/index.ts | 9 -- .../loaders/next-edge-ssr-loader/index.ts | 2 - .../loaders/next-edge-ssr-loader/render.ts | 3 - .../plugins/flight-client-entry-plugin.ts | 122 ------------------ .../webpack/plugins/flight-manifest-plugin.ts | 82 ++++++------ .../webpack/plugins/middleware-plugin.ts | 2 - packages/next/src/export/index.ts | 6 - .../src/server/app-render/action-handler.ts | 12 ++ .../next/src/server/app-render/app-render.tsx | 23 +--- .../app-render/get-css-inlined-link-tags.tsx | 52 ++------ .../app-render/get-preloadable-fonts.tsx | 30 ++--- .../app-render/get-server-css-for-entries.tsx | 18 --- packages/next/src/server/app-render/types.ts | 6 +- packages/next/src/server/base-server.ts | 6 - .../next/src/server/dev/next-dev-server.ts | 5 - packages/next/src/server/next-server.ts | 11 -- packages/next/src/server/render.tsx | 1 - packages/next/src/server/web-server.ts | 3 - packages/next/src/shared/lib/constants.ts | 2 - test/e2e/app-dir/next-font/next-font.test.ts | 23 +++- 21 files changed, 117 insertions(+), 341 deletions(-) delete mode 100644 packages/next/src/server/app-render/get-server-css-for-entries.tsx diff --git a/packages/next-swc/crates/next-core/js/src/entry/app-renderer.tsx b/packages/next-swc/crates/next-core/js/src/entry/app-renderer.tsx index e7417ca821d5..6ee374288b57 100644 --- a/packages/next-swc/crates/next-core/js/src/entry/app-renderer.tsx +++ b/packages/next-swc/crates/next-core/js/src/entry/app-renderer.tsx @@ -16,10 +16,7 @@ declare global { import type { Ipc } from '@vercel/turbopack-node/ipc/index' import type { IncomingMessage } from 'node:http' -import type { - ClientCSSReferenceManifest, - ClientReferenceManifest, -} from 'next/dist/build/webpack/plugins/flight-manifest-plugin' +import type { ClientReferenceManifest } from 'next/dist/build/webpack/plugins/flight-manifest-plugin' import type { RenderData } from 'types/turbopack' import type { RenderOpts } from 'next/dist/server/app-render/types' @@ -139,10 +136,11 @@ async function runOperation(renderData: RenderData) { } const proxyMethodsNested = ( - type: 'ssrModuleMapping' | 'clientModules' + type: 'ssrModuleMapping' | 'clientModules' | 'entryCSSFiles' ): ProxyHandler< | ClientReferenceManifest['ssrModuleMapping'] | ClientReferenceManifest['clientModules'] + | ClientReferenceManifest['entryCSSFiles'] > => { return { get(_target, key: string) { @@ -170,6 +168,14 @@ async function runOperation(renderData: RenderData) { chunks: JSON.parse(id)[1], } } + if (type === 'entryCSSFiles') { + const cssChunks = JSON.parse(key) + // TODO(WEB-856) subscribe to changes + return { + modules: [], + files: cssChunks.filter(filterAvailable).map(toPath), + } + } }, } } @@ -183,6 +189,10 @@ async function runOperation(renderData: RenderData) { {}, proxyMethodsNested('ssrModuleMapping') ) + const entryCSSFilesProxy = new Proxy( + {}, + proxyMethodsNested('entryCSSFiles') + ) return { get(_target: any, prop: string) { if (prop === 'ssrModuleMapping') { @@ -191,6 +201,9 @@ async function runOperation(renderData: RenderData) { if (prop === 'clientModules') { return clientModulesProxy } + if (prop === 'entryCSSFiles') { + return entryCSSFilesProxy + } }, } } @@ -216,24 +229,8 @@ async function runOperation(renderData: RenderData) { return needed } } - const cssImportProxyMethods = { - get(_target: any, prop: string) { - const cssChunks = JSON.parse(prop.replace(/\.js$/, '')) - // TODO(WEB-856) subscribe to changes - - // This return value is passed to proxyMethodsNested for clientModules - return cssChunks - .filter(filterAvailable) - .map(toPath) - .map((chunk: string) => JSON.stringify([chunk, [chunk]])) - }, - } const manifest: ClientReferenceManifest = new Proxy({} as any, proxyMethods()) - const serverCSSManifest: ClientCSSReferenceManifest = { - cssImports: new Proxy({} as any, cssImportProxyMethods), - cssModules: {}, - } const req: IncomingMessage = { url: renderData.originalUrl, method: renderData.method, @@ -275,7 +272,6 @@ async function runOperation(renderData: RenderData) { pages: ['page.js'], }, clientReferenceManifest: manifest, - serverCSSManifest, runtime: 'nodejs', serverComponents: true, assetPrefix: '', diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 582d92846db0..6f4bf58ea5a1 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -62,7 +62,6 @@ import { APP_PATHS_MANIFEST, APP_PATH_ROUTES_MANIFEST, APP_BUILD_MANIFEST, - FLIGHT_SERVER_CSS_MANIFEST, RSC_MODULE_TYPES, NEXT_FONT_MANIFEST, SUBRESOURCE_INTEGRITY_MANIFEST, @@ -893,14 +892,6 @@ export default async function build( SERVER_DIRECTORY, CLIENT_REFERENCE_MANIFEST + '.json' ), - path.join( - SERVER_DIRECTORY, - FLIGHT_SERVER_CSS_MANIFEST + '.js' - ), - path.join( - SERVER_DIRECTORY, - FLIGHT_SERVER_CSS_MANIFEST + '.json' - ), path.join( SERVER_DIRECTORY, SERVER_REFERENCE_MANIFEST + '.js' 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 d4539b0b1475..df43ae853700 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 @@ -151,7 +151,6 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction = const prerenderManifest = maybeJSONParse(self.__PRERENDER_MANIFEST) const reactLoadableManifest = maybeJSONParse(self.__REACT_LOADABLE_MANIFEST) const rscManifest = maybeJSONParse(self.__RSC_MANIFEST) - const rscCssManifest = maybeJSONParse(self.__RSC_CSS_MANIFEST) const rscServerManifest = maybeJSONParse(self.__RSC_SERVER_MANIFEST) const subresourceIntegrityManifest = ${ sriEnabled @@ -176,7 +175,6 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction = pagesRenderToHTML, reactLoadableManifest, clientReferenceManifest: ${isServerComponent} ? rscManifest : null, - serverCSSManifest: ${isServerComponent} ? rscCssManifest : null, serverActionsManifest: ${isServerComponent} ? rscServerManifest : null, subresourceIntegrityManifest, config: ${stringifiedConfig}, diff --git a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts index 9e4371e1ee57..a120b6502247 100644 --- a/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts +++ b/packages/next/src/build/webpack/loaders/next-edge-ssr-loader/render.ts @@ -31,7 +31,6 @@ export function getRender({ pagesRenderToHTML, clientReferenceManifest, subresourceIntegrityManifest, - serverCSSManifest, serverActionsManifest, config, buildId, @@ -53,7 +52,6 @@ export function getRender({ reactLoadableManifest: ReactLoadableManifest subresourceIntegrityManifest?: Record clientReferenceManifest?: ClientReferenceManifest - serverCSSManifest: any serverActionsManifest: any appServerMod: any config: NextConfigComplete @@ -88,7 +86,6 @@ export function getRender({ supportsDynamicHTML: true, disableOptimizedLoading: true, clientReferenceManifest, - serverCSSManifest, serverActionsManifest, }, appRenderToHTML, diff --git a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts index 67b8c48bc2d5..cf6f0d097c43 100644 --- a/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts @@ -3,7 +3,6 @@ import type { ClientComponentImports, NextFlightClientEntryLoaderOptions, } from '../loaders/next-flight-client-entry-loader' -import type { ClientCSSReferenceManifest } from './flight-manifest-plugin' import { webpack } from 'next/dist/compiled/webpack/webpack' import { stringify } from 'querystring' @@ -21,7 +20,6 @@ import { COMPILER_NAMES, EDGE_RUNTIME_WEBPACK, SERVER_REFERENCE_MANIFEST, - FLIGHT_SERVER_CSS_MANIFEST, } from '../../../shared/lib/constants' import { generateActionId, @@ -76,10 +74,6 @@ const pluginState = getProxiedPluginState({ } >, - // Manifest of CSS entry files for server/edge server. - serverCSSManifest: {} as ClientCSSReferenceManifest, - edgeServerCSSManifest: {} as ClientCSSReferenceManifest, - // Mapping of resource path to module id for server/edge server. serverModuleIds: {} as Record, edgeServerModuleIds: {} as Record, @@ -308,7 +302,6 @@ export class ClientReferenceEntryPlugin { compilation, entryName: name, clientComponentImports, - cssImports, bundlePath, absolutePagePath: entryRequest, }) @@ -475,121 +468,6 @@ export class ClientReferenceEntryPlugin { return Promise.all(addedClientActionEntryList) }) - // After optimizing all the modules, we collect the CSS that are still used - // by the certain chunk. - compilation.hooks.afterOptimizeModules.tap(PLUGIN_NAME, () => { - const cssImportsForChunk: Record> = {} - - const cssManifest = this.isEdgeServer - ? pluginState.edgeServerCSSManifest - : pluginState.serverCSSManifest - - function collectModule(entryName: string, mod: any) { - const resource = mod.resource - const modId = resource - if (modId) { - if (isCSSMod(mod)) { - cssImportsForChunk[entryName].add(modId) - } - } - } - - compilation.chunkGroups.forEach((chunkGroup: any) => { - chunkGroup.chunks.forEach((chunk: webpack.Chunk) => { - // Here we only track page chunks. - if (!chunk.name) return - if (!chunk.name.endsWith('/page')) return - - const entryName = path.join(this.appDir, '..', chunk.name) - - if (!cssImportsForChunk[entryName]) { - cssImportsForChunk[entryName] = new Set() - } - - const chunkModules = compilation.chunkGraph.getChunkModulesIterable( - chunk - ) as Iterable - for (const mod of chunkModules) { - collectModule(entryName, mod) - - const anyModule = mod as any - if (anyModule.modules) { - anyModule.modules.forEach((concatenatedMod: any) => { - collectModule(entryName, concatenatedMod) - }) - } - } - - const entryCSSInfo: Record = - cssManifest.cssModules || {} - entryCSSInfo[entryName] = [...cssImportsForChunk[entryName]] - - cssManifest.cssModules = entryCSSInfo - }) - }) - - forEachEntryModule(compilation, ({ name, entryModule }) => { - const clientEntryDependencyMap = collectClientEntryDependencyMap(name) - const tracked = new Set() - const mergedCSSimports: CssImports = {} - - for (const connection of compilation.moduleGraph.getOutgoingConnections( - entryModule - )) { - const entryDependency = connection.dependency - const entryRequest = connection.dependency.request - - // It is possible that the same entry is added multiple times in the - // connection graph. We can just skip these to speed up the process. - if (tracked.has(entryRequest)) continue - tracked.add(entryRequest) - - const { cssImports } = this.collectComponentInfoFromDependencies({ - entryRequest, - compilation, - dependency: entryDependency, - clientEntryDependencyMap, - }) - - Object.assign(mergedCSSimports, cssImports) - } - - if (!cssManifest.cssImports) cssManifest.cssImports = {} - Object.assign( - cssManifest.cssImports, - deduplicateCSSImportsForEntry(mergedCSSimports) - ) - }) - }) - - compilation.hooks.processAssets.tap( - { - name: PLUGIN_NAME, - stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH, - }, - (assets: webpack.Compilation['assets']) => { - const data: ClientCSSReferenceManifest = { - cssImports: { - ...pluginState.serverCSSManifest.cssImports, - ...pluginState.edgeServerCSSManifest.cssImports, - }, - cssModules: { - ...pluginState.serverCSSManifest.cssModules, - ...pluginState.edgeServerCSSManifest.cssModules, - }, - } - const manifest = JSON.stringify(data, null, this.dev ? 2 : undefined) - assets[`${this.assetPrefix}${FLIGHT_SERVER_CSS_MANIFEST}.json`] = - new sources.RawSource( - manifest - ) as unknown as webpack.sources.RawSource - assets[`${this.assetPrefix}${FLIGHT_SERVER_CSS_MANIFEST}.js`] = - new sources.RawSource( - `self.__RSC_CSS_MANIFEST=${JSON.stringify(manifest)}` - ) as unknown as webpack.sources.RawSource - } - ) - // Invalidate in development to trigger recompilation const invalidator = getInvalidator(compiler.outputPath) // Check if any of the entry injections need an invalidation diff --git a/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts b/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts index 7003bc3689c5..0f54539b8d01 100644 --- a/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts +++ b/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts @@ -18,7 +18,6 @@ import { getProxiedPluginState } from '../../build-context' import { traverseModules } from '../utils' import { nonNullable } from '../../../lib/non-nullable' import { WEBPACK_LAYERS } from '../../../lib/constants' -import { getClientReferenceModuleKey } from '../../../lib/client-reference' interface Options { dev: boolean @@ -69,14 +68,11 @@ export type ClientReferenceManifest = { edgeSSRModuleMapping: { [moduleId: string]: ManifestNode } -} - -export type ClientCSSReferenceManifest = { - cssImports: { - [modulePath: string]: string[] - } - cssModules?: { - [entry: string]: string[] + entryCSSFiles: { + [entry: string]: { + modules: string[] + files: string[] + } } } @@ -85,11 +81,13 @@ const PLUGIN_NAME = 'ClientReferenceManifestPlugin' export class ClientReferenceManifestPlugin { dev: Options['dev'] = false appDir: Options['appDir'] + appDirBase: string ASYNC_CLIENT_MODULES: Set constructor(options: Options) { this.dev = options.dev this.appDir = options.appDir + this.appDirBase = path.dirname(this.appDir) + path.sep this.ASYNC_CLIENT_MODULES = new Set(pluginState.ASYNC_CLIENT_MODULES) } @@ -130,6 +128,7 @@ export class ClientReferenceManifestPlugin { ssrModuleMapping: {}, edgeSSRModuleMapping: {}, clientModules: {}, + entryCSSFiles: {}, } const clientRequestsSet = new Set() @@ -172,11 +171,24 @@ export class ClientReferenceManifestPlugin { } const requiredChunks = getAppPathRequiredChunks() - const recordModule = ( - id: ModuleId, - mod: webpack.NormalModule, - chunkCSS: string[] - ) => { + let chunkEntryName: string | null = null + if (chunkGroup.name && /^app[\\/]/.test(chunkGroup.name)) { + // Absolute path without the extension + chunkEntryName = (this.appDirBase + chunkGroup.name).replace( + /[\\/]/g, + path.sep + ) + manifest.entryCSSFiles[chunkEntryName] = { + modules: [], + files: chunkGroup + .getFiles() + .filter( + (f) => !f.startsWith('static/css/pages/') && f.endsWith('.css') + ), + } + } + + const recordModule = (id: ModuleId, mod: webpack.NormalModule) => { const isCSSModule = isCSSMod(mod) // Skip all modules from the pages folder. CSS modules are a special case @@ -196,6 +208,13 @@ export class ClientReferenceManifestPlugin { return } + if (isCSSModule) { + if (chunkEntryName) { + manifest.entryCSSFiles[chunkEntryName].modules.push(resource) + } + return + } + const moduleReferences = manifest.clientModules const moduleIdMapping = manifest.ssrModuleMapping const edgeModuleIdMapping = manifest.edgeSSRModuleMapping @@ -211,26 +230,6 @@ export class ClientReferenceManifestPlugin { if (!ssrNamedModuleId.startsWith('.')) ssrNamedModuleId = `./${ssrNamedModuleId.replace(/\\/g, '/')}` - if (isCSSModule) { - const exportName = getClientReferenceModuleKey(resource, '') - if (!moduleReferences[exportName]) { - moduleReferences[exportName] = { - id: id || '', - name: 'default', - chunks: chunkCSS, - } - } else { - // It is possible that there are multiple modules with the same resource, - // e.g. extracted by mini-css-extract-plugin. In that case we need to - // merge the chunks. - moduleReferences[exportName].chunks = [ - ...new Set([...moduleReferences[exportName].chunks, ...chunkCSS]), - ] - } - - return - } - // Only apply following logic to client module requests from client entry, // or if the module is marked as client module. if ( @@ -312,25 +311,28 @@ export class ClientReferenceManifestPlugin { // TODO: Update type so that it doesn't have to be cast. ) as Iterable - const chunkCSS = [...chunk.files].filter( - (f) => !f.startsWith('static/css/pages/') && f.endsWith('.css') - ) - for (const mod of chunkModules) { const modId: string = compilation.chunkGraph.getModuleId(mod) + '' - recordModule(modId, mod, chunkCSS) + recordModule(modId, mod) // If this is a concatenation, register each child to the parent ID. // TODO: remove any const anyModule = mod as any if (anyModule.modules) { anyModule.modules.forEach((concatenatedMod: any) => { - recordModule(modId, concatenatedMod, chunkCSS) + recordModule(modId, concatenatedMod) }) } } }) + + if (chunkEntryName) { + // Make sure CSS modules are deduped + manifest.entryCSSFiles[chunkEntryName].modules = [ + ...new Set(manifest.entryCSSFiles[chunkEntryName].modules), + ] + } }) const file = 'server/' + CLIENT_REFERENCE_MANIFEST diff --git a/packages/next/src/build/webpack/plugins/middleware-plugin.ts b/packages/next/src/build/webpack/plugins/middleware-plugin.ts index 1d1466797358..709a4626f0fd 100644 --- a/packages/next/src/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/src/build/webpack/plugins/middleware-plugin.ts @@ -17,7 +17,6 @@ import { MIDDLEWARE_MANIFEST, MIDDLEWARE_REACT_LOADABLE_MANIFEST, NEXT_CLIENT_SSR_ENTRY_SUFFIX, - FLIGHT_SERVER_CSS_MANIFEST, SUBRESOURCE_INTEGRITY_MANIFEST, NEXT_FONT_MANIFEST, SERVER_REFERENCE_MANIFEST, @@ -97,7 +96,6 @@ function getEntryFiles( if (meta.edgeSSR.isServerComponent) { files.push(`server/${SERVER_REFERENCE_MANIFEST}.js`) files.push(`server/${CLIENT_REFERENCE_MANIFEST}.js`) - files.push(`server/${FLIGHT_SERVER_CSS_MANIFEST}.js`) if (opts.sriEnabled) { files.push(`server/${SUBRESOURCE_INTEGRITY_MANIFEST}.js`) } diff --git a/packages/next/src/export/index.ts b/packages/next/src/export/index.ts index e0bf0bc5c110..75c9a52348b4 100644 --- a/packages/next/src/export/index.ts +++ b/packages/next/src/export/index.ts @@ -25,7 +25,6 @@ import { EXPORT_DETAIL, EXPORT_MARKER, CLIENT_REFERENCE_MANIFEST, - FLIGHT_SERVER_CSS_MANIFEST, NEXT_FONT_MANIFEST, MIDDLEWARE_MANIFEST, PAGES_MANIFEST, @@ -486,11 +485,6 @@ export default async function exportApp( SERVER_DIRECTORY, CLIENT_REFERENCE_MANIFEST + '.json' )), - serverCSSManifest: require(join( - distDir, - SERVER_DIRECTORY, - FLIGHT_SERVER_CSS_MANIFEST + '.json' - )), serverActionsManifest: require(join( distDir, SERVER_DIRECTORY, diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index cb9ba968c1a8..dfa43ba60095 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -383,6 +383,18 @@ export async function handleAction({ } } + // actions.js + // app/page.js + // action woker1 + // appRender1 + + // app/foo/page.js + // action worker2 + // appRender + + // / -> fire action -> POST / -> appRender1 -> modId for the action file + // /foo -> fire action -> POST /foo -> appRender2 -> modId for the action file + const actionModId = serverActionsManifest[ process.env.NEXT_RUNTIME === 'edge' ? 'edge' : 'node' diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 70b89b867fdc..52a93b57d80c 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -59,7 +59,6 @@ import { createErrorHandler } from './create-error-handler' import { getShortDynamicParamType } from './get-short-dynamic-param-type' import { getSegmentParam } from './get-segment-param' import { getCssInlinedLinkTags } from './get-css-inlined-link-tags' -import { getServerCSSForEntries } from './get-server-css-for-entries' import { getPreloadableFonts } from './get-preloadable-fonts' import { getScriptNonceFromHeader } from './get-script-nonce-from-header' import { renderToString } from './render-to-string' @@ -162,7 +161,6 @@ export async function renderToHTMLOrFlight( const appUsingSizeAdjust = nextFontManifest?.appUsingSizeAdjust const clientReferenceManifest = renderOpts.clientReferenceManifest! - const serverCSSManifest = renderOpts.serverCSSManifest! const capturedErrors: Error[] = [] const allCapturedErrors: Error[] = [] @@ -358,15 +356,6 @@ export async function renderToHTMLOrFlight( let defaultRevalidate: false | undefined | number = false - // Collect all server CSS imports used by this specific entry (or entries, for parallel routes). - // Not that we can't rely on the CSS manifest because it tracks CSS imports per module, - // which can be used by multiple entries and cannot be tree-shaked in the module graph. - // More info: https://github.com/vercel/next.js/issues/41018 - const serverCSSForEntries = getServerCSSForEntries( - serverCSSManifest!, - ComponentMod.pages - ) - const assetPrefix = renderOpts.assetPrefix || '' const getAssetQueryString = (addTimestamp: boolean) => { @@ -396,9 +385,7 @@ export async function renderToHTMLOrFlight( }): Promise => { const cssHrefs = getCssInlinedLinkTags( clientReferenceManifest, - serverCSSManifest!, filePath, - serverCSSForEntries, injectedCSS ) @@ -455,9 +442,7 @@ export async function renderToHTMLOrFlight( const stylesheets: string[] = layoutOrPagePath ? getCssInlinedLinkTags( clientReferenceManifest, - serverCSSManifest!, layoutOrPagePath, - serverCSSForEntries, injectedCSSWithCurrentLayout, true ) @@ -465,9 +450,8 @@ export async function renderToHTMLOrFlight( const preloadedFontFiles = layoutOrPagePath ? getPreloadableFonts( - serverCSSManifest!, + clientReferenceManifest, nextFontManifest, - serverCSSForEntries, layoutOrPagePath, injectedFontPreloadTagsWithCurrentLayout ) @@ -1114,16 +1098,13 @@ export async function renderToHTMLOrFlight( if (layoutPath) { getCssInlinedLinkTags( clientReferenceManifest, - serverCSSManifest!, layoutPath, - serverCSSForEntries, injectedCSSWithCurrentLayout, true ) getPreloadableFonts( - serverCSSManifest!, + clientReferenceManifest, nextFontManifest, - serverCSSForEntries, layoutPath, injectedFontPreloadTagsWithCurrentLayout ) diff --git a/packages/next/src/server/app-render/get-css-inlined-link-tags.tsx b/packages/next/src/server/app-render/get-css-inlined-link-tags.tsx index d11751878cf0..7a82bb240090 100644 --- a/packages/next/src/server/app-render/get-css-inlined-link-tags.tsx +++ b/packages/next/src/server/app-render/get-css-inlined-link-tags.tsx @@ -1,59 +1,27 @@ -import { - ClientCSSReferenceManifest, - ClientReferenceManifest, -} from '../../build/webpack/plugins/flight-manifest-plugin' -import { getClientReferenceModuleKey } from '../../lib/client-reference' +import { ClientReferenceManifest } from '../../build/webpack/plugins/flight-manifest-plugin' /** * Get external stylesheet link hrefs based on server CSS manifest. */ export function getCssInlinedLinkTags( clientReferenceManifest: ClientReferenceManifest, - serverCSSManifest: ClientCSSReferenceManifest, filePath: string, - serverCSSForEntries: string[], injectedCSS: Set, collectNewCSSImports?: boolean ): string[] { - const layoutOrPageCssModules = serverCSSManifest.cssImports[filePath] - - const filePathWithoutExt = filePath.replace(/(\.[A-Za-z0-9]+)+$/, '') - - if (!layoutOrPageCssModules) { - return [] - } + const filePathWithoutExt = filePath.replace(/\.[^.]+$/, '') const chunks = new Set() - const isNotFoundPage = /(\/|\\)not-found/.test(filePathWithoutExt) - - for (const mod of layoutOrPageCssModules) { - // We only include the CSS if it's a global CSS, or it is used by this - // entrypoint (CSS files that actually affect this layer). - const isGlobalCSS = !/\.module\.(css|sass|scss)$/.test(mod) - - // For not-found pages, it will generally match all non-existing entries so - // even if `serverCSSForEntries` is empty, we still want to include the CSS. - const isImportedByEntry = - serverCSSForEntries.includes(mod) || isNotFoundPage + const entryCSSFiles = + clientReferenceManifest.entryCSSFiles[filePathWithoutExt] - if (isImportedByEntry || isGlobalCSS) { - // If the CSS is already injected by a parent layer, we don't need - // to inject it again. - if (!injectedCSS.has(mod)) { - const modData = - clientReferenceManifest.clientModules[ - getClientReferenceModuleKey(mod, '') - ] - if (modData) { - for (const chunk of modData.chunks) { - chunks.add(chunk) - // This might be a new layout, and to make it more efficient and - // not introducing another loop, we mutate the set directly. - if (collectNewCSSImports) { - injectedCSS.add(mod) - } - } + if (entryCSSFiles) { + for (const file of entryCSSFiles.files) { + if (!injectedCSS.has(file)) { + if (collectNewCSSImports) { + injectedCSS.add(file) } + chunks.add(file) } } } diff --git a/packages/next/src/server/app-render/get-preloadable-fonts.tsx b/packages/next/src/server/app-render/get-preloadable-fonts.tsx index 9fcef03af032..f4949b7a41c8 100644 --- a/packages/next/src/server/app-render/get-preloadable-fonts.tsx +++ b/packages/next/src/server/app-render/get-preloadable-fonts.tsx @@ -1,5 +1,5 @@ import { NextFontManifest } from '../../build/webpack/plugins/next-font-manifest-plugin' -import { ClientCSSReferenceManifest } from '../../build/webpack/plugins/flight-manifest-plugin' +import { ClientReferenceManifest } from '../../build/webpack/plugins/flight-manifest-plugin' /** * Get hrefs for fonts to preload @@ -9,35 +9,33 @@ import { ClientCSSReferenceManifest } from '../../build/webpack/plugins/flight-m * Returns null if there are fonts but none to preload and at least some were previously preloaded */ export function getPreloadableFonts( - serverCSSManifest: ClientCSSReferenceManifest, + clientReferenceManifest: ClientReferenceManifest, nextFontManifest: NextFontManifest | undefined, - serverCSSForEntries: string[], filePath: string | undefined, injectedFontPreloadTags: Set ): string[] | null { if (!nextFontManifest || !filePath) { return null } - const layoutOrPageCss = serverCSSManifest.cssImports[filePath] + const filepathWithoutExtension = filePath.replace(/\.[^.]+$/, '') + const entryCSS = + clientReferenceManifest.entryCSSFiles[filepathWithoutExtension] - if (!layoutOrPageCss) { + if (!entryCSS) { return null } const fontFiles = new Set() let foundFontUsage = false - for (const css of layoutOrPageCss) { - // We only include the CSS if it is used by this entrypoint. - if (serverCSSForEntries.includes(css)) { - const preloadedFontFiles = nextFontManifest.app[css] - if (preloadedFontFiles) { - foundFontUsage = true - for (const fontFile of preloadedFontFiles) { - if (!injectedFontPreloadTags.has(fontFile)) { - fontFiles.add(fontFile) - injectedFontPreloadTags.add(fontFile) - } + for (const cssModules of entryCSS.modules) { + const preloadedFontFiles = nextFontManifest.app[cssModules] + if (preloadedFontFiles) { + foundFontUsage = true + for (const fontFile of preloadedFontFiles) { + if (!injectedFontPreloadTags.has(fontFile)) { + fontFiles.add(fontFile) + injectedFontPreloadTags.add(fontFile) } } } diff --git a/packages/next/src/server/app-render/get-server-css-for-entries.tsx b/packages/next/src/server/app-render/get-server-css-for-entries.tsx deleted file mode 100644 index e5f23063b140..000000000000 --- a/packages/next/src/server/app-render/get-server-css-for-entries.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { ClientCSSReferenceManifest } from '../../build/webpack/plugins/flight-manifest-plugin' - -export function getServerCSSForEntries( - serverCSSManifest: ClientCSSReferenceManifest, - entries: string[] -) { - const css = [] - for (const entry of entries) { - const entryName = entry.replace(/\.[^.]+$/, '') - if ( - serverCSSManifest.cssModules && - serverCSSManifest.cssModules[entryName] - ) { - css.push(...serverCSSManifest.cssModules[entryName]) - } - } - return css -} diff --git a/packages/next/src/server/app-render/types.ts b/packages/next/src/server/app-render/types.ts index 7e522cc4f435..bf0abf06dd97 100644 --- a/packages/next/src/server/app-render/types.ts +++ b/packages/next/src/server/app-render/types.ts @@ -1,9 +1,6 @@ import type { LoadComponentsReturnType } from '../load-components' import type { ServerRuntime } from '../../../types' -import type { - ClientCSSReferenceManifest, - ClientReferenceManifest, -} from '../../build/webpack/plugins/flight-manifest-plugin' +import type { ClientReferenceManifest } from '../../build/webpack/plugins/flight-manifest-plugin' import type { NextFontManifest } from '../../build/webpack/plugins/next-font-manifest-plugin' import zod from 'zod' @@ -127,7 +124,6 @@ export type RenderOptsPartial = { dev?: boolean buildId: string clientReferenceManifest?: ClientReferenceManifest - serverCSSManifest?: ClientCSSReferenceManifest supportsDynamicHTML: boolean runtime?: ServerRuntime serverComponents?: boolean diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 9d3c6a523221..33c15bbce630 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -246,7 +246,6 @@ export default abstract class Server { supportsDynamicHTML?: boolean isBot?: boolean clientReferenceManifest?: ClientReferenceManifest - serverCSSManifest?: any serverActionsManifest?: any nextFontManifest?: NextFontManifest renderServerComponentData?: boolean @@ -261,7 +260,6 @@ export default abstract class Server { protected appPathRoutes?: Record protected customRoutes: CustomRoutes protected clientReferenceManifest?: ClientReferenceManifest - protected serverCSSManifest?: any protected nextFontManifest?: NextFontManifest public readonly hostname?: string public readonly port?: number @@ -286,7 +284,6 @@ export default abstract class Server { protected abstract getFontManifest(): FontManifest | undefined protected abstract getPrerenderManifest(): PrerenderManifest protected abstract getServerComponentManifest(): any - protected abstract getServerCSSManifest(): any protected abstract getNextFontManifest(): NextFontManifest | undefined protected abstract attachRequestMeta( req: BaseNextRequest, @@ -414,9 +411,6 @@ export default abstract class Server { this.clientReferenceManifest = serverComponents ? this.getServerComponentManifest() : undefined - this.serverCSSManifest = serverComponents - ? this.getServerCSSManifest() - : undefined this.nextFontManifest = this.getNextFontManifest() if (process.env.NEXT_RUNTIME !== 'edge') { diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index e1b2700770ad..e4005d372795 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -1430,10 +1430,6 @@ export default class DevServer extends Server { return undefined } - protected getServerCSSManifest() { - return undefined - } - protected getNextFontManifest() { return undefined } @@ -1747,7 +1743,6 @@ export default class DevServer extends Server { // manifest. if (!!this.appDir) { this.clientReferenceManifest = super.getServerComponentManifest() - this.serverCSSManifest = super.getServerCSSManifest() } this.nextFontManifest = super.getNextFontManifest() // before we re-evaluate a route module, we want to restore globals that might diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index be282da83a62..33cfe2c3d49e 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -44,7 +44,6 @@ import { CLIENT_REFERENCE_MANIFEST, CLIENT_PUBLIC_FILES_PATH, APP_PATHS_MANIFEST, - FLIGHT_SERVER_CSS_MANIFEST, SERVER_DIRECTORY, NEXT_FONT_MANIFEST, PHASE_PRODUCTION_BUILD, @@ -960,7 +959,6 @@ export default class NextNodeServer extends BaseServer { // object here but only updating its `clientReferenceManifest` field. // https://github.com/vercel/next.js/blob/df7cbd904c3bd85f399d1ce90680c0ecf92d2752/packages/next/server/render.tsx#L947-L952 renderOpts.clientReferenceManifest = this.clientReferenceManifest - renderOpts.serverCSSManifest = this.serverCSSManifest renderOpts.nextFontManifest = this.nextFontManifest if (this.hasAppDir && renderOpts.isAppPath) { @@ -1169,15 +1167,6 @@ export default class NextNodeServer extends BaseServer { )) } - protected getServerCSSManifest() { - if (!this.hasAppDir) return undefined - return require(join( - this.distDir, - 'server', - FLIGHT_SERVER_CSS_MANIFEST + '.json' - )) - } - protected getNextFontManifest() { return require(join(this.distDir, 'server', `${NEXT_FONT_MANIFEST}.json`)) } diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index 52a285e2d419..be626f326d01 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -249,7 +249,6 @@ export type RenderOptsPartial = { resolvedUrl?: string resolvedAsPath?: string clientReferenceManifest?: ClientReferenceManifest - serverCSSManifest?: any nextFontManifest?: NextFontManifest distDir?: string locale?: string diff --git a/packages/next/src/server/web-server.ts b/packages/next/src/server/web-server.ts index 1ff5fb3d6f59..2c1f80e3221c 100644 --- a/packages/next/src/server/web-server.ts +++ b/packages/next/src/server/web-server.ts @@ -168,9 +168,6 @@ export default class NextWebServer extends BaseServer { return this.serverOptions.webServerConfig.extendRenderOpts .clientReferenceManifest } - protected getServerCSSManifest() { - return this.serverOptions.webServerConfig.extendRenderOpts.serverCSSManifest - } protected getNextFontManifest() { return this.serverOptions.webServerConfig.extendRenderOpts.nextFontManifest diff --git a/packages/next/src/shared/lib/constants.ts b/packages/next/src/shared/lib/constants.ts index 82845e267d88..fa1fcd0e7450 100644 --- a/packages/next/src/shared/lib/constants.ts +++ b/packages/next/src/shared/lib/constants.ts @@ -56,8 +56,6 @@ export const NEXT_CLIENT_SSR_ENTRY_SUFFIX = '.__sc_client__' // server/client-reference-manifest export const CLIENT_REFERENCE_MANIFEST = 'client-reference-manifest' -// server/flight-server-css-manifest -export const FLIGHT_SERVER_CSS_MANIFEST = 'flight-server-css-manifest' // server/server-reference-manifest export const SERVER_REFERENCE_MANIFEST = 'server-reference-manifest' // server/middleware-build-manifest.js diff --git a/test/e2e/app-dir/next-font/next-font.test.ts b/test/e2e/app-dir/next-font/next-font.test.ts index b80e98f08b74..4320d6b556ea 100644 --- a/test/e2e/app-dir/next-font/next-font.test.ts +++ b/test/e2e/app-dir/next-font/next-font.test.ts @@ -2,6 +2,20 @@ import { createNextDescribe, FileRef } from 'e2e-utils' import { getRedboxSource, hasRedbox } from 'next-test-utils' import { join } from 'path' +// TODO-APP: due to a current implementation limitation, we don't have proper tree +// shaking when across the server/client boundaries (e.g. all referenced client +// modules by a server module will be included in the bundle even it's not actually +// used by that server module). +// This is a known limitation of flight-client-entry-plugin and we will improve +// this in the future. +// TODO-APP: After the above issue is fixed, we should change this function to +// be strictly equal instead: `expect(arr).toEqual(expected)`. +function containObjects(arr: any[], expected: any[]) { + for (const o of expected) { + expect(arr).toContainEqual(o) + } +} + const getAttrs = (elems: Cheerio) => Array.from(elems) .map((elem) => elem.attribs) @@ -231,8 +245,7 @@ describe.each([['app'], ['app-old']])('%s', (fixture: string) => { expect($('link[rel="preconnect"]').length).toBe(0) // From root layout - expect($('link[as="font"]').length).toBe(3) - expect(getAttrs($('link[as="font"]'))).toEqual([ + containObjects(getAttrs($('link[as="font"]')), [ { as: 'font', crossorigin: '', @@ -264,7 +277,7 @@ describe.each([['app'], ['app-old']])('%s', (fixture: string) => { expect($('link[rel="preconnect"]').length).toBe(0) // From root layout - expect(getAttrs($('link[as="font"]'))).toEqual([ + containObjects(getAttrs($('link[as="font"]')), [ { as: 'font', crossorigin: '', @@ -296,7 +309,7 @@ describe.each([['app'], ['app-old']])('%s', (fixture: string) => { expect($('link[rel="preconnect"]').length).toBe(0) // From root layout - expect(getAttrs($('link[as="font"]'))).toEqual([ + containObjects(getAttrs($('link[as="font"]')), [ { as: 'font', crossorigin: '', @@ -321,7 +334,7 @@ describe.each([['app'], ['app-old']])('%s', (fixture: string) => { expect($('link[rel="preconnect"]').length).toBe(0) // From root layout - expect(getAttrs($('link[as="font"]'))).toEqual([ + containObjects(getAttrs($('link[as="font"]')), [ { as: 'font', crossorigin: '', From 3ec70a26d3b44e9b6ee8f3a004e2c32aa25b5860 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 14 Jun 2023 14:44:23 -0700 Subject: [PATCH 50/54] Skip cargo bench job on release (#51311) This fails when run during a release since the new version isn't published yet x-ref: https://github.com/vercel/next.js/actions/runs/5266469610/jobs/9520466217 --- .github/workflows/build_and_test.yml | 1 + .github/workflows/build_reusable.yml | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 7fb1db8eb03e..06d4e666de5b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -94,6 +94,7 @@ jobs: uses: ./.github/workflows/build_reusable.yml with: skipForDocsOnly: 'yes' + skipForRelease: 'yes' needsRust: 'yes' skipNativeBuild: 'yes' afterBuild: xvfb-run turbo run test-cargo-bench diff --git a/.github/workflows/build_reusable.yml b/.github/workflows/build_reusable.yml index 67c277f781b3..0a3a2821b3ce 100644 --- a/.github/workflows/build_reusable.yml +++ b/.github/workflows/build_reusable.yml @@ -19,6 +19,10 @@ on: required: false description: 'skip for docs only changes' type: string + skipForRelease: + required: false + description: 'skip for release' + type: string nodeVersion: required: false description: 'version of Node.js to use' @@ -103,6 +107,15 @@ jobs: name: check docs only change id: docs-change + - id: is-release + run: | + if [[ $(node ./scripts/check-is-release.js 2> /dev/null || :) = v* ]]; + then + echo "IS_RELEASE=yes" >> $GITHUB_OUTPUT + else + echo "IS_RELEASE=nope" >> $GITHUB_OUTPUT + fi + # normalize versions before build-native for better cache hits - run: node scripts/normalize-version-bump.js name: normalize versions @@ -138,7 +151,7 @@ jobs: - run: turbo run get-test-timings -- --build ${{ github.sha }} - run: /bin/bash -c "${{ inputs.afterBuild }}" - if: ${{inputs.skipForDocsOnly != 'yes' || steps.docs-change.outputs.DOCS_CHANGE == 'nope'}} + if: ${{(inputs.skipForDocsOnly != 'yes' || steps.docs-change.outputs.DOCS_CHANGE == 'nope') && (inputs.skipForRelease != 'yes' || steps.is-release.outputs.IS_RELEASE == 'nope')}} - name: Upload artifact uses: actions/upload-artifact@v3 From 323e335b54aebccc0e20346fd51e1295c8c7e688 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 14 Jun 2023 14:48:20 -0700 Subject: [PATCH 51/54] Ensure swc is copied for release stats (#51315) x-ref: https://github.com/vercel/next.js/actions/runs/5266469399/jobs/9520655185 --- .github/workflows/build_and_deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 657fa1386bbb..1f035ad246e7 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -472,6 +472,8 @@ jobs: name: next-swc-binaries path: packages/next-swc/native + - run: cp -r packages/next-swc/native .github/actions/next-stats-action/native + - run: ./scripts/release-stats.sh - uses: ./.github/actions/next-stats-action env: From 4aef2b42e40bac291932ea87787f03696c745e67 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 14 Jun 2023 14:50:05 -0700 Subject: [PATCH 52/54] Update flakey deploy tests (#51314) x-ref: https://github.com/vercel/next.js/actions/runs/5266469399/jobs/9520655301 --- test/e2e/app-dir/actions/app-action.test.ts | 165 +++++++++++--------- 1 file changed, 88 insertions(+), 77 deletions(-) diff --git a/test/e2e/app-dir/actions/app-action.test.ts b/test/e2e/app-dir/actions/app-action.test.ts index 0e0836012060..9c28c75dde93 100644 --- a/test/e2e/app-dir/actions/app-action.test.ts +++ b/test/e2e/app-dir/actions/app-action.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable jest/no-standalone-expect */ import { createNextDescribe } from 'e2e-utils' import { check } from 'next-test-utils' import { Request } from 'playwright-chromium' @@ -98,7 +99,7 @@ createNextDescribe( it('should support setting cookies in route handlers with the correct overrides', async () => { const res = await next.fetch('/handler') - const setCookieHeader = res.headers.get('set-cookie') as string[] + const setCookieHeader = res.headers.get('set-cookie') as any as string[] expect(setCookieHeader).toContain('bar=bar2; Path=/') expect(setCookieHeader).toContain('baz=baz2; Path=/') expect(setCookieHeader).toContain('foo=foo1; Path=/') @@ -382,7 +383,10 @@ createNextDescribe( }, 'https://example.com/') }) - it('should handle revalidatePath', async () => { + // TODO: investigate flakey behavior on deploy + const skipDeploy = (global as any).isNextDeploy ? it.skip : it + + skipDeploy('should handle revalidatePath', async () => { const browser = await next.browser('/revalidate') const randomNumber = await browser.elementByCss('#random-number').text() const justPutIt = await browser.elementByCss('#justputit').text() @@ -407,8 +411,7 @@ createNextDescribe( }, 'success') }) - // TODO: investigate flakiness when deployed - it.skip('should handle revalidateTag', async () => { + skipDeploy('should handle revalidateTag', async () => { const browser = await next.browser('/revalidate') const randomNumber = await browser.elementByCss('#random-number').text() const justPutIt = await browser.elementByCss('#justputit').text() @@ -433,7 +436,7 @@ createNextDescribe( }, 'success') }) - it('should handle revalidateTag + redirect', async () => { + skipDeploy('should handle revalidateTag + redirect', async () => { const browser = await next.browser('/revalidate') const randomNumber = await browser.elementByCss('#random-number').text() const justPutIt = await browser.elementByCss('#justputit').text() @@ -458,37 +461,42 @@ createNextDescribe( }, 'success') }) - it('should store revalidation data in the prefetch cache', async () => { - const browser = await next.browser('/revalidate') - const justPutIt = await browser.elementByCss('#justputit').text() - await browser.elementByCss('#revalidate-justputit').click() - - // TODO: investigate flakiness when deployed - if (!isNextDeploy) { - await check(async () => { - const newJustPutIt = await browser.elementByCss('#justputit').text() - expect(newJustPutIt).not.toBe(justPutIt) - return 'success' - }, 'success') - } + skipDeploy( + 'should store revalidation data in the prefetch cache', + async () => { + const browser = await next.browser('/revalidate') + const justPutIt = await browser.elementByCss('#justputit').text() + await browser.elementByCss('#revalidate-justputit').click() + + // TODO: investigate flakiness when deployed + if (!isNextDeploy) { + await check(async () => { + const newJustPutIt = await browser + .elementByCss('#justputit') + .text() + expect(newJustPutIt).not.toBe(justPutIt) + return 'success' + }, 'success') + } - const newJustPutIt = await browser.elementByCss('#justputit').text() + const newJustPutIt = await browser.elementByCss('#justputit').text() - await browser - .elementByCss('#navigate-client') - .click() - .waitForElementByCss('#inc') - await browser - .elementByCss('#navigate-revalidate') - .click() - .waitForElementByCss('#revalidate-justputit') + await browser + .elementByCss('#navigate-client') + .click() + .waitForElementByCss('#inc') + await browser + .elementByCss('#navigate-revalidate') + .click() + .waitForElementByCss('#revalidate-justputit') - const newJustPutIt2 = await browser.elementByCss('#justputit').text() + const newJustPutIt2 = await browser.elementByCss('#justputit').text() - expect(newJustPutIt).toEqual(newJustPutIt2) - }) + expect(newJustPutIt).toEqual(newJustPutIt2) + } + ) - it('should revalidate when cookies.set is called', async () => { + skipDeploy('should revalidate when cookies.set is called', async () => { const browser = await next.browser('/revalidate') const randomNumber = await browser.elementByCss('#random-cookie').text() @@ -503,64 +511,67 @@ createNextDescribe( }, 'success') }) - it('should revalidate when cookies.set is called in a client action', async () => { - const browser = await next.browser('/revalidate') - await browser.refresh() + skipDeploy( + 'should revalidate when cookies.set is called in a client action', + async () => { + const browser = await next.browser('/revalidate') + await browser.refresh() - let randomCookie - await check(async () => { - randomCookie = JSON.parse( - await browser.elementByCss('#random-cookie').text() - ).value - return randomCookie ? 'success' : 'failure' - }, 'success') + let randomCookie + await check(async () => { + randomCookie = JSON.parse( + await browser.elementByCss('#random-cookie').text() + ).value + return randomCookie ? 'success' : 'failure' + }, 'success') - console.log(123, await browser.elementByCss('body').text()) + console.log(123, await browser.elementByCss('body').text()) - await browser.elementByCss('#another').click() - await check(async () => { - return browser.elementByCss('#title').text() - }, 'another route') + await browser.elementByCss('#another').click() + await check(async () => { + return browser.elementByCss('#title').text() + }, 'another route') - const newRandomCookie = JSON.parse( - await browser.elementByCss('#random-cookie').text() - ).value + const newRandomCookie = JSON.parse( + await browser.elementByCss('#random-cookie').text() + ).value - console.log(456, await browser.elementByCss('body').text()) + console.log(456, await browser.elementByCss('body').text()) - // Should be the same value - expect(randomCookie).toEqual(newRandomCookie) + // Should be the same value + expect(randomCookie).toEqual(newRandomCookie) - await browser.elementByCss('#back').click() + await browser.elementByCss('#back').click() - // Modify the cookie - await browser.elementByCss('#set-cookie').click() + // Modify the cookie + await browser.elementByCss('#set-cookie').click() - // Should be different - let revalidatedRandomCookie - await check(async () => { - revalidatedRandomCookie = JSON.parse( - await browser.elementByCss('#random-cookie').text() - ).value - return randomCookie !== revalidatedRandomCookie - ? 'success' - : 'failure' - }, 'success') + // Should be different + let revalidatedRandomCookie + await check(async () => { + revalidatedRandomCookie = JSON.parse( + await browser.elementByCss('#random-cookie').text() + ).value + return randomCookie !== revalidatedRandomCookie + ? 'success' + : 'failure' + }, 'success') - await browser.elementByCss('#another').click() + await browser.elementByCss('#another').click() - // The other page should be revalidated too - await check(async () => { - const newRandomCookie = await JSON.parse( - await browser.elementByCss('#random-cookie').text() - ).value - return revalidatedRandomCookie === newRandomCookie - ? 'success' - : 'failure' - }, 'success') - }) + // The other page should be revalidated too + await check(async () => { + const newRandomCookie = await JSON.parse( + await browser.elementByCss('#random-cookie').text() + ).value + return revalidatedRandomCookie === newRandomCookie + ? 'success' + : 'failure' + }, 'success') + } + ) - it.each(['tag', 'path'])( + skipDeploy.each(['tag', 'path'])( 'should invalidate client cache when %s is revalidated', async (type) => { const browser = await next.browser('/revalidate') From 40ce621901343b4a8f830d47a7ec883d1dba39a6 Mon Sep 17 00:00:00 2001 From: vercel-release-bot Date: Wed, 14 Jun 2023 21:54:03 +0000 Subject: [PATCH 53/54] v13.4.6-canary.5 --- 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 7f5c0d1e38e1..25ff32c3b078 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.4.6-canary.4" + "version": "13.4.6-canary.5" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 68fd7850701b..549e6a504c21 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.6-canary.4", + "version": "13.4.6-canary.5", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 4b0ec080b2ea..7737fb64e42e 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.6-canary.4", + "version": "13.4.6-canary.5", "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.6-canary.4", + "@next/eslint-plugin-next": "13.4.6-canary.5", "@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 40bbc477cb22..a51599c28c11 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.6-canary.4", + "version": "13.4.6-canary.5", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index e610eddc35e2..4d6267c53537 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.4.6-canary.4", + "version": "13.4.6-canary.5", "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 abe44bb20259..a6373494f9e8 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.6-canary.4", + "version": "13.4.6-canary.5", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index ad0dfc209761..ba90cd0b323d 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.4.6-canary.4", + "version": "13.4.6-canary.5", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 81529aa8178e..956af9587d4a 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.4.6-canary.4", + "version": "13.4.6-canary.5", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index a30e07aca730..7fd932a07c46 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.4.6-canary.4", + "version": "13.4.6-canary.5", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 5811d858823e..477cd6caa2aa 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.6-canary.4", + "version": "13.4.6-canary.5", "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 1dd77a3de843..17648ed7eb03 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.6-canary.4", + "version": "13.4.6-canary.5", "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 e34cd929c369..6b6aefe9d155 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.6-canary.4", + "version": "13.4.6-canary.5", "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 ca3c3ec24f06..532752bd1572 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.4.6-canary.4", + "version": "13.4.6-canary.5", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index 5d04eccd810e..ee60d740c434 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.4.6-canary.4", + "version": "13.4.6-canary.5", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -83,7 +83,7 @@ ] }, "dependencies": { - "@next/env": "13.4.6-canary.4", + "@next/env": "13.4.6-canary.5", "@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.6-canary.4", - "@next/polyfill-nomodule": "13.4.6-canary.4", - "@next/react-dev-overlay": "13.4.6-canary.4", - "@next/react-refresh-utils": "13.4.6-canary.4", - "@next/swc": "13.4.6-canary.4", + "@next/polyfill-module": "13.4.6-canary.5", + "@next/polyfill-nomodule": "13.4.6-canary.5", + "@next/react-dev-overlay": "13.4.6-canary.5", + "@next/react-refresh-utils": "13.4.6-canary.5", + "@next/swc": "13.4.6-canary.5", "@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 2a355ed5f483..989a38c7ee46 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.6-canary.4", + "version": "13.4.6-canary.5", "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 70e962f27cbf..ee8f5b779296 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.6-canary.4", + "version": "13.4.6-canary.5", "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 ed387495a895..2358b7f73b22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -426,7 +426,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.4.6-canary.4 + '@next/eslint-plugin-next': 13.4.6-canary.5 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint: ^7.23.0 || ^8.0.0 @@ -503,12 +503,12 @@ importers: '@jest/types': 29.5.0 '@napi-rs/cli': 2.14.7 '@napi-rs/triples': 1.1.0 - '@next/env': 13.4.6-canary.4 - '@next/polyfill-module': 13.4.6-canary.4 - '@next/polyfill-nomodule': 13.4.6-canary.4 - '@next/react-dev-overlay': 13.4.6-canary.4 - '@next/react-refresh-utils': 13.4.6-canary.4 - '@next/swc': 13.4.6-canary.4 + '@next/env': 13.4.6-canary.5 + '@next/polyfill-module': 13.4.6-canary.5 + '@next/polyfill-nomodule': 13.4.6-canary.5 + '@next/react-dev-overlay': 13.4.6-canary.5 + '@next/react-refresh-utils': 13.4.6-canary.5 + '@next/swc': 13.4.6-canary.5 '@opentelemetry/api': 1.4.1 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.5.1 From 115b6ad953cc2873ab20beb9d71b1b5eaa503950 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 14 Jun 2023 16:33:39 -0700 Subject: [PATCH 54/54] Allow configuring analyzerMode in bundle-analyzer (#47468) It's valid to want to output JSON instead of HTML for the bundle analyzer so we can expose this specific config the same as `openAnalyzer`. cc @mknichel --- packages/next-bundle-analyzer/index.d.ts | 9 +++++---- packages/next-bundle-analyzer/index.js | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/next-bundle-analyzer/index.d.ts b/packages/next-bundle-analyzer/index.d.ts index 5a97ee9b5be4..d76b617c1f4f 100644 --- a/packages/next-bundle-analyzer/index.d.ts +++ b/packages/next-bundle-analyzer/index.d.ts @@ -1,8 +1,9 @@ import type { NextConfig } from 'next' -declare const NextBundleAnalyzer = - (options?: { enabled?: boolean; openAnalyzer?: boolean }) => - (config?: NextConfig) => - NextConfig +declare function NextBundleAnalyzer(options?: { + enabled?: boolean + openAnalyzer?: boolean + analyzerMode?: 'json' | 'static' +}): (config?: NextConfig) => NextConfig export = NextBundleAnalyzer diff --git a/packages/next-bundle-analyzer/index.js b/packages/next-bundle-analyzer/index.js index ddcdbf1cff73..e44e2ccf5c82 100644 --- a/packages/next-bundle-analyzer/index.js +++ b/packages/next-bundle-analyzer/index.js @@ -1,5 +1,5 @@ module.exports = - ({ enabled = true, openAnalyzer = true } = {}) => + ({ enabled = true, openAnalyzer, analyzerMode } = {}) => (nextConfig = {}) => { return Object.assign({}, nextConfig, { webpack(config, options) { @@ -7,7 +7,7 @@ module.exports = const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') config.plugins.push( new BundleAnalyzerPlugin({ - analyzerMode: 'static', + analyzerMode: analyzerMode || 'static', openAnalyzer, reportFilename: !options.nextRuntime ? `./analyze/client.html`