diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 5385a03bcada..3ad8cc36f014 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -297,25 +297,24 @@ jobs: timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: npx @replayio/playwright install chromium - if: ${{needs.build.outputs.docsChange == 'nope'}} - + # - run: npx @replayio/playwright install chromium + # if: ${{needs.build.outputs.docsChange == 'nope'}} - run: node run-tests.js --type development --timings -g ${{ matrix.group }}/2 name: Run test/development if: ${{needs.build.outputs.docsChange == 'nope'}} - env: - RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testDev / Group ${{ matrix.group }} - RECORD_ALL_CONTENT: 1 - RECORD_REPLAY: 1 - RECORD_REPLAY_TEST_METRICS: 1 - RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} + # env: + # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testDev / Group ${{ matrix.group }} + # RECORD_ALL_CONTENT: 1 + # RECORD_REPLAY: 1 + # RECORD_REPLAY_TEST_METRICS: 1 + # RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} - - uses: replayio/action-upload@v0.4.5 - if: always() - with: - api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti - public: true - filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} + # - uses: replayio/action-upload@v0.4.5 + # if: always() + # with: + # api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti + # public: true + # filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} - name: Upload test trace if: always() @@ -375,26 +374,25 @@ jobs: timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: npx @replayio/playwright install chromium - if: ${{needs.build.outputs.docsChange == 'nope'}} - + # - run: npx @replayio/playwright install chromium + # if: ${{needs.build.outputs.docsChange == 'nope'}} - run: node run-tests.js --type e2e --timings -g ${{ matrix.group }}/3 name: Run test/e2e (dev) if: ${{needs.build.outputs.docsChange == 'nope'}} env: - RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testDevE2E / Group ${{ matrix.group }} / Node ${{ matrix.node }} - RECORD_ALL_CONTENT: 1 - RECORD_REPLAY: 1 NEXT_TEST_MODE: dev - RECORD_REPLAY_TEST_METRICS: 1 - RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} - - - uses: replayio/action-upload@v0.4.5 - if: always() - with: - api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti - public: true - filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} + # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testDevE2E / Group ${{ matrix.group }} / Node ${{ matrix.node }} + # RECORD_ALL_CONTENT: 1 + # RECORD_REPLAY: 1 + # RECORD_REPLAY_TEST_METRICS: 1 + # RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} + + # - uses: replayio/action-upload@v0.4.5 + # if: always() + # with: + # api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti + # public: true + # filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} - name: Upload test trace if: always() @@ -453,25 +451,24 @@ jobs: timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: npx @replayio/playwright install chromium - if: ${{needs.build.outputs.docsChange == 'nope'}} - + # - run: npx @replayio/playwright install chromium + # if: ${{needs.build.outputs.docsChange == 'nope'}} - run: node run-tests.js --type production --timings -g ${{ matrix.group }}/2 name: Run test/production if: ${{needs.build.outputs.docsChange == 'nope'}} - env: - RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testProd / Group ${{ matrix.group }} / Node ${{ matrix.node }} - RECORD_ALL_CONTENT: 1 - RECORD_REPLAY: 1 - RECORD_REPLAY_TEST_METRICS: 1 - RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} + # env: + # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testProd / Group ${{ matrix.group }} / Node ${{ matrix.node }} + # RECORD_ALL_CONTENT: 1 + # RECORD_REPLAY: 1 + # RECORD_REPLAY_TEST_METRICS: 1 + # RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} - - uses: replayio/action-upload@v0.4.5 - if: always() - with: - api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti - public: true - filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} + # - uses: replayio/action-upload@v0.4.5 + # if: always() + # with: + # api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti + # public: true + # filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} testProdE2E: name: Test Production (E2E) @@ -521,26 +518,25 @@ jobs: timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: npx @replayio/playwright install chromium - if: ${{needs.build.outputs.docsChange == 'nope'}} - + # - run: npx @replayio/playwright install chromium + # if: ${{needs.build.outputs.docsChange == 'nope'}} - run: node run-tests.js --type e2e --timings -g ${{ matrix.group }}/3 name: Run test/e2e (production) if: ${{needs.build.outputs.docsChange == 'nope'}} env: - RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testProdE2E / Group ${{ matrix.group }} / Node ${{ matrix.node }} - RECORD_ALL_CONTENT: 1 - RECORD_REPLAY: 1 NEXT_TEST_MODE: start - RECORD_REPLAY_TEST_METRICS: 1 - RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} - - - uses: replayio/action-upload@v0.4.5 - if: always() - with: - api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti - public: true - filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} + # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testProdE2E / Group ${{ matrix.group }} / Node ${{ matrix.node }} + # RECORD_ALL_CONTENT: 1 + # RECORD_REPLAY: 1 + # RECORD_REPLAY_TEST_METRICS: 1 + # RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} + + # - uses: replayio/action-upload@v0.4.5 + # if: always() + # with: + # api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti + # public: true + # filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} testIntegration: name: Test Integration @@ -611,24 +607,23 @@ jobs: timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: npx @replayio/playwright install chromium - if: ${{needs.build.outputs.docsChange == 'nope'}} - + # - run: npx @replayio/playwright install chromium + # if: ${{needs.build.outputs.docsChange == 'nope'}} - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/20 if: ${{needs.build.outputs.docsChange == 'nope'}} - env: - RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testIntegration / Group ${{ matrix.group }} - RECORD_ALL_CONTENT: 1 - RECORD_REPLAY: 1 - RECORD_REPLAY_TEST_METRICS: 1 - RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} - - - uses: replayio/action-upload@v0.4.5 - if: always() - with: - api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti - public: true - filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} + # env: + # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testIntegration / Group ${{ matrix.group }} + # RECORD_ALL_CONTENT: 1 + # RECORD_REPLAY: 1 + # RECORD_REPLAY_TEST_METRICS: 1 + # RECORD_REPLAY_WEBHOOK_URL: ${{ secrets.RECORD_REPLAY_WEBHOOK_URL }} + + # - uses: replayio/action-upload@v0.4.5 + # if: always() + # with: + # api-key: rwk_iKsQnEoQwKd31WAJxgN9ARPFuAlyXlVrDH4uhYpRnti + # public: true + # filter: ${{ 'function($v) { $v.metadata.test.result = "failed" }' }} - name: Upload test trace if: always() diff --git a/docs/api-reference/next/script.md b/docs/api-reference/next/script.md index a8f95a41c077..b28a3903af2d 100644 --- a/docs/api-reference/next/script.md +++ b/docs/api-reference/next/script.md @@ -37,7 +37,7 @@ Here's a summary of the props available for the Script Component: | Prop | Example | Values | Required | | ----------------------- | --------------------------------- | -------- | ------------------------------------- | | [`src`](#src) | `src="http://example.com/script"` | String | Required unless inline script is used | -| [`strategy`](#strategy) | `strategy="lazyOnload` | String | Optional | +| [`strategy`](#strategy) | `strategy="lazyOnload"` | String | Optional | | [`onLoad`](#onload) | `onLoad={onLoadFunc}` | Function | Optional | | [`onReady`](#onReady) | `onReady={onReadyFunc}` | Function | Optional | | [`onError`](#onerror) | `onError={onErrorFunc}` | Function | Optional | diff --git a/docs/api-reference/next/server.md b/docs/api-reference/next/server.md index e9987ae75f81..6f2fe46acfe4 100644 --- a/docs/api-reference/next/server.md +++ b/docs/api-reference/next/server.md @@ -56,7 +56,7 @@ The `waitUntil()` method can be used to prolong the execution of the function if import { NextResponse } from 'next/server' import type { NextFetchEvent, NextRequest } from 'next/server' -export async function middleware(req: NextRequest, event: NextFetchEvent) { +export function middleware(req: NextRequest, event: NextFetchEvent) { event.waitUntil( fetch('https://my-analytics-platform.com', { method: 'POST', diff --git a/docs/basic-features/static-file-serving.md b/docs/basic-features/static-file-serving.md index c4234b7aa2f2..a479361ec832 100644 --- a/docs/basic-features/static-file-serving.md +++ b/docs/basic-features/static-file-serving.md @@ -22,7 +22,7 @@ export default Avatar This folder is also useful for `robots.txt`, `favicon.ico`, Google Site Verification, and any other static files (including `.html`)! -> **Note**: Don't name the `public` directory anything else. The name cannot be changed and is the only directory used to serve static assets. +> **Note**: Be sure the directory is named `public`. The name cannot be changed and is the only directory used to serve static assets. > **Note**: Be sure to not have a static file with the same name as a file in the `pages/` directory, as this will result in an error. > diff --git a/errors/node-module-in-edge-runtime.md b/errors/node-module-in-edge-runtime.md index ea128326a41b..cd7a38fd809a 100644 --- a/errors/node-module-in-edge-runtime.md +++ b/errors/node-module-in-edge-runtime.md @@ -16,3 +16,4 @@ For example, you might replace the Node.js `crypto` module with the [Web Crypto - [Edge Runtime Supported APIs](https://nextjs.org/docs/api-reference/edge-runtime) - [Next.js Middleware](https://nextjs.org/docs/advanced-features/middleware) +- [JWT Example](https://github.com/vercel/examples/tree/main/edge-functions/jwt-authentication) diff --git a/examples/with-msw/package.json b/examples/with-msw/package.json index 5b9b20b7b4b4..131fad7ab032 100644 --- a/examples/with-msw/package.json +++ b/examples/with-msw/package.json @@ -6,7 +6,7 @@ "start": "next start" }, "dependencies": { - "msw": "0.47.3", + "msw": "^0.49.0", "next": "latest", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/examples/with-passport/package.json b/examples/with-passport/package.json index 083feeb81e76..2d01f8c0bb4a 100644 --- a/examples/with-passport/package.json +++ b/examples/with-passport/package.json @@ -10,11 +10,11 @@ "cookie": "0.4.1", "next": "latest", "next-connect": "0.8.1", - "passport": "0.4.1", - "passport-local": "1.0.0", + "passport": "^0.4.1", + "passport-local": "^1.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "swr": "^0.5.5", + "swr": "^1.3.0", "uuid": "8.3.1" } } diff --git a/examples/with-redux-wrapper/package.json b/examples/with-redux-wrapper/package.json index fcafedde23b7..03d3411794e9 100644 --- a/examples/with-redux-wrapper/package.json +++ b/examples/with-redux-wrapper/package.json @@ -6,7 +6,7 @@ "start": "next start" }, "dependencies": { - "next": "9.4.1", + "next": "latest", "next-redux-wrapper": "^7.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/with-styled-components-babel/pages/_app.tsx b/examples/with-styled-components-babel/pages/_app.tsx index 75b23189d4b1..0f4c9ce68e0f 100644 --- a/examples/with-styled-components-babel/pages/_app.tsx +++ b/examples/with-styled-components-babel/pages/_app.tsx @@ -1,3 +1,4 @@ +import type { AppProps } from 'next/app' import { createGlobalStyle, ThemeProvider } from 'styled-components' const GlobalStyle = createGlobalStyle` @@ -20,7 +21,7 @@ const theme: ThemeInterface = { }, } -export default function App({ Component, pageProps }) { +export default function App({ Component, pageProps }: AppProps) { return ( <> diff --git a/examples/with-supertokens/pages/index.tsx b/examples/with-supertokens/pages/index.tsx index 43950b22f434..81c3c34567ec 100644 --- a/examples/with-supertokens/pages/index.tsx +++ b/examples/with-supertokens/pages/index.tsx @@ -16,7 +16,11 @@ export async function getServerSideProps(context) { supertokensNode.init(backendConfig()) let session try { - session = await Session.getSession(context.req, context.res) + session = await Session.getSession(context.req, context.res, { + overrideGlobalClaimValidators: async function () { + return [] + }, + }) } catch (err) { if (err.type === Session.Error.TRY_REFRESH_TOKEN) { return { props: { fromSupertokens: 'needs-refresh' } } @@ -24,11 +28,8 @@ export async function getServerSideProps(context) { // this will force the frontend to try and refresh which will fail // clearing all cookies and redirecting the user to the login screen. return { props: { fromSupertokens: 'needs-refresh' } } - } else if (err.type === Session.Error.INVALID_CLAIMS) { - return { props: {} } - } else { - throw err } + throw err } return { diff --git a/lerna.json b/lerna.json index 2d477b3a484e..6fbde5eb3f97 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.0.5-canary.3" + "version": "13.0.5-canary.4" } diff --git a/packages/create-next-app/helpers/examples.ts b/packages/create-next-app/helpers/examples.ts index 06fef57d27b1..3ff54b51fac0 100644 --- a/packages/create-next-app/helpers/examples.ts +++ b/packages/create-next-app/helpers/examples.ts @@ -102,7 +102,9 @@ export async function downloadAndExtractRepo( strip: filePath ? filePath.split('/').length + 1 : 1, filter: (p) => p.startsWith( - `${name}-${branch.replace(/\//g, '-')}${filePath ? `/${filePath}` : ''}` + `${name}-${branch.replace(/\//g, '-')}${ + filePath ? `/${filePath}/` : '/' + }` ), }) diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index bc0531c1f074..0f2e5bd02edd 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.0.5-canary.3", + "version": "13.0.5-canary.4", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 34d66a1a6267..c46636e9b3dd 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.0.5-canary.3", + "version": "13.0.5-canary.4", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "13.0.5-canary.3", + "@next/eslint-plugin-next": "13.0.5-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 1761c7dc4861..d4732e7a6fc9 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.0.5-canary.3", + "version": "13.0.5-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 2e10b0149dd2..1b5d670fa37b 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.0.5-canary.3", + "version": "13.0.5-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 5349e03c7741..565594a4737a 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.0.5-canary.3", + "version": "13.0.5-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 d25ae51d6b0b..525ec1be0639 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.0.5-canary.3", + "version": "13.0.5-canary.4", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index ff70f169028c..1cfbd9637b07 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.0.5-canary.3", + "version": "13.0.5-canary.4", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 4027d2ad096b..7795868b7612 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.0.5-canary.3", + "version": "13.0.5-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 8c65d734c07d..ae3e70283fbf 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.0.5-canary.3", + "version": "13.0.5-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 259b1f0ecaf7..d53079b6ad35 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.0.5-canary.3", + "version": "13.0.5-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 87ee8957ac25..af179d3f247e 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.0.5-canary.3", + "version": "13.0.5-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 7384ec8dd567..c526ac5cd4f6 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.0.5-canary.3", + "version": "13.0.5-canary.4", "private": true, "scripts": { "build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --features plugin --js false native", diff --git a/packages/next/build/entries.ts b/packages/next/build/entries.ts index e6f5fc1d11d8..57305cc15566 100644 --- a/packages/next/build/entries.ts +++ b/packages/next/build/entries.ts @@ -457,7 +457,11 @@ export async function createEntrypoints(params: CreateEntrypointsParams) { await Promise.all(Object.keys(pages).map(getEntryHandler(pages, 'pages'))) if (nestedMiddleware.length > 0) { - throw new NestedMiddlewareError(nestedMiddleware, rootDir, pagesDir!) + throw new NestedMiddlewareError( + nestedMiddleware, + rootDir, + (appDir || pagesDir)! + ) } return { diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index fbdf356c28a4..5ea05ac8cec2 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -11,7 +11,7 @@ import { escapeStringRegexp } from '../shared/lib/escape-regexp' import findUp from 'next/dist/compiled/find-up' import { nanoid } from 'next/dist/compiled/nanoid/index.cjs' import { pathToRegexp } from 'next/dist/compiled/path-to-regexp' -import path, { join } from 'path' +import path from 'path' import formatWebpackMessages from '../client/dev/error-overlay/format-webpack-messages' import { STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR, @@ -331,15 +331,16 @@ export default async function build( }) const { pagesDir, appDir } = findPagesDir(dir, isAppDirEnabled) + const isSrcDir = path + .relative(dir, pagesDir || appDir || '') + .startsWith('src') const hasPublicDir = await fileExists(publicDir) telemetry.record( eventCliSession(dir, config, { webpackVersion: 5, cliCommand: 'build', - isSrcDir: - (!!pagesDir && path.relative(dir, pagesDir).startsWith('src')) || - (!!appDir && path.relative(dir, appDir).startsWith('src')), + isSrcDir, hasNowJson: !!(await findUp('now.json', { cwd: dir })), isCustomServer: null, turboFlag: false, @@ -502,11 +503,10 @@ export default async function build( `^${MIDDLEWARE_FILENAME}\\.(?:${config.pageExtensions.join('|')})$` ) - const rootPaths = pagesDir - ? ( - await flatReaddir(join(pagesDir, '..'), middlewareDetectionRegExp) - ).map((absoluteFile) => absoluteFile.replace(dir, '')) - : [] + const rootDir = path.join((pagesDir || appDir)!, '..') + const rootPaths = ( + await flatReaddir(rootDir, middlewareDetectionRegExp) + ).map((absoluteFile) => absoluteFile.replace(dir, '')) // needed for static exporting since we want to replace with HTML // files @@ -1306,7 +1306,7 @@ export default async function build( config.experimental.gzipSize ) - const middlewareManifest: MiddlewareManifest = require(join( + const middlewareManifest: MiddlewareManifest = require(path.join( distDir, SERVER_DIRECTORY, MIDDLEWARE_MANIFEST @@ -1387,7 +1387,7 @@ export default async function build( const staticInfo = pagePath ? await getPageStaticInfo({ - pageFilePath: join( + pageFilePath: path.join( (pageType === 'pages' ? pagesDir : appDir) || '', pagePath ), @@ -1982,7 +1982,7 @@ export default async function build( const cssFilePaths = await new Promise((resolve, reject) => { globOrig( '**/*.css', - { cwd: join(distDir, 'static') }, + { cwd: path.join(distDir, 'static') }, (err, files) => { if (err) { return reject(err) @@ -2803,7 +2803,7 @@ export default async function build( .traceAsyncFn(async () => { await verifyPartytownSetup( dir, - join(distDir, CLIENT_STATIC_FILES_PATH) + path.join(distDir, CLIENT_STATIC_FILES_PATH) ) }) } diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 6b20468b2339..aea1ace28871 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1774,13 +1774,17 @@ export function getPossibleMiddlewareFilenames( } export class NestedMiddlewareError extends Error { - constructor(nestedFileNames: string[], mainDir: string, pagesDir: string) { + constructor( + nestedFileNames: string[], + mainDir: string, + pagesOrAppDir: string + ) { super( `Nested Middleware is not allowed, found:\n` + `${nestedFileNames.map((file) => `pages${file}`).join('\n')}\n` + `Please move your code to a single file at ${path.join( path.posix.sep, - path.relative(mainDir, path.resolve(pagesDir, '..')), + path.relative(mainDir, path.resolve(pagesOrAppDir, '..')), 'middleware' )} instead.\n` + `Read More - https://nextjs.org/docs/messages/nested-middleware` diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 2e7bebaaf90d..6e09c85cb558 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -299,6 +299,9 @@ export function getDefineEnv({ 'process.env.__NEXT_NO_MIDDLEWARE_URL_NORMALIZE': JSON.stringify( config.experimental.skipMiddlewareUrlNormalize ), + 'process.env.__NEXT_MANUAL_TRAILING_SLASH': JSON.stringify( + config.experimental?.skipTrailingSlashRedirect + ), 'process.env.__NEXT_HAS_WEB_VITALS_ATTRIBUTION': JSON.stringify( config.experimental.webVitalsAttribution && config.experimental.webVitalsAttribution.length > 0 @@ -828,6 +831,7 @@ export default async function getBaseWebpackConfig( const customRootAliases: { [key: string]: string[] } = {} if (dev) { + const nextDist = 'next/dist/' + (isEdgeServer ? 'esm/' : '') customAppAliases[`${PAGES_DIR_ALIAS}/_app`] = [ ...(pagesDir ? pageExtensions.reduce((prev, ext) => { @@ -835,7 +839,7 @@ export default async function getBaseWebpackConfig( return prev }, [] as string[]) : []), - 'next/dist/pages/_app.js', + `${nextDist}pages/_app.js`, ] customAppAliases[`${PAGES_DIR_ALIAS}/_error`] = [ ...(pagesDir @@ -844,7 +848,7 @@ export default async function getBaseWebpackConfig( return prev }, [] as string[]) : []), - 'next/dist/pages/_error.js', + `${nextDist}pages/_error.js`, ] customDocumentAliases[`${PAGES_DIR_ALIAS}/_document`] = [ ...(pagesDir @@ -853,7 +857,7 @@ export default async function getBaseWebpackConfig( return prev }, [] as string[]) : []), - 'next/dist/pages/_document.js', + `${nextDist}pages/_document.js`, ] } @@ -905,6 +909,10 @@ export default async function getBaseWebpackConfig( 'next/dist/esm/shared/lib/head', [require.resolve('next/dist/shared/lib/dynamic')]: 'next/dist/esm/shared/lib/dynamic', + [require.resolve('next/dist/pages/_document')]: + 'next/dist/esm/pages/_document', + [require.resolve('next/dist/pages/_app')]: + 'next/dist/esm/pages/_app', } : undefined), diff --git a/packages/next/build/webpack/plugins/flight-types-plugin.ts b/packages/next/build/webpack/plugins/flight-types-plugin.ts index 15371d5337a2..9d92207b222a 100644 --- a/packages/next/build/webpack/plugins/flight-types-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-types-plugin.ts @@ -35,8 +35,8 @@ interface LayoutProps { params: any } -type PageComponent = (props: PageProps) => React.ReactNode | Promise -type LayoutComponent = (props: LayoutProps) => React.ReactNode | Promise +export type PageComponent = (props: PageProps) => React.ReactNode | Promise +export type LayoutComponent = (props: LayoutProps) => React.ReactNode | Promise interface IEntry { ${ diff --git a/packages/next/cli/next-dev.ts b/packages/next/cli/next-dev.ts index 1f0fc1ab875d..b0a689c84f66 100755 --- a/packages/next/cli/next-dev.ts +++ b/packages/next/cli/next-dev.ts @@ -380,9 +380,9 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit eventCliSession(distDir, rawNextConfig as NextConfigComplete, { webpackVersion: 5, cliCommand: 'dev', - isSrcDir: - (!!pagesDir && path.relative(dir, pagesDir).startsWith('src')) || - (!!appDir && path.relative(dir, appDir).startsWith('src')), + isSrcDir: path + .relative(dir, pagesDir || appDir || '') + .startsWith('src'), hasNowJson: !!(await findUp('now.json', { cwd: dir })), isCustomServer: false, turboFlag: true, diff --git a/packages/next/client/components/app-router-headers.ts b/packages/next/client/components/app-router-headers.ts index 1eb2747dd1cd..de00963e625e 100644 --- a/packages/next/client/components/app-router-headers.ts +++ b/packages/next/client/components/app-router-headers.ts @@ -3,3 +3,9 @@ export const NEXT_ROUTER_STATE_TREE = 'Next-Router-State-Tree' as const export const NEXT_ROUTER_PREFETCH = 'Next-Router-Prefetch' as const export const RSC_VARY_HEADER = `${RSC}, ${NEXT_ROUTER_STATE_TREE}, ${NEXT_ROUTER_PREFETCH}` as const + +export const FLIGHT_PARAMETERS = [ + [RSC], + [NEXT_ROUTER_STATE_TREE], + [NEXT_ROUTER_PREFETCH], +] as const diff --git a/packages/next/client/components/react-dev-overlay/hot-reloader-client.tsx b/packages/next/client/components/react-dev-overlay/hot-reloader-client.tsx index f5a4f91f9fa8..0a5cbb9c6a78 100644 --- a/packages/next/client/components/react-dev-overlay/hot-reloader-client.tsx +++ b/packages/next/client/components/react-dev-overlay/hot-reloader-client.tsx @@ -124,7 +124,7 @@ function tryApplyUpdates( return } - function handleApplyUpdates(err: any, updatedModules: any) { + function handleApplyUpdates(err: any, updatedModules: any[] | null) { if (err || RuntimeErrorHandler.hadRuntimeError || !updatedModules) { if (err) { console.warn( @@ -144,7 +144,7 @@ function tryApplyUpdates( return } - const hasUpdates = Boolean(updatedModules.length) + const hasUpdates = Boolean(updatedModules?.length) if (typeof onHotUpdateSuccess === 'function') { // Maybe we want to do something. onHotUpdateSuccess(hasUpdates) @@ -175,8 +175,8 @@ function tryApplyUpdates( // @ts-expect-error module.hot exists module.hot .check(/* autoApply */ false) - .then((updatedModules: any) => { - const hasUpdates = Boolean(updatedModules.length) + .then((updatedModules: any[] | null) => { + const hasUpdates = Boolean(updatedModules?.length) if (typeof onBeforeUpdate === 'function') { onBeforeUpdate(hasUpdates) } diff --git a/packages/next/client/dev/error-overlay/hot-dev-client.js b/packages/next/client/dev/error-overlay/hot-dev-client.js index 697c99945c21..fa613a6ee03d 100644 --- a/packages/next/client/dev/error-overlay/hot-dev-client.js +++ b/packages/next/client/dev/error-overlay/hot-dev-client.js @@ -337,7 +337,7 @@ function tryApplyUpdates(onBeforeHotUpdate, onHotUpdateSuccess) { return } - const hasUpdates = Boolean(updatedModules.length) + const hasUpdates = Boolean(updatedModules?.length) if (typeof onHotUpdateSuccess === 'function') { // Maybe we want to do something. onHotUpdateSuccess(hasUpdates) @@ -368,7 +368,7 @@ function tryApplyUpdates(onBeforeHotUpdate, onHotUpdateSuccess) { .check(/* autoApply */ false) .then((updatedModules) => { if (typeof onBeforeHotUpdate === 'function') { - const hasUpdates = Boolean(updatedModules.length) + const hasUpdates = Boolean(updatedModules?.length) onBeforeHotUpdate(hasUpdates) } return module.hot.apply() diff --git a/packages/next/client/normalize-trailing-slash.ts b/packages/next/client/normalize-trailing-slash.ts index d2405675a1de..11547936af6b 100644 --- a/packages/next/client/normalize-trailing-slash.ts +++ b/packages/next/client/normalize-trailing-slash.ts @@ -6,7 +6,7 @@ import { parsePath } from '../shared/lib/router/utils/parse-path' * in `next.config.js`. */ export const normalizePathTrailingSlash = (path: string) => { - if (!path.startsWith('/')) { + if (!path.startsWith('/') || process.env.__NEXT_MANUAL_TRAILING_SLASH) { return path } diff --git a/packages/next/package.json b/packages/next/package.json index 53d96414645d..da1ae1710729 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.0.5-canary.3", + "version": "13.0.5-canary.4", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -75,7 +75,7 @@ ] }, "dependencies": { - "@next/env": "13.0.5-canary.3", + "@next/env": "13.0.5-canary.4", "@swc/helpers": "0.4.14", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", @@ -126,11 +126,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.12.0", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.0.5-canary.3", - "@next/polyfill-nomodule": "13.0.5-canary.3", - "@next/react-dev-overlay": "13.0.5-canary.3", - "@next/react-refresh-utils": "13.0.5-canary.3", - "@next/swc": "13.0.5-canary.3", + "@next/polyfill-module": "13.0.5-canary.4", + "@next/polyfill-nomodule": "13.0.5-canary.4", + "@next/react-dev-overlay": "13.0.5-canary.4", + "@next/react-refresh-utils": "13.0.5-canary.4", + "@next/swc": "13.0.5-canary.4", "@segment/ajv-human-errors": "2.1.2", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index a3ca3cda425b..9c2723e8ec50 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -42,6 +42,7 @@ import { NEXT_ROUTER_PREFETCH, NEXT_ROUTER_STATE_TREE, RSC, + FLIGHT_PARAMETERS, } from '../client/components/app-router-headers' import type { StaticGenerationStore } from '../client/components/static-generation-async-storage' @@ -704,12 +705,6 @@ function getScriptNonceFromHeader(cspHeaderValue: string): string | undefined { return nonce } -export const FLIGHT_PARAMETERS = [ - [RSC], - [NEXT_ROUTER_STATE_TREE], - [NEXT_ROUTER_PREFETCH], -] as const - function headersWithoutFlight(headers: IncomingHttpHeaders) { const newHeaders = { ...headers } for (const param of FLIGHT_PARAMETERS) { @@ -999,13 +994,17 @@ export async function renderToHTMLOrFlight( filePath, serverCSSForEntries ) - const cacheBustingUrlSuffix = dev ? `?ts=${Date.now()}` : '' const styles = cssHrefs ? cssHrefs.map((href, index) => ( { - // Add extra cache busting (DEV only) for https://github.com/vercel/next.js/issues/5860 - // See also https://bugs.webkit.org/show_bug.cgi?id=187726 - const cacheBustingUrlSuffix = dev ? `?ts=${Date.now()}` : '' - return ( <> {preloadedFontFiles.map((fontFile) => { @@ -1347,7 +1342,12 @@ export async function renderToHTMLOrFlight( ? stylesheets.map((href, index) => ( !this.appPathRoutes![route] + ), }) ) .send() diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 753dd1163778..c76016103393 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.0.5-canary.3", + "version": "13.0.5-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 e68b764481a9..2a6a6999b5bd 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.0.5-canary.3", + "version": "13.0.5-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 f94b6011783a..819b1ed56419 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -410,7 +410,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.0.5-canary.3 + '@next/eslint-plugin-next': 13.0.5-canary.4 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint-import-resolver-node: ^0.3.6 @@ -478,12 +478,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.12.0 '@napi-rs/triples': 1.1.0 - '@next/env': 13.0.5-canary.3 - '@next/polyfill-module': 13.0.5-canary.3 - '@next/polyfill-nomodule': 13.0.5-canary.3 - '@next/react-dev-overlay': 13.0.5-canary.3 - '@next/react-refresh-utils': 13.0.5-canary.3 - '@next/swc': 13.0.5-canary.3 + '@next/env': 13.0.5-canary.4 + '@next/polyfill-module': 13.0.5-canary.4 + '@next/polyfill-nomodule': 13.0.5-canary.4 + '@next/react-dev-overlay': 13.0.5-canary.4 + '@next/react-refresh-utils': 13.0.5-canary.4 + '@next/swc': 13.0.5-canary.4 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.14 '@taskr/clear': 1.1.0 diff --git a/test/e2e/app-dir/app-middleware.test.ts b/test/e2e/app-dir/app-middleware.test.ts index 91b79a72f52a..4c1caccd53d8 100644 --- a/test/e2e/app-dir/app-middleware.test.ts +++ b/test/e2e/app-dir/app-middleware.test.ts @@ -1,7 +1,7 @@ /* eslint-env jest */ import { NextInstance } from 'test/lib/next-modes/base' -import { fetchViaHTTP } from 'next-test-utils' +import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils' import { createNext, FileRef } from 'e2e-utils' import cheerio from 'cheerio' import path from 'path' @@ -18,10 +18,6 @@ describe('app-dir with middleware', () => { beforeAll(async () => { next = await createNext({ files: new FileRef(path.join(__dirname, 'app-middleware')), - dependencies: { - react: 'experimental', - 'react-dom': 'experimental', - }, }) }) @@ -122,3 +118,41 @@ describe('app-dir with middleware', () => { }) }) }) + +describe('app dir middleware without pages dir', () => { + if ((global as any).isNextDeploy) { + it('should skip next deploy for now', () => {}) + return + } + + let next: NextInstance + + afterAll(() => next.destroy()) + beforeAll(async () => { + next = await createNext({ + files: { + app: new FileRef(path.join(__dirname, 'app-middleware/app')), + 'next.config.js': new FileRef( + path.join(__dirname, 'app-middleware/next.config.js') + ), + 'middleware.js': ` + import { NextResponse } from 'next/server' + + export async function middleware(request) { + return new NextResponse('redirected') + } + + export const config = { + matcher: '/headers' + } + `, + }, + }) + }) + + it(`Updates headers`, async () => { + const html = await renderViaHTTP(next.url, '/headers') + + expect(html).toContain('redirected') + }) +}) diff --git a/test/e2e/app-dir/app-middleware/next.config.js b/test/e2e/app-dir/app-middleware/next.config.js index a928ea943ce2..4ab660b86b15 100644 --- a/test/e2e/app-dir/app-middleware/next.config.js +++ b/test/e2e/app-dir/app-middleware/next.config.js @@ -1,7 +1,6 @@ module.exports = { experimental: { appDir: true, - legacyBrowsers: false, - browsersListForSwc: true, + allowMiddlewareResponseBody: true, }, } diff --git a/test/e2e/app-dir/app/app/dynamic-pages-route-app-overlap/app-dir/page.js b/test/e2e/app-dir/app/app/dynamic-pages-route-app-overlap/app-dir/page.js new file mode 100644 index 000000000000..2b529331402f --- /dev/null +++ b/test/e2e/app-dir/app/app/dynamic-pages-route-app-overlap/app-dir/page.js @@ -0,0 +1,7 @@ +export default function Page() { + return ( +

+ hello from app/dynamic-pages-route-app-overlap/app-dir/page +

+ ) +} diff --git a/test/e2e/app-dir/app/pages/dynamic-pages-route-app-overlap.js b/test/e2e/app-dir/app/pages/dynamic-pages-route-app-overlap.js new file mode 100644 index 000000000000..725caae59813 --- /dev/null +++ b/test/e2e/app-dir/app/pages/dynamic-pages-route-app-overlap.js @@ -0,0 +1,9 @@ +import Link from 'next/link' + +export default function Page() { + return ( + + To /dynamic-pages-route-app-overlap/app-dir + + ) +} diff --git a/test/e2e/app-dir/app/pages/dynamic-pages-route-app-overlap/[slug].js b/test/e2e/app-dir/app/pages/dynamic-pages-route-app-overlap/[slug].js new file mode 100644 index 000000000000..80f7029a0281 --- /dev/null +++ b/test/e2e/app-dir/app/pages/dynamic-pages-route-app-overlap/[slug].js @@ -0,0 +1,7 @@ +export default function Page() { + return ( +

+ hello from pages/dynamic-pages-route-app-overlap/[slug] +

+ ) +} diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index 8f04fdfb6269..215d1af06884 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -299,20 +299,6 @@ describe('app dir', () => { expect(html).toContain('hello from app/partial-match-[id]. ID is: 123') }) - // This is a workaround to fix https://github.com/vercel/next.js/issues/5860 - // TODO: remove this workaround when https://bugs.webkit.org/show_bug.cgi?id=187726 is fixed. - it('should use cache busting when loading css (dev only)', async () => { - const html = await renderViaHTTP(next.url, '/') - const $ = cheerio.load(html) - const links = $('link[rel=stylesheet]') - links.each((_, link) => { - const href = $(link).attr('href') - isDev - ? expect(href).toMatch(/\?ts=/) - : expect(href).not.toMatch(/\?ts=/) - }) - }) - describe('rewrites', () => { // TODO-APP: rewrite url is broken it('should support rewrites on initial load', async () => { @@ -615,6 +601,29 @@ describe('app dir', () => { await browser.close() } }) + + it('should navigate to pages dynamic route from pages page if it overlaps with an app page', async () => { + const browser = await webdriver( + next.url, + '/dynamic-pages-route-app-overlap' + ) + + try { + // Click the link. + await browser.elementById('pages-link').click() + expect(await browser.waitForElementByCss('#pages-text').text()).toBe( + 'hello from pages/dynamic-pages-route-app-overlap/[slug]' + ) + + // When refreshing the browser, the app page should be rendered + await browser.refresh() + expect(await browser.waitForElementByCss('#app-text').text()).toBe( + 'hello from app/dynamic-pages-route-app-overlap/app-dir/page' + ) + } finally { + await browser.close() + } + }) }) describe('server components', () => { @@ -1514,7 +1523,7 @@ describe('app dir', () => { const html = await renderViaHTTP(next.url, '/loading-bug/hi') // The link tag should be included together with loading expect(html).toMatch( - /

Loading...<\/h2>/ + /

Loading...<\/h2>/ ) }) @@ -1574,6 +1583,66 @@ describe('app dir', () => { ).toBe('50px') }) }) + + if (isDev) { + describe('HMR', () => { + it('should support HMR for CSS imports in server components', async () => { + const filePath = 'app/css/css-page/style.css' + const origContent = await next.readFile(filePath) + + // h1 should be red + const browser = await webdriver(next.url, '/css/css-page') + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('h1')).color` + ) + ).toBe('rgb(255, 0, 0)') + + try { + await next.patchFile(filePath, origContent.replace('red', 'blue')) + + // Wait for HMR to trigger + await new Promise((resolve) => setTimeout(resolve, 2000)) + + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('h1')).color` + ) + ).toBe('rgb(0, 0, 255)') + } finally { + await next.patchFile(filePath, origContent) + } + }) + + it('should support HMR for CSS imports in client components', async () => { + const filePath = 'app/css/css-client/client-page.css' + const origContent = await next.readFile(filePath) + + // h1 should be red + const browser = await webdriver(next.url, '/css/css-client') + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('h1')).color` + ) + ).toBe('rgb(255, 0, 0)') + + try { + await next.patchFile(filePath, origContent.replace('red', 'blue')) + + // Wait for HMR to trigger + await new Promise((resolve) => setTimeout(resolve, 2000)) + + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('h1')).color` + ) + ).toBe('rgb(0, 0, 255)') + } finally { + await next.patchFile(filePath, origContent) + } + }) + }) + } }) describe('searchParams prop', () => { diff --git a/test/e2e/skip-trailing-slash-redirect/app/pages/index.js b/test/e2e/skip-trailing-slash-redirect/app/pages/index.js index 04fbd07fee5b..8cf5943c3cd9 100644 --- a/test/e2e/skip-trailing-slash-redirect/app/pages/index.js +++ b/test/e2e/skip-trailing-slash-redirect/app/pages/index.js @@ -8,6 +8,10 @@ export default function Page(props) { to another
+ + to another + +
to /blog/first diff --git a/test/e2e/skip-trailing-slash-redirect/index.test.ts b/test/e2e/skip-trailing-slash-redirect/index.test.ts index e31d7a6909fc..cb3e14c15f54 100644 --- a/test/e2e/skip-trailing-slash-redirect/index.test.ts +++ b/test/e2e/skip-trailing-slash-redirect/index.test.ts @@ -174,6 +174,54 @@ describe('skip-trailing-slash-redirect', () => { expect(await res.text()).toContain('another page') }) + it('should not apply trailing slash to links on client', async () => { + const browser = await webdriver(next.url, '/') + await browser.eval('window.beforeNav = 1') + + expect( + new URL( + await browser.elementByCss('#to-another').getAttribute('href'), + 'http://n' + ).pathname + ).toBe('/another') + + await browser.elementByCss('#to-another').click() + await browser.waitForElementByCss('#another') + + expect(await browser.eval('window.location.pathname')).toBe('/another') + + await browser.back().waitForElementByCss('#to-another') + + expect( + new URL( + await browser + .elementByCss('#to-another-with-slash') + .getAttribute('href'), + 'http://n' + ).pathname + ).toBe('/another/') + + await browser.elementByCss('#to-another-with-slash').click() + await browser.waitForElementByCss('#another') + + expect(await browser.eval('window.location.pathname')).toBe('/another/') + + await browser.back().waitForElementByCss('#to-another') + expect(await browser.eval('window.beforeNav')).toBe(1) + }) + + it('should not apply trailing slash on load on client', async () => { + let browser = await webdriver(next.url, '/another') + await check(() => browser.eval('next.router.isReady ? "yes": "no"'), 'yes') + + expect(await browser.eval('location.pathname')).toBe('/another') + + browser = await webdriver(next.url, '/another/') + await check(() => browser.eval('next.router.isReady ? "yes": "no"'), 'yes') + + expect(await browser.eval('location.pathname')).toBe('/another/') + }) + it('should respond to index correctly', async () => { const res = await fetchViaHTTP(next.url, '/', undefined, { redirect: 'manual', diff --git a/test/e2e/streaming-ssr/index.test.ts b/test/e2e/streaming-ssr/index.test.ts index 958bec7e0a5c..9f500fa2a17c 100644 --- a/test/e2e/streaming-ssr/index.test.ts +++ b/test/e2e/streaming-ssr/index.test.ts @@ -2,6 +2,7 @@ import { join } from 'path' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import { + check, fetchViaHTTP, findPort, initNextServerScript, @@ -16,7 +17,7 @@ const react18Deps = { const isNextProd = !(global as any).isNextDev && !(global as any).isNextDeploy -describe('react 18 streaming SSR with custom next configs', () => { +describe('streaming SSR with custom next configs', () => { let next: NextInstance beforeAll(async () => { @@ -74,10 +75,37 @@ describe('react 18 streaming SSR with custom next configs', () => { const html = await renderViaHTTP(next.url, '/multi-byte') expect(html).toContain('マルチバイト'.repeat(28)) }) + + if ((global as any).isNextDev) { + it('should work with custom document', async () => { + await next.patchFile( + 'pages/_document.js', + ` + import { Html, Head, Main, NextScript } from 'next/document' + + export default function Document() { + return ( + + + +
+ + + + ) + } + ` + ) + await check(async () => { + return await renderViaHTTP(next.url, '/') + }, /index/) + await next.deleteFile('pages/_document.js') + }) + } }) if (isNextProd) { - describe('react 18 streaming SSR with custom server', () => { + describe('streaming SSR with custom server', () => { let next let server let appPort