diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index c945c51077c25..8afc07c12e76b 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -208,7 +208,7 @@ jobs: name: Test Unit runs-on: ubuntu-latest needs: [build, build-native-test] - timeout-minutes: 5 + timeout-minutes: 10 env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -243,7 +243,7 @@ jobs: name: Test Development runs-on: ubuntu-latest needs: [build, build-native-test] - timeout-minutes: 20 + timeout-minutes: 25 env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -283,7 +283,7 @@ jobs: if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: node run-tests.js --type development --timings -g ${{ matrix.group }}/2 @@ -343,7 +343,7 @@ jobs: if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: node run-tests.js --type development --timings -g ${{ matrix.group }}/2 @@ -364,7 +364,7 @@ jobs: name: Test Development (E2E) runs-on: ubuntu-latest needs: [build, build-native-test] - timeout-minutes: 20 + timeout-minutes: 25 env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -405,7 +405,7 @@ jobs: if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npx @replayio/playwright install chromium @@ -484,7 +484,7 @@ jobs: if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: NEXT_TEST_MODE=dev node run-tests.js --type e2e --timings -g ${{ matrix.group }}/3 @@ -545,7 +545,7 @@ jobs: if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: node run-tests.js --type production --timings -g ${{ matrix.group }}/2 @@ -595,7 +595,7 @@ jobs: if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: node run-tests.js --type production --timings -g ${{ matrix.group }}/2 @@ -606,7 +606,7 @@ jobs: name: Test Production (E2E) runs-on: ubuntu-latest needs: [build, build-native-test] - timeout-minutes: 25 + timeout-minutes: 30 env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -647,7 +647,7 @@ jobs: if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: NEXT_TEST_MODE=start node run-tests.js --type e2e --timings -g ${{ matrix.group }}/3 @@ -698,7 +698,7 @@ jobs: if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: NEXT_TEST_MODE=start node run-tests.js --type e2e --timings -g ${{ matrix.group }}/3 @@ -709,7 +709,7 @@ jobs: name: Test Integration runs-on: ubuntu-latest needs: [build, build-native-test] - timeout-minutes: 20 + timeout-minutes: 25 env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -771,7 +771,7 @@ jobs: if: ${{needs.build.outputs.docsChange == 'nope'}} - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/20 @@ -791,7 +791,7 @@ jobs: name: Test Electron runs-on: ubuntu-latest needs: [build, build-native-test] - timeout-minutes: 5 + timeout-minutes: 10 env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 @@ -847,7 +847,7 @@ jobs: name: Test Firefox (production) runs-on: ubuntu-latest needs: [build, build-native-test] - timeout-minutes: 5 + timeout-minutes: 10 env: BROWSER_NAME: 'firefox' NEXT_TELEMETRY_DISABLED: 1 @@ -882,7 +882,7 @@ jobs: name: Test Safari (production) runs-on: ubuntu-latest needs: [build, build-native-test] - timeout-minutes: 10 + timeout-minutes: 15 env: BROWSER_NAME: 'safari' NEXT_TEST_MODE: 'start' @@ -928,7 +928,7 @@ jobs: name: Test Safari 10.1 (nav) runs-on: ubuntu-latest needs: [build, build-native-test] - timeout-minutes: 5 + timeout-minutes: 10 env: BROWSERSTACK: true LEGACY_SAFARI: true @@ -975,7 +975,7 @@ jobs: name: Test Firefox Node.js 18 runs-on: ubuntu-latest needs: [build, testFirefox, build-native-test] - timeout-minutes: 5 + timeout-minutes: 10 env: BROWSER_NAME: 'firefox' NEXT_TELEMETRY_DISABLED: 1 @@ -1081,7 +1081,7 @@ jobs: name: Install pnpm - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 name: Install playwright dependencies - run: RESET_VC_PROJECT=true node scripts/reset-vercel-project.mjs @@ -1248,7 +1248,7 @@ jobs: test-wasm: name: Test the wasm build runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 needs: [build, build-native-test, build-wasm-dev] steps: @@ -1283,7 +1283,7 @@ jobs: check-latest: true - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 5 + timeout-minutes: 10 if: ${{needs.build.outputs.docsChange == 'nope'}} - run: node ./scripts/setup-wasm.mjs @@ -1361,6 +1361,7 @@ jobs: docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 build: >- set -e && + export JEMALLOC_SYS_WITH_LG_PAGE=16 && rustup toolchain install "${RUST_TOOLCHAIN}" && rustup default "${RUST_TOOLCHAIN}" && rustup target add aarch64-unknown-linux-gnu && @@ -1412,6 +1413,7 @@ jobs: docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine build: >- set -e && + export JEMALLOC_SYS_WITH_LG_PAGE=16 && 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 && rustup toolchain install "${RUST_TOOLCHAIN}" && rustup default "${RUST_TOOLCHAIN}" && diff --git a/bench/minimal-server/benchmark-app/app/layout.js b/bench/minimal-server/benchmark-app/app/layout.js new file mode 100644 index 0000000000000..bf15eff980a84 --- /dev/null +++ b/bench/minimal-server/benchmark-app/app/layout.js @@ -0,0 +1,10 @@ +import * as React from 'react' + +export default function Root({ children }) { + return ( + + + {children} + + ) +} diff --git a/bench/minimal-server/benchmark-app/app/rsc/page.js b/bench/minimal-server/benchmark-app/app/rsc/page.js new file mode 100644 index 0000000000000..395757bacd944 --- /dev/null +++ b/bench/minimal-server/benchmark-app/app/rsc/page.js @@ -0,0 +1,5 @@ +import * as React from 'react' + +export default function page() { + return
hello
+} diff --git a/bench/minimal-server/benchmark-app/next.config.js b/bench/minimal-server/benchmark-app/next.config.js new file mode 100644 index 0000000000000..cfa3ac3d7aa94 --- /dev/null +++ b/bench/minimal-server/benchmark-app/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + experimental: { + appDir: true, + }, +} diff --git a/bench/minimal-server/benchmark-app/package.json b/bench/minimal-server/benchmark-app/package.json new file mode 100644 index 0000000000000..883a323075faf --- /dev/null +++ b/bench/minimal-server/benchmark-app/package.json @@ -0,0 +1,14 @@ +{ + "name": "stats-app", + "private": true, + "license": "MIT", + "dependencies": { + "webpack-bundle-analyzer": "^4.6.1", + "webpack-stats-plugin": "^1.1.0" + }, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + } +} diff --git a/bench/minimal-server/benchmark-app/pages/index.js b/bench/minimal-server/benchmark-app/pages/index.js new file mode 100644 index 0000000000000..a41e3b2fd91bf --- /dev/null +++ b/bench/minimal-server/benchmark-app/pages/index.js @@ -0,0 +1,9 @@ +import * as React from 'react' + +export default function page() { + return
hello world
+} + +export async function getServerSideProps() { + return {} +} diff --git a/bench/minimal-server/package.json b/bench/minimal-server/package.json new file mode 100644 index 0000000000000..cdb7cecb8f611 --- /dev/null +++ b/bench/minimal-server/package.json @@ -0,0 +1,7 @@ +{ + "name": "next-minimal-server", + "description": "Minimal server for Next.js for benchmarking/perf analysis purposes.", + "scripts": { + "start": "node start.js" + } +} diff --git a/bench/minimal-server/start.js b/bench/minimal-server/start.js new file mode 100644 index 0000000000000..ceee4f2e235a9 --- /dev/null +++ b/bench/minimal-server/start.js @@ -0,0 +1,40 @@ +process.env.__NEXT_REACT_CHANNEL = 'exp' +process.env.NODE_ENV = 'production' + +require('../../test/lib/react-channel-require-hook') + +console.time('next-cold-start') +const NextServer = require('next/dist/server/next-server').default +const path = require('path') + +const appDir = path.join(__dirname, 'benchmark-app') +const distDir = '.next' + +const compiledConfig = require(path.join( + appDir, + distDir, + 'required-server-files.json' +)).config + +process.chdir(appDir) + +const nextServer = new NextServer({ + conf: compiledConfig, + dir: appDir, + distDir, + minimalMode: true, + customServer: false, +}) + +const requestHandler = nextServer.getRequestHandler() + +require('http') + .createServer((req, res) => { + console.time('next-request') + return requestHandler(req, res).finally(() => { + console.timeEnd('next-request') + }) + }) + .listen(3000, () => { + console.timeEnd('next-cold-start') + }) diff --git a/docs/advanced-features/codemods.md b/docs/advanced-features/codemods.md index f897c89f206ea..ced2bbf5cc5ca 100644 --- a/docs/advanced-features/codemods.md +++ b/docs/advanced-features/codemods.md @@ -17,6 +17,55 @@ Codemods are transformations that run on your codebase programmatically. This al - `--dry` Do a dry-run, no code will be edited - `--print` Prints the changed output for comparison +## Next.js 13 + +### `next-image-to-legacy-image` + +Safely migrates existing Next.js 10, 11, 12 applications importing `next/image` to the renamed `next/legacy/image` import in Next.js 13. + +For example: + +```jsx +import Image1 from 'next/image' +import Image2 from 'next/future/image' + +export default function Home() { + return ( +
+ + +
+ ) +} +``` + +Transforms into: + +```jsx +import Image1 from 'next/legacy/image' +import Image2 from 'next/image' + +export default function Home() { + return ( +
+ + +
+ ) +} +``` + +### `next-image-experimental` (experimental) + +Dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props. + +- Removes `layout` prop and adds `style` +- Removes `objectFit` prop and adds `style` +- Removes `objectPosition` prop and adds `style` +- Removes `lazyBoundary` prop +- Removes `lazyRoot` prop +- TODO: handle `loader` + ## Next.js 11 ### `cra-to-next` (experimental) diff --git a/docs/advanced-features/security-headers.md b/docs/advanced-features/security-headers.md index 660c97711c157..1e055400f3348 100644 --- a/docs/advanced-features/security-headers.md +++ b/docs/advanced-features/security-headers.md @@ -130,9 +130,9 @@ const ContentSecurityPolicy = ` When a directive uses a keyword such as `self`, wrap it in single quotes `''`. -In the header's value, replace the new line with an empty string. +In the header's value, replace the new line with a space. -```jsx +```js { key: 'Content-Security-Policy', value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim() diff --git a/docs/upgrading.md b/docs/upgrading.md index e8c4f5b6cb8cc..97b55cb296e98 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -4,6 +4,11 @@ description: Learn how to upgrade Next.js. # Upgrade Guide +## Upgrading from 12 to 13 + +The `next/image` import was renamed to `next/legacy/image`. The `next/future/image` import was renamed to `next/image`. +A [codemod is available](/docs/advanced-features/codemods.md#next-image-to-legacy-image) to safely and automatically rename your imports. + ## Upgrading to 12.2 If you were using Middleware prior to `12.2`, please see the [upgrade guide](https://nextjs.org/docs/messages/middleware-upgrade-guide) for more information. diff --git a/errors/edge-dynamic-code-evaluation.md b/errors/edge-dynamic-code-evaluation.md index 29a5e74b6329f..26a2de05ef172 100644 --- a/errors/edge-dynamic-code-evaluation.md +++ b/errors/edge-dynamic-code-evaluation.md @@ -29,6 +29,18 @@ export default async function middleware() { ``` In rare cases, your code could contain (or import) some dynamic code evaluation statements which _can not be reached at runtime_ and which can not be removed by treeshaking. -You can relax the check to allow specific files with your Middleware or Edge API Route exported [configuration](https://nextjs.org/docs/api-reference/edge-runtime#unsupported-apis). +You can relax the check to allow specific files with your Middleware or Edge API Route exported [configuration](https://nextjs.org/docs/api-reference/edge-runtime#unsupported-apis): + +```typescript +export const config = { + runtime: 'experimental-edge', // for Edge API Routes only + unstable_allowDynamic: [ + '/lib/utilities.js', // allows a single file + '/node_modules/function-bind/**', // use a glob to allow anything in the function-bind 3rd party module + ], +} +``` + +`unstable_allowDynamic` is a glob, or an array of globs, ignoring dynamic code evaluation for specific files. The globs are relative to your application root folder. Be warned that if these statements are executed on the Edge, _they will throw and cause a runtime error_. diff --git a/examples/auth0/.env.local.example b/examples/auth0/.env.local.example index c2c828ea3bb15..d27d0dd099409 100644 --- a/examples/auth0/.env.local.example +++ b/examples/auth0/.env.local.example @@ -1,12 +1,5 @@ -# Public Environment variables that can be used in the browser. -NEXT_PUBLIC_AUTH0_CLIENT_ID=TEST -NEXT_PUBLIC_AUTH0_SCOPE="openid profile" -NEXT_PUBLIC_AUTH0_DOMAIN="http://example.com" -NEXT_PUBLIC_BASE_URL="http://localhost:3000" -NEXT_PUBLIC_REDIRECT_URI="/api/callback" -NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI="/" - -# Secret environment variables only available to Node.js +AUTH0_ISSUER_BASE_URL="https://" +AUTH0_CLIENT_ID= AUTH0_CLIENT_SECRET= -SESSION_COOKIE_SECRET= -SESSION_COOKIE_LIFETIME=7200 +AUTH0_BASE_URL="http://localhost:3000" +AUTH0_SECRET= \ No newline at end of file diff --git a/examples/auth0/README.md b/examples/auth0/README.md index 65b9554305d83..4f46d062e7dfd 100644 --- a/examples/auth0/README.md +++ b/examples/auth0/README.md @@ -6,8 +6,9 @@ This example shows how you can use `@auth0/nextjs-auth` to easily add authentica - Signing out - Loading the user on the server side and adding it as part of SSR ([`pages/advanced/ssr-profile.tsx`](pages/advanced/ssr-profile.tsx)) - Loading the user on the client side and using fast/cached SSR pages ([`pages/index.tsx`](pages/index.tsx)) -- API Routes which can load the current user ([`pages/api/me.ts`](pages/api/me.ts)) -- Using hooks to make the user available throughout the application ([`lib/user.ts`](lib/user.ts)) +- Loading the user on the client side and checking authentication CSR pages ([`pages/profile.tsx`](pages/profile.tsx)) +- Loading the user on the client side by accessing API (Serverless function) CSR pages ([`pages/advanced/api-profile.tsx`](pages/advanced/api-profile.tsx)) +- Creates route handlers under the hood that perform different parts of the authentication flow ([`pages/auth/[...auth0].tsx`](pages/auth/[...auth0].tsx)) Read more: [https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/](https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/) @@ -50,14 +51,11 @@ cp .env.local.example .env.local Then, open `.env.local` and add the missing environment variables: -- `NEXT_PUBLIC_AUTH0_DOMAIN` - Can be found in the Auth0 dashboard under `settings`. (Should be prefixed with `https://`) -- `NEXT_PUBLIC_AUTH0_CLIENT_ID` - Can be found in the Auth0 dashboard under `settings`. +- `AUTH0_ISSUER_BASE_URL` - Can be found in the Auth0 dashboard under `settings`. (Should be prefixed with `https://`) +- `AUTH0_CLIENT_ID` - Can be found in the Auth0 dashboard under `settings`. - `AUTH0_CLIENT_SECRET` - Can be found in the Auth0 dashboard under `settings`. -- `NEXT_PUBLIC_BASE_URL` - The base url of the application. -- `NEXT_PUBLIC_REDIRECT_URI` - The relative url path where Auth0 redirects back to. -- `NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI` - Where to redirect after logging out. -- `SESSION_COOKIE_SECRET` - A unique secret used to encrypt the cookies, has to be at least 32 characters. You can use [this generator](https://generate-secret.vercel.app/32) to generate a value. -- `SESSION_COOKIE_LIFETIME` - How long a session lasts in seconds. The default is 2 hours. +- `AUTH0_BASE_URL` - The base url of the application. +- `AUTH0_SECRET` - Has to be at least 32 characters. You can use [this generator](https://generate-secret.vercel.app/32) to generate a value. ## Deploy on Vercel diff --git a/examples/auth0/components/header.tsx b/examples/auth0/components/header.tsx index 334a0c2ece12f..b405bd9d530e7 100644 --- a/examples/auth0/components/header.tsx +++ b/examples/auth0/components/header.tsx @@ -20,12 +20,17 @@ const Header = ({ user, loading }: HeaderProps) => { About +
  • + + API rendered profile (advanced) + +
  • {!loading && (user ? ( <>
  • - Client-rendered profile + Client rendered profile
  • @@ -34,12 +39,12 @@ const Header = ({ user, loading }: HeaderProps) => {
  • - Logout + Logout
  • ) : (
  • - Login + Login
  • ))} @@ -63,8 +68,9 @@ const Header = ({ user, loading }: HeaderProps) => { } li { margin-right: 1rem; + padding-right: 2rem; } - li:nth-child(2) { + li:nth-child(3) { margin-right: auto; } a { diff --git a/examples/auth0/lib/auth0.ts b/examples/auth0/lib/auth0.ts deleted file mode 100644 index cdb1efe6f1136..0000000000000 --- a/examples/auth0/lib/auth0.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { initAuth0 } from '@auth0/nextjs-auth0' -import { SignInWithAuth0 } from '@auth0/nextjs-auth0/dist/instance' - -const auth0: SignInWithAuth0 = initAuth0({ - secret: process.env.SESSION_COOKIE_SECRET, - issuerBaseURL: process.env.NEXT_PUBLIC_AUTH0_DOMAIN, - baseURL: process.env.NEXT_PUBLIC_BASE_URL, - clientID: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID, - clientSecret: process.env.AUTH0_CLIENT_SECRET, - routes: { - callback: - process.env.NEXT_PUBLIC_REDIRECT_URI || - 'http://localhost:3000/api/callback', - postLogoutRedirect: - process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI || - 'http://localhost:3000', - }, - authorizationParams: { - response_type: 'code', - scope: process.env.NEXT_PUBLIC_AUTH0_SCOPE, - }, - session: { - absoluteDuration: Number(process.env.SESSION_COOKIE_LIFETIME), - }, -}) - -export default auth0 diff --git a/examples/auth0/lib/user.ts b/examples/auth0/lib/user.ts deleted file mode 100644 index a17ad4c43cce2..0000000000000 --- a/examples/auth0/lib/user.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { UserProfile } from '@auth0/nextjs-auth0' -import { useState, useEffect } from 'react' - -export async function fetchUser(cookie = '') { - if (typeof window !== 'undefined' && window.__user) { - return window.__user - } - - const res = await fetch( - '/api/me', - cookie - ? { - headers: { - cookie, - }, - } - : {} - ) - - if (!res.ok) { - delete window.__user - return null - } - - const json: UserProfile = await res.json() - if (typeof window !== 'undefined') { - window.__user = json - } - return json -} - -export function useFetchUser( - { required }: { required: boolean } = { required: false } -) { - const [loading, setLoading] = useState( - () => !(typeof window !== 'undefined' && window.__user) - ) - const [user, setUser] = useState(() => { - if (typeof window === 'undefined') { - return null - } - - return window.__user || null - }) - - useEffect( - () => { - if (!loading && user) { - return - } - setLoading(true) - let isMounted = true - - fetchUser().then((user) => { - // Only set the user if the component is still mounted - if (isMounted) { - // When the user is not logged in but login is required - if (required && !user) { - window.location.href = '/api/login' - return - } - setUser(user) - setLoading(false) - } - }) - - return () => { - isMounted = false - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ) - - return { user, loading } -} diff --git a/examples/auth0/pages/_app.tsx b/examples/auth0/pages/_app.tsx new file mode 100644 index 0000000000000..eb843bd9d26b4 --- /dev/null +++ b/examples/auth0/pages/_app.tsx @@ -0,0 +1,14 @@ +import { UserProvider } from '@auth0/nextjs-auth0' + +export default function App({ Component, pageProps }) { + // optionally pass the 'user' prop from pages that require server-side + // rendering to prepopulate the 'useUser' hook. + + const { user } = pageProps + + return ( + + + + ) +} diff --git a/examples/auth0/pages/about.tsx b/examples/auth0/pages/about.tsx index 4a36608c06d6a..0c3b389a5b20a 100644 --- a/examples/auth0/pages/about.tsx +++ b/examples/auth0/pages/about.tsx @@ -1,16 +1,21 @@ +import { useUser } from '@auth0/nextjs-auth0' import Layout from '../components/layout' -import { useFetchUser } from '../lib/user' const About = () => { - const { user, loading } = useFetchUser() + const { user, isLoading } = useUser() return ( - +

    About

    - This is the about page, navigating between this page and Home is - always pretty fast. However, when you navigate to the Profile{' '} - page it takes more time because it uses SSR to fetch the user first; + This project shows different ways to display Profile info: using{' '} + Client rendered, Server rendered, and API rendered +

    +

    + Navigating between this page and Home is always pretty fast. + However, when you navigate to the Server rendered profile page it + takes more time because it uses SSR to fetch the user and then to + display it

    ) diff --git a/examples/auth0/pages/advanced/api-profile.tsx b/examples/auth0/pages/advanced/api-profile.tsx new file mode 100644 index 0000000000000..38ead26f6b732 --- /dev/null +++ b/examples/auth0/pages/advanced/api-profile.tsx @@ -0,0 +1,37 @@ +import { useEffect, useState } from 'react' +import { useUser } from '@auth0/nextjs-auth0' +import Layout from '../../components/layout' + +const ApiProfile = () => { + const { user, isLoading } = useUser() + + const [data, setData] = useState(null) + + useEffect(() => { + ;(async () => { + const res = await fetch('/api/protected-api') + + const data = await res.json() + + setData(data) + })() + }, []) + + return ( + +

    Profile

    + +
    +

    Public page (client rendered)

    +

    We are fetching data on the client-side :

    +

    By making request to '/api/protected-api' serverless function

    +

    so without a valid session cookie will fail

    +

    {JSON.stringify(data)}

    +
    +
    + ) +} + +// Public route.(CSR) also accessing API from the client-side. +// data is not cached when redirecting between pages. +export default ApiProfile diff --git a/examples/auth0/pages/advanced/ssr-profile.tsx b/examples/auth0/pages/advanced/ssr-profile.tsx index c8b5ca60009d5..298b890db17dd 100644 --- a/examples/auth0/pages/advanced/ssr-profile.tsx +++ b/examples/auth0/pages/advanced/ssr-profile.tsx @@ -1,7 +1,4 @@ -import { GetServerSideProps } from 'next' - -// This import is only included in the server build, because it's only used by getServerSideProps -import auth0 from '../../lib/auth0' +import { withPageAuthRequired } from '@auth0/nextjs-auth0' import Layout from '../../components/layout' import { User } from '../../interfaces' @@ -9,7 +6,7 @@ type ProfileProps = { user: User } -const Profile = ({ user }: ProfileProps) => { +export default function Profile({ user }: ProfileProps) { return (

    Profile

    @@ -24,17 +21,6 @@ const Profile = ({ user }: ProfileProps) => { ) } -export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { - // Here you can check authentication status directly before rendering the page, - // however the page would be a serverless function, which is more expensive and - // slower than a static page with client side authentication - const session = await auth0.getSession(req, res) - - if (!session || !session.user) { - return { redirect: { destination: '/api/login', permanent: false } } - } - - return { props: { user: session.user } } -} - -export default Profile +// Protected route, checking authentication status before rendering the page.(SSR) +// It's slower than a static page with client side authentication +export const getServerSideProps = withPageAuthRequired() diff --git a/examples/auth0/pages/api/auth/[...auth0].tsx b/examples/auth0/pages/api/auth/[...auth0].tsx new file mode 100644 index 0000000000000..1edbfe3eb472d --- /dev/null +++ b/examples/auth0/pages/api/auth/[...auth0].tsx @@ -0,0 +1,3 @@ +import { handleAuth } from '@auth0/nextjs-auth0' + +export default handleAuth() diff --git a/examples/auth0/pages/api/callback.ts b/examples/auth0/pages/api/callback.ts deleted file mode 100644 index 7928765a53408..0000000000000 --- a/examples/auth0/pages/api/callback.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next' -import auth0 from '../../lib/auth0' - -const callback = async (req: NextApiRequest, res: NextApiResponse) => { - try { - await auth0.handleCallback(req, res) - } catch (error) { - console.error(error) - const errorMessage = - error instanceof Error ? error.message : 'Internal server error' - res.status(500).end(errorMessage) - } -} - -export default callback diff --git a/examples/auth0/pages/api/login.ts b/examples/auth0/pages/api/login.ts deleted file mode 100644 index 904f3ea6ef6c5..0000000000000 --- a/examples/auth0/pages/api/login.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next' -import auth0 from '../../lib/auth0' - -const login = async (req: NextApiRequest, res: NextApiResponse) => { - try { - await auth0.handleLogin(req, res) - } catch (error) { - console.error(error) - const errorMessage = - error instanceof Error ? error.message : 'Internal server error' - res.status(500).end(errorMessage) - } -} - -export default login diff --git a/examples/auth0/pages/api/logout.ts b/examples/auth0/pages/api/logout.ts deleted file mode 100644 index a3393caac57b2..0000000000000 --- a/examples/auth0/pages/api/logout.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next' -import auth0 from '../../lib/auth0' - -const logout = async (req: NextApiRequest, res: NextApiResponse) => { - try { - await auth0.handleLogout(req, res) - } catch (error) { - console.error(error) - const errorMessage = - error instanceof Error ? error.message : 'Internal server error' - res.status(500).end(errorMessage) - } -} - -export default logout diff --git a/examples/auth0/pages/api/me.ts b/examples/auth0/pages/api/me.ts deleted file mode 100644 index 8b05cac2a73f3..0000000000000 --- a/examples/auth0/pages/api/me.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next' -import auth0 from '../../lib/auth0' - -const me = async (req: NextApiRequest, res: NextApiResponse) => { - try { - await auth0.handleProfile(req, res) - } catch (error) { - console.error(error) - const errorMessage = - error instanceof Error ? error.message : 'Internal server error' - res.status(500).end(errorMessage) - } -} - -export default me diff --git a/examples/auth0/pages/api/protected-api.ts b/examples/auth0/pages/api/protected-api.ts new file mode 100644 index 0000000000000..a2d6a95720cd7 --- /dev/null +++ b/examples/auth0/pages/api/protected-api.ts @@ -0,0 +1,20 @@ +import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0' + +// Serverless function +// Protected API, requests to '/api/protected' without a valid session cookie will fail + +async function handle(req, res) { + const { user } = getSession(req, res) + + try { + res.status(200).json({ + session: 'true', + id: user.sub, + nickname: user.nickname, + }) + } catch (e) { + res.status(500).json({ error: 'Unable to fetch', description: e }) + } +} + +export default withApiAuthRequired(handle) diff --git a/examples/auth0/pages/index.tsx b/examples/auth0/pages/index.tsx index 2e4828ee7a45d..a184ac9fb6444 100644 --- a/examples/auth0/pages/index.tsx +++ b/examples/auth0/pages/index.tsx @@ -1,23 +1,24 @@ +import { useUser } from '@auth0/nextjs-auth0' import Layout from '../components/layout' -import { useFetchUser } from '../lib/user' const Home = () => { - const { user, loading } = useFetchUser() + const { user, isLoading } = useUser() return ( - +

    Next.js and Auth0 Example

    - {loading &&

    Loading login info...

    } + {isLoading &&

    Loading login info...

    } - {!loading && !user && ( + {!isLoading && !user && ( <>

    To test the login click in Login

    - Once you have logged in you should be able to click in{' '} - Profile and Logout + Once you have logged in you should be able to navigate between + protected routes: client rendered, server rendered profile pages, + and Logout

    )} @@ -34,4 +35,5 @@ const Home = () => { ) } +// fast/cached SSR page export default Home diff --git a/examples/auth0/pages/profile.tsx b/examples/auth0/pages/profile.tsx index c3c95e3f2f638..9e83595b6b55d 100644 --- a/examples/auth0/pages/profile.tsx +++ b/examples/auth0/pages/profile.tsx @@ -1,6 +1,4 @@ -// This import is only needed when checking authentication status directly from getInitialProps -// import auth0 from '../lib/auth0' -import { useFetchUser } from '../lib/user' +import { withPageAuthRequired } from '@auth0/nextjs-auth0' import Layout from '../components/layout' import { User } from '../interfaces' @@ -23,14 +21,13 @@ const ProfileCard = ({ user }: ProfileCardProps) => { ) } -const Profile = () => { - const { user, loading } = useFetchUser({ required: true }) - +const Profile = ({ user, isLoading }) => { return ( - - {loading ? <>Loading... : } + + {isLoading ? <>Loading... : } ) } -export default Profile +// Protected route, checking user authentication client-side.(CSR) +export default withPageAuthRequired(Profile) diff --git a/examples/cloudflare-turnstile/.env.local.example b/examples/cloudflare-turnstile/.env.local.example new file mode 100644 index 0000000000000..7e3b3485ce38f --- /dev/null +++ b/examples/cloudflare-turnstile/.env.local.example @@ -0,0 +1,5 @@ +# Public Environment variables that can be used in the browser. +NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY= + +# Secret environment variables only available to Node.js +CLOUDFLARE_TURNSTILE_SECRET_KEY= diff --git a/examples/cloudflare-turnstile/.gitignore b/examples/cloudflare-turnstile/.gitignore new file mode 100644 index 0000000000000..c87c9b392c020 --- /dev/null +++ b/examples/cloudflare-turnstile/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/cloudflare-turnstile/README.md b/examples/cloudflare-turnstile/README.md new file mode 100644 index 0000000000000..af59d58eb4fb1 --- /dev/null +++ b/examples/cloudflare-turnstile/README.md @@ -0,0 +1,55 @@ +# Example with Cloudflare Turnstile + +[Turnstile](https://developers.cloudflare.com/turnstile/) is Cloudflare’s smart CAPTCHA alternative. It can be embedded into any website without sending traffic through Cloudflare and works without showing visitors a CAPTCHA. + +This example shows how you can use **Cloudflare Turnstile** with your Next.js project. You can see a [live version here](https://with-cloudflare-turnstile.vercel.app/). + +## Deploy your own + +Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example). + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cloudflare-turnstile&project-name=cloudflare-turnstile&repository-name=cloudflare-turnstile) + +**Important**: When you import your project on Vercel, make sure to click on **Environment Variable**s and set them to match your .env.local file. + +## How to use + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example: + +```bash +npx create-next-app --example cloudflare-turnstile cloudflare-turnstile-app +``` + +```bash +yarn create next-app --example cloudflare-turnstile cloudflare-turnstile-app +``` + +```bash +pnpm create next-app --example cloudflare-turnstile cloudflare-turnstile-app +``` + +Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). + +## Configuring Cloudflare Turnstile + +### Get a sitekey and secret key + +1. Go to the [Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/turnstile) and select your account. +2. Go to Turnstile. +3. Select Add a site and fill out the form. +4. Copy your **Site Key** and **Secret Key**. + +### Set up environment variables + +To connect the app with Cloudflare Turnstile, you'll need to add the settings from your Cloudflare dashboard as environment variables + +Copy the .env.local.example file in this directory to .env.local. + +```bash +cp .env.local.example .env.local +``` + +Then, open .env.local and fill these environment variables: + +- `NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY` +- `CLOUDFLARE_TURNSTILE_SECRET_KEY` diff --git a/examples/cloudflare-turnstile/app.css b/examples/cloudflare-turnstile/app.css new file mode 100644 index 0000000000000..e278079ff756a --- /dev/null +++ b/examples/cloudflare-turnstile/app.css @@ -0,0 +1,30 @@ +html, +body { + display: grid; + place-content: center; + margin: 0; + height: 100%; +} + +main { + height: 50vh; + width: 300px; + text-align: center; +} + +button[type='submit'] { + cursor: pointer; + width: 100%; + outline: none; + border: none; + padding: 0.5rem 1rem; + margin-top: 1rem; + border-radius: 0.25rem; + background: #0d6efd; + font-size: 1.25rem; + color: #ffffff; +} + +.checkbox { + min-height: 70px; +} diff --git a/examples/cloudflare-turnstile/next.config.js b/examples/cloudflare-turnstile/next.config.js new file mode 100644 index 0000000000000..4488de1db71ec --- /dev/null +++ b/examples/cloudflare-turnstile/next.config.js @@ -0,0 +1,10 @@ +module.exports = { + async rewrites() { + return [ + { + source: '/', + destination: '/implicit', + }, + ] + }, +} diff --git a/examples/cloudflare-turnstile/package.json b/examples/cloudflare-turnstile/package.json new file mode 100644 index 0000000000000..9fa64a28dc61a --- /dev/null +++ b/examples/cloudflare-turnstile/package.json @@ -0,0 +1,19 @@ +{ + "private": true, + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/node": "^18.0.0", + "@types/react": "^18.0.14", + "@types/react-dom": "^18.0.5", + "typescript": "^4.7.4" + } +} diff --git a/examples/cloudflare-turnstile/pages/_app.tsx b/examples/cloudflare-turnstile/pages/_app.tsx new file mode 100644 index 0000000000000..7a774ea733105 --- /dev/null +++ b/examples/cloudflare-turnstile/pages/_app.tsx @@ -0,0 +1,6 @@ +import type { AppProps } from 'next/app' +import '../app.css' + +export default function App({ Component, pageProps }: AppProps) { + return +} diff --git a/examples/cloudflare-turnstile/pages/api/handler.ts b/examples/cloudflare-turnstile/pages/api/handler.ts new file mode 100644 index 0000000000000..00368bee542aa --- /dev/null +++ b/examples/cloudflare-turnstile/pages/api/handler.ts @@ -0,0 +1,18 @@ +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function Handler( + req: NextApiRequest, + res: NextApiResponse +) { + const form = new URLSearchParams() + form.append('secret', process.env.CLOUDFLARE_TURNSTILE_SECRET_KEY) + form.append('response', req.body['cf-turnstile-response']) + form.append('remoteip', req.headers['x-forwarded-for'] as string) + + const result = await fetch( + 'https://challenges.cloudflare.com/turnstile/v0/siteverify', + { method: 'POST', body: form } + ) + const json = await result.json() + res.status(result.status).json(json) +} diff --git a/examples/cloudflare-turnstile/pages/explicit.tsx b/examples/cloudflare-turnstile/pages/explicit.tsx new file mode 100644 index 0000000000000..fc2409ee72f7a --- /dev/null +++ b/examples/cloudflare-turnstile/pages/explicit.tsx @@ -0,0 +1,43 @@ +import Script from 'next/script' + +type RenderParameters = { + sitekey: string + theme?: 'light' | 'dark' + callback?(token: string): void +} + +declare global { + interface Window { + onloadTurnstileCallback(): void + turnstile: { + render(container: string | HTMLElement, params: RenderParameters): void + } + } +} + +export default function ExplicitRender() { + return ( +
    + + ` ) @@ -298,7 +299,7 @@ function useFlightResponse( writer.close() } else { const responsePartial = decodeText(value) - const scripts = `${startScriptTag}(self.__next_s=self.__next_s||[]).push(${htmlEscapeJsonString( + const scripts = `${startScriptTag}self.__next_f.push(${htmlEscapeJsonString( JSON.stringify([1, responsePartial]) )})` @@ -511,7 +512,8 @@ function getSegmentParam(segment: string): { function getCssInlinedLinkTags( serverComponentManifest: FlightManifest, serverCSSManifest: FlightCSSManifest, - filePath: string + filePath: string, + serverCSSForEntries: string[] ): string[] { const layoutOrPageCss = serverCSSManifest[filePath] || @@ -524,10 +526,14 @@ function getCssInlinedLinkTags( const chunks = new Set() for (const css of layoutOrPageCss) { - const mod = serverComponentManifest[css] - if (mod) { - for (const chunk of mod.default.chunks) { - chunks.add(chunk) + // We only include the CSS if it's a global CSS, or it is used by this + // entrypoint. + if (serverCSSForEntries.includes(css) || !/\.module\.css/.test(css)) { + const mod = serverComponentManifest[css] + if (mod) { + for (const chunk of mod.default.chunks) { + chunks.add(chunk) + } } } } @@ -535,6 +541,23 @@ function getCssInlinedLinkTags( return [...chunks] } +function getServerCSSForEntries( + serverCSSManifest: FlightCSSManifest, + entries: string[] +) { + const css = [] + for (const entry of entries) { + const entryName = entry.replace(/\.[^.]+$/, '') + if ( + serverCSSManifest.__entry_css__ && + serverCSSManifest.__entry_css__[entryName] + ) { + css.push(...serverCSSManifest.__entry_css__[entryName]) + } + } + return css +} + /** * Get inline tags based on server CSS manifest and font loader manifest. Only used when rendering to HTML. */ @@ -542,6 +565,7 @@ function getPreloadedFontFilesInlineLinkTags( serverComponentManifest: FlightManifest, serverCSSManifest: FlightCSSManifest, fontLoaderManifest: FontLoaderManifest | undefined, + serverCSSForEntries: string[], filePath?: string ): string[] { if (!fontLoaderManifest || !filePath) { @@ -558,10 +582,14 @@ function getPreloadedFontFilesInlineLinkTags( const fontFiles = new Set() for (const css of layoutOrPageCss) { - const preloadedFontFiles = fontLoaderManifest.app[css] - if (preloadedFontFiles) { - for (const fontFile of preloadedFontFiles) { - fontFiles.add(fontFile) + // We only include the CSS if it's a global CSS, or it is used by this + // entrypoint. + if (serverCSSForEntries.includes(css) || !/\.module\.css/.test(css)) { + const preloadedFontFiles = fontLoaderManifest.app[css] + if (preloadedFontFiles) { + for (const fontFile of preloadedFontFiles) { + fontFiles.add(fontFile) + } } } } @@ -807,12 +835,9 @@ export async function renderToHTMLOrFlight( stripInternalQueries(query) const LayoutRouter = - ComponentMod.LayoutRouter as typeof import('../client/components/layout-router.client').default + ComponentMod.LayoutRouter as typeof import('../client/components/layout-router').default const RenderFromTemplateContext = - ComponentMod.RenderFromTemplateContext as typeof import('../client/components/render-from-template-context.client').default - const HotReloader = ComponentMod.HotReloader as - | typeof import('../client/components/hot-reloader.client').default - | null + ComponentMod.RenderFromTemplateContext as typeof import('../client/components/render-from-template-context').default /** * Server Context is specifically only available in Server Components. @@ -910,6 +935,15 @@ 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 + ) + /** * Use the provided loader tree to create the React Component tree. */ @@ -943,13 +977,16 @@ export async function renderToHTMLOrFlight( ? getCssInlinedLinkTags( serverComponentManifest, serverCSSManifest!, - layoutOrPagePath + layoutOrPagePath, + serverCSSForEntries ) : [] + const preloadedFontFiles = getPreloadedFontFilesInlineLinkTags( serverComponentManifest, serverCSSManifest!, fontLoaderManifest, + serverCSSForEntries, layoutOrPagePath ) const Template = template @@ -1339,7 +1376,7 @@ export async function renderToHTMLOrFlight( // AppRouter is provided by next-app-loader const AppRouter = - ComponentMod.AppRouter as typeof import('../client/components/app-router.client').default + ComponentMod.AppRouter as typeof import('../client/components/app-router').default let serverComponentsInlinedTransformStream: TransformStream< Uint8Array, @@ -1373,11 +1410,7 @@ export async function renderToHTMLOrFlight( return ( - ) - } + assetPrefix={renderOpts.assetPrefix || ''} initialCanonicalUrl={initialCanonicalUrl} initialTree={initialTree} > @@ -1403,40 +1436,69 @@ export async function renderToHTMLOrFlight( ) return ( - - {children} - + + + {children} + + ) } const bodyResult = async () => { + const polyfills = buildManifest.polyfillFiles + .filter( + (polyfill) => + polyfill.endsWith('.js') && !polyfill.endsWith('.module.js') + ) + .map((polyfill) => ({ + src: `${renderOpts.assetPrefix || ''}/_next/${polyfill}`, + integrity: subresourceIntegrityManifest?.[polyfill], + })) + const content = ( ) + let polyfillsFlushed = false const getServerInsertedHTML = (): Promise => { const flushed = renderToString( <> {Array.from(serverInsertedHTMLCallbacks).map((callback) => callback() )} + {polyfillsFlushed + ? null + : polyfills?.map((polyfill) => { + return ( + ` - ) - .join('') - : '' - // TODO-APP: Insert server side html to end of head in app layout rendering, to avoid // hydration errors. Remove this once it's ready to be handled by react itself. const serverInsertedHTML = getServerInsertedHTML && serverInsertedHTMLToHead ? await getServerInsertedHTML() : '' - return polyfillScripts + serverInsertedHTML + return serverInsertedHTML }), dev ? createRootLayoutValidatorStream() : null, ].filter(nonNullable) diff --git a/packages/next/server/request-meta.ts b/packages/next/server/request-meta.ts index ac3dcad68d525..aa40d1b84952b 100644 --- a/packages/next/server/request-meta.ts +++ b/packages/next/server/request-meta.ts @@ -23,6 +23,7 @@ export interface RequestMeta { _nextRewroteUrl?: string _nextMiddlewareCookie?: string[] _protocol?: string + _nextDataNormalizing?: boolean } export function getRequestMeta( diff --git a/packages/next/server/router.ts b/packages/next/server/router.ts index ca2ce97b1a7dc..a7251b0e1bd55 100644 --- a/packages/next/server/router.ts +++ b/packages/next/server/router.ts @@ -7,7 +7,11 @@ import type { } from '../shared/lib/router/utils/route-matcher' import type { RouteHas } from '../lib/load-custom-routes' -import { getNextInternalQuery, NextUrlWithParsedQuery } from './request-meta' +import { + addRequestMeta, + getNextInternalQuery, + NextUrlWithParsedQuery, +} from './request-meta' import { getPathMatch } from '../shared/lib/router/utils/path-match' import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash' import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path' @@ -189,7 +193,11 @@ export default class Router { ...(middlewareCatchAllRoute ? this.fsRoutes .filter((route) => route.name === '_next/data catchall') - .map((route) => ({ ...route, check: false })) + .map((route) => ({ + ...route, + name: '_next/data normalizing', + check: false, + })) : []), ...this.headers, ...this.redirects, @@ -433,6 +441,11 @@ export default class Router { } if (params) { + const isNextDataNormalizing = route.name === '_next/data normalizing' + + if (isNextDataNormalizing) { + addRequestMeta(req, '_nextDataNormalizing', true) + } parsedUrlUpdated.pathname = matchPathname const result = await route.fn( req, @@ -441,6 +454,10 @@ export default class Router { parsedUrlUpdated, upgradeHead ) + + if (isNextDataNormalizing) { + addRequestMeta(req, '_nextDataNormalizing', false) + } if (result.finished) { return true } diff --git a/packages/next/server/web/adapter.ts b/packages/next/server/web/adapter.ts index 53dcce2e4715e..49a8ec2b52887 100644 --- a/packages/next/server/web/adapter.ts +++ b/packages/next/server/web/adapter.ts @@ -9,6 +9,7 @@ import { relativizeURL } from '../../shared/lib/router/utils/relativize-url' import { waitUntilSymbol } from './spec-extension/fetch-event' import { NextURL } from './next-url' import { stripInternalSearchParams } from '../internal-utils' +import { normalizeRscPath } from '../../shared/lib/router/utils/app-paths' class NextRequestHint extends NextRequest { sourcePage: string @@ -49,6 +50,8 @@ export async function adapter(params: { // TODO-APP: use explicit marker for this const isEdgeRendering = typeof self.__BUILD_MANIFEST !== 'undefined' + params.request.url = normalizeRscPath(params.request.url, true) + const requestUrl = new NextURL(params.request.url, { headers: params.request.headers, nextConfig: params.request.nextConfig, @@ -103,6 +106,11 @@ export async function adapter(params: { const event = new NextFetchEvent({ request, page: params.page }) let response = await params.handler(request, event) + // check if response is a Response object + if (response && !(response instanceof Response)) { + throw new TypeError('Expected an instance of Response to be returned') + } + /** * For rewrites we must always include the locale in the final pathname * so we re-create the NextURL forcing it to include it when the it is @@ -186,6 +194,10 @@ export async function adapter(params: { export function blockUnallowedResponse( promise: Promise ): Promise { + if (process.env.__NEXT_ALLOW_MIDDLEWARE_RESPONSE_BODY) { + return promise + } + return promise.then((result) => { if (result.response?.body) { console.error( diff --git a/packages/next/server/web/next-url.ts b/packages/next/server/web/next-url.ts index 9ed9fc34a4a2c..117667a53ec4c 100644 --- a/packages/next/server/web/next-url.ts +++ b/packages/next/server/web/next-url.ts @@ -72,7 +72,7 @@ export class NextURL { private analyzeUrl() { const pathnameInfo = getNextPathnameInfo(this[Internal].url.pathname, { nextConfig: this[Internal].options.nextConfig, - parseData: true, + parseData: !process.env.__NEXT_NO_MIDDLEWARE_URL_NORMALIZE, }) this[Internal].domainLocale = detectDomainLocale( diff --git a/packages/next/shared/lib/app-router-context.ts b/packages/next/shared/lib/app-router-context.ts index 686a5c3e99508..25620a8ed900d 100644 --- a/packages/next/shared/lib/app-router-context.ts +++ b/packages/next/shared/lib/app-router-context.ts @@ -12,7 +12,7 @@ export type CacheNode = { * In-flight request for this node. */ data: ReturnType< - typeof import('../../client/components/app-router.client').fetchServerResponse + typeof import('../../client/components/app-router').fetchServerResponse > | null /** * React Component for this node. diff --git a/packages/next/shared/lib/head-manager-context.ts b/packages/next/shared/lib/head-manager-context.ts index 7388a1da7b096..f301086cd8ca2 100644 --- a/packages/next/shared/lib/head-manager-context.ts +++ b/packages/next/shared/lib/head-manager-context.ts @@ -6,6 +6,10 @@ export const HeadManagerContext: React.Context<{ updateScripts?: (state: any) => void scripts?: any getIsSsr?: () => boolean + + // Used in app directory, to render script tags as server components. + appDir?: boolean + nonce?: string }> = React.createContext({}) if (process.env.NODE_ENV !== 'production') { diff --git a/packages/next/shared/lib/head.tsx b/packages/next/shared/lib/head.tsx index 526bf8c296050..637c6fed778ea 100644 --- a/packages/next/shared/lib/head.tsx +++ b/packages/next/shared/lib/head.tsx @@ -1,4 +1,4 @@ -'client' +'use client' import React, { useContext } from 'react' import Effect from './side-effect' diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index caba3b9c0bb5d..88912dd18adca 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -1187,7 +1187,7 @@ export default class Router implements BaseRouter { // hydration. Your app should _never_ use this property. It may change at // any time without notice. const isQueryUpdating = (options as any)._h - const shouldResolveHref = + let shouldResolveHref = isQueryUpdating || (options as any)._shouldResolveHref || parsePath(url).pathname === parsePath(as).pathname @@ -1418,6 +1418,10 @@ export default class Router implements BaseRouter { pathname = this.pathname } + if (isQueryUpdating && isMiddlewareMatch) { + shouldResolveHref = false + } + if (shouldResolveHref && pathname !== '/_error') { ;(options as any)._shouldResolveHref = true @@ -1955,12 +1959,14 @@ export default class Router implements BaseRouter { isBackground: isQueryUpdating, } - const data = await withMiddlewareEffects({ - fetchData: () => fetchNextData(fetchNextDataParams), - asPath: resolvedAs, - locale: locale, - router: this, - }) + const data = isQueryUpdating + ? ({} as any) + : await withMiddlewareEffects({ + fetchData: () => fetchNextData(fetchNextDataParams), + asPath: resolvedAs, + locale: locale, + router: this, + }) if (isQueryUpdating && data) { data.json = self.__NEXT_DATA__.props @@ -2079,7 +2085,8 @@ export default class Router implements BaseRouter { if ( !this.isPreview && routeInfo.__N_SSG && - process.env.NODE_ENV !== 'development' + process.env.NODE_ENV !== 'development' && + !isQueryUpdating ) { fetchNextData( Object.assign({}, fetchNextDataParams, { diff --git a/packages/next/shared/lib/router/utils/app-paths.ts b/packages/next/shared/lib/router/utils/app-paths.ts index f48b324d1a6c2..3488d11e9300d 100644 --- a/packages/next/shared/lib/router/utils/app-paths.ts +++ b/packages/next/shared/lib/router/utils/app-paths.ts @@ -21,3 +21,7 @@ export function normalizeAppPath(pathname: string) { return acc + `/${segment}` }, '') } + +export function normalizeRscPath(pathname: string, enabled?: boolean) { + return enabled ? pathname.replace(/\.rsc($|\?)/, '') : pathname +} diff --git a/packages/next/shared/lib/router/utils/format-next-pathname-info.ts b/packages/next/shared/lib/router/utils/format-next-pathname-info.ts index 784bd8adc8d0e..55cdbb05d64dd 100644 --- a/packages/next/shared/lib/router/utils/format-next-pathname-info.ts +++ b/packages/next/shared/lib/router/utils/format-next-pathname-info.ts @@ -17,6 +17,10 @@ export function formatNextPathnameInfo(info: ExtendedInfo) { info.ignorePrefix ) + if (info.buildId || !info.trailingSlash) { + pathname = removeTrailingSlash(pathname) + } + if (info.buildId) { pathname = addPathSuffix( addPathPrefix(pathname, `/_next/data/${info.buildId}`), @@ -25,8 +29,8 @@ export function formatNextPathnameInfo(info: ExtendedInfo) { } pathname = addPathPrefix(pathname, info.basePath) - return info.trailingSlash - ? !info.buildId && !pathname.endsWith('/') + return !info.buildId && info.trailingSlash + ? !pathname.endsWith('/') ? addPathSuffix(pathname, '/') : pathname : removeTrailingSlash(pathname) diff --git a/packages/next/taskfile-swc.js b/packages/next/taskfile-swc.js index ea4bcf40ddec2..22f123159fa3a 100644 --- a/packages/next/taskfile-swc.js +++ b/packages/next/taskfile-swc.js @@ -113,10 +113,10 @@ module.exports = function (task) { const output = yield transform(source, options) const ext = path.extname(file.base) - // Make sure the output content keeps the `"client"` directive. + // Make sure the output content keeps the `"use client"` directive. // TODO: Remove this once SWC fixes the issue. - if (/^['"]client['"]/.test(source)) { - output.code = '"client";\n' + output.code + if (/^['"]use client['"]/.test(source)) { + output.code = '"use client";\n' + output.code } // Replace `.ts|.tsx` with `.js` in files with an extension diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 48189b4fb91b5..b9d1dc7b07716 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -169,6 +169,76 @@ export async function ncc_node_fetch(task, opts) { .target('compiled/node-fetch') } +externals['anser'] = 'next/dist/compiled/anser' +export async function ncc_node_anser(task, opts) { + await task + .source(opts.src || relative(__dirname, require.resolve('anser'))) + .ncc({ packageName: 'anser', externals }) + .target('compiled/anser') +} + +externals['stacktrace-parser'] = 'next/dist/compiled/stacktrace-parser' +export async function ncc_node_stacktrace_parser(task, opts) { + await task + .source( + opts.src || relative(__dirname, require.resolve('stacktrace-parser')) + ) + .ncc({ packageName: 'stacktrace-parser', externals }) + .target('compiled/stacktrace-parser') +} + +externals['data-uri-to-buffer'] = 'next/dist/compiled/data-uri-to-buffer' +export async function ncc_node_data_uri_to_buffer(task, opts) { + await task + .source( + opts.src || relative(__dirname, require.resolve('data-uri-to-buffer')) + ) + .ncc({ packageName: 'data-uri-to-buffer', externals }) + .target('compiled/data-uri-to-buffer') +} + +externals['css.escape'] = 'next/dist/compiled/css.escape' +export async function ncc_node_cssescape(task, opts) { + await task + .source(opts.src || relative(__dirname, require.resolve('css.escape'))) + .ncc({ packageName: 'css.escape', externals }) + .target('compiled/css.escape') +} + +externals['shell-quote'] = 'next/dist/compiled/shell-quote' +export async function ncc_node_shell_quote(task, opts) { + await task + .source(opts.src || relative(__dirname, require.resolve('shell-quote'))) + .ncc({ packageName: 'shell-quote', externals }) + .target('compiled/shell-quote') +} + +externals['platform'] = 'next/dist/compiled/platform' +export async function ncc_node_platform(task, opts) { + await task + .source(opts.src || relative(__dirname, require.resolve('platform'))) + .ncc({ packageName: 'platform', externals }) + .target('compiled/platform') + + const clientFile = join(__dirname, 'compiled/platform/platform.js') + const content = fs.readFileSync(clientFile, 'utf8') + // remove AMD define branch as this forces the module to not + // be treated as commonjs in serverless/client mode + fs.writeFileSync( + clientFile, + content.replace( + new RegExp( + 'if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){r.platform=d;define((function(){return d}))}else '.replace( + /[|\\{}()[\]^$+*?.-]/g, + '\\$&' + ), + 'g' + ), + '' + ) + ) +} + externals['undici'] = 'next/dist/compiled/undici' export async function ncc_undici(task, opts) { await task @@ -1751,6 +1821,7 @@ export async function ncc_mini_css_extract_plugin(task, opts) { }) .target('compiled/mini-css-extract-plugin') } + // eslint-disable-next-line camelcase externals['ua-parser-js'] = 'next/dist/compiled/ua-parser-js' export async function ncc_ua_parser_js(task, opts) { @@ -1849,6 +1920,12 @@ export async function ncc(task, opts) { 'ncc_get_orientation', 'ncc_hapi_accept', 'ncc_node_fetch', + 'ncc_node_anser', + 'ncc_node_stacktrace_parser', + 'ncc_node_data_uri_to_buffer', + 'ncc_node_cssescape', + 'ncc_node_platform', + 'ncc_node_shell_quote', 'ncc_undici', 'ncc_acorn', 'ncc_amphtml_validator', diff --git a/packages/next/types/misc.d.ts b/packages/next/types/misc.d.ts index b02c74daf1e80..fc72746cc1dcc 100644 --- a/packages/next/types/misc.d.ts +++ b/packages/next/types/misc.d.ts @@ -339,6 +339,35 @@ declare module 'next/dist/compiled/@segment/ajv-human-errors' { export = m } +declare module 'next/dist/compiled/stacktrace-parser' { + import * as m from 'stacktrace-parser' + export = m +} + +declare module 'next/dist/compiled/anser' { + import * as m from 'anser' + export = m +} + +declare module 'next/dist/compiled/platform' { + import * as m from 'platform' + export = m +} + +declare module 'next/dist/compiled/css.escape' { + export = CSS.escape +} + +declare module 'next/dist/compiled/data-uri-to-buffer' { + import * as m from 'data-uri-to-buffer' + export = m +} + +declare module 'next/dist/compiled/shell-quote' { + import * as m from 'shell-quote' + export = m +} + declare namespace NodeJS { interface ProcessVersions { pnp?: string diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index f066f0aaf1edb..382f595e7493c 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": "12.3.2-canary.22", + "version": "12.3.2-canary.26", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx b/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx index 21279b730bd59..8db93560df47a 100644 --- a/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx +++ b/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx @@ -60,69 +60,72 @@ const shouldPreventDisplay = ( return preventType.includes(errorType) } -const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({ - children, - preventDisplay, - globalOverlay, -}: { +type ReactDevOverlayProps = { children?: React.ReactNode preventDisplay?: ErrorType[] globalOverlay?: boolean -}) { - const [state, dispatch] = React.useReducer< - React.Reducer - >(reducer, { - nextId: 1, - buildError: null, - errors: [], - }) +} - React.useEffect(() => { - Bus.on(dispatch) - return function () { - Bus.off(dispatch) - } - }, [dispatch]) +const ReactDevOverlay: React.FunctionComponent = + function ReactDevOverlay({ children, preventDisplay, globalOverlay }) { + const [state, dispatch] = React.useReducer< + React.Reducer + >(reducer, { + nextId: 1, + buildError: null, + errors: [], + }) - const onComponentError = React.useCallback( - (_error: Error, _componentStack: string | null) => { - // TODO: special handling - }, - [] - ) + React.useEffect(() => { + Bus.on(dispatch) + return function () { + Bus.off(dispatch) + } + }, [dispatch]) - const hasBuildError = state.buildError != null - const hasRuntimeErrors = Boolean(state.errors.length) + const onComponentError = React.useCallback( + (_error: Error, _componentStack: string | null) => { + // TODO: special handling + }, + [] + ) - const isMounted = hasBuildError || hasRuntimeErrors + const hasBuildError = state.buildError != null + const hasRuntimeErrors = Boolean(state.errors.length) + const errorType = hasBuildError + ? 'build' + : hasRuntimeErrors + ? 'runtime' + : null + const isMounted = errorType !== null - return ( - - - {children ?? null} - - {isMounted ? ( - - - - + return ( + + + {children ?? null} + + {isMounted ? ( + + + + - {shouldPreventDisplay( - hasBuildError ? 'build' : hasRuntimeErrors ? 'runtime' : null, - preventDisplay - ) ? null : hasBuildError ? ( - - ) : hasRuntimeErrors ? ( - - ) : undefined} - - ) : undefined} - - ) -} + {shouldPreventDisplay( + errorType, + preventDisplay + ) ? null : hasBuildError ? ( + + ) : hasRuntimeErrors ? ( + + ) : undefined} + + ) : undefined} + + ) + } export default ReactDevOverlay diff --git a/packages/react-dev-overlay/src/internal/helpers/getRawSourceMap.ts b/packages/react-dev-overlay/src/internal/helpers/getRawSourceMap.ts index a073a885598c1..98e09489bb58b 100644 --- a/packages/react-dev-overlay/src/internal/helpers/getRawSourceMap.ts +++ b/packages/react-dev-overlay/src/internal/helpers/getRawSourceMap.ts @@ -1,5 +1,5 @@ import dataUriToBuffer, { MimeBuffer } from 'data-uri-to-buffer' -import { RawSourceMap } from 'source-map' +import type { RawSourceMap } from 'source-map' import { getSourceMapUrl } from './getSourceMapUrl' export function getRawSourceMap(fileContents: string): RawSourceMap | null { diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index a3882bc2d94e2..9320a2fa84806 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -2,11 +2,8 @@ import { codeFrameColumns } from '@babel/code-frame' import { constants as FS, promises as fs } from 'fs' import { IncomingMessage, ServerResponse } from 'http' import path from 'path' -import { - NullableMappedPosition, - RawSourceMap, - SourceMapConsumer, -} from 'source-map' +import type { NullableMappedPosition, RawSourceMap } from 'source-map' +import { SourceMapConsumer } from 'source-map' import { StackFrame } from 'stacktrace-parser' import url from 'url' // @ts-ignore diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 2a7b8d77633ff..897f843992a29 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": "12.3.2-canary.22", + "version": "12.3.2-canary.26", "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 183412b1d9ef6..ad028f40f4844 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -397,7 +397,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 12.3.2-canary.22 + '@next/eslint-plugin-next': 12.3.2-canary.26 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.21.0 eslint-import-resolver-node: ^0.3.6 @@ -429,7 +429,14 @@ importers: eslint: 7.24.0 packages/font: - specifiers: {} + specifiers: + '@types/fontkit': 2.0.0 + '@vercel/ncc': 0.34.0 + fontkit: 2.0.2 + devDependencies: + '@types/fontkit': 2.0.0 + '@vercel/ncc': 0.34.0 + fontkit: 2.0.2 packages/next: specifiers: @@ -458,12 +465,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.7.0 '@napi-rs/triples': 1.1.0 - '@next/env': 12.3.2-canary.22 - '@next/polyfill-module': 12.3.2-canary.22 - '@next/polyfill-nomodule': 12.3.2-canary.22 - '@next/react-dev-overlay': 12.3.2-canary.22 - '@next/react-refresh-utils': 12.3.2-canary.22 - '@next/swc': 12.3.2-canary.22 + '@next/env': 12.3.2-canary.26 + '@next/polyfill-module': 12.3.2-canary.26 + '@next/polyfill-nomodule': 12.3.2-canary.26 + '@next/react-dev-overlay': 12.3.2-canary.26 + '@next/react-refresh-utils': 12.3.2-canary.26 + '@next/swc': 12.3.2-canary.26 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.11 '@taskr/clear': 1.1.0 @@ -492,11 +499,13 @@ importers: '@types/micromatch': 4.0.2 '@types/node-fetch': 2.6.1 '@types/path-to-regexp': 1.7.0 + '@types/platform': 1.3.4 '@types/react': 16.9.17 '@types/react-dom': 16.9.4 '@types/react-is': 16.7.1 '@types/semver': 7.3.1 '@types/send': 0.14.4 + '@types/shell-quote': 1.7.1 '@types/tar': 4.0.3 '@types/text-table': 0.2.1 '@types/ua-parser-js': 0.7.36 @@ -508,6 +517,7 @@ importers: acorn: 8.5.0 ajv: 8.11.0 amphtml-validator: 1.0.35 + anser: 1.4.9 arg: 4.1.0 assert: 2.0.0 async-retry: 1.2.3 @@ -531,7 +541,9 @@ importers: cookie: 0.4.1 cross-spawn: 6.0.5 crypto-browserify: 3.12.0 + css.escape: 1.5.1 cssnano-simple: 3.0.1 + data-uri-to-buffer: 3.0.1 debug: 4.1.1 devalue: 2.0.1 domain-browser: 4.19.0 @@ -569,6 +581,7 @@ importers: p-limit: 3.1.0 path-browserify: 1.0.1 path-to-regexp: 6.1.0 + platform: 1.3.6 postcss: 8.4.14 postcss-flexbugs-fixes: 5.0.2 postcss-modules-extract-imports: 3.0.0 @@ -593,7 +606,9 @@ importers: semver: 7.3.2 send: 0.17.1 setimmediate: 1.0.5 + shell-quote: 1.7.3 source-map: 0.6.1 + stacktrace-parser: 0.1.10 stream-browserify: 3.0.0 stream-http: 3.1.1 string_decoder: 1.3.0 @@ -684,11 +699,13 @@ importers: '@types/micromatch': 4.0.2 '@types/node-fetch': 2.6.1 '@types/path-to-regexp': 1.7.0 + '@types/platform': 1.3.4 '@types/react': 16.9.17 '@types/react-dom': 16.9.4 '@types/react-is': 16.7.1 '@types/semver': 7.3.1 '@types/send': 0.14.4 + '@types/shell-quote': 1.7.1 '@types/tar': 4.0.3 '@types/text-table': 0.2.1 '@types/ua-parser-js': 0.7.36 @@ -700,6 +717,7 @@ importers: acorn: 8.5.0 ajv: 8.11.0 amphtml-validator: 1.0.35 + anser: 1.4.9 arg: 4.1.0 assert: 2.0.0 async-retry: 1.2.3 @@ -722,7 +740,9 @@ importers: cookie: 0.4.1 cross-spawn: 6.0.5 crypto-browserify: 3.12.0 + css.escape: 1.5.1 cssnano-simple: 3.0.1_postcss@8.4.14 + data-uri-to-buffer: 3.0.1 debug: 4.1.1 devalue: 2.0.1 domain-browser: 4.19.0 @@ -760,6 +780,7 @@ importers: p-limit: 3.1.0 path-browserify: 1.0.1 path-to-regexp: 6.1.0 + platform: 1.3.6 postcss-flexbugs-fixes: 5.0.2_postcss@8.4.14 postcss-modules-extract-imports: 3.0.0_postcss@8.4.14 postcss-modules-local-by-default: 4.0.0_postcss@8.4.14 @@ -783,7 +804,9 @@ importers: semver: 7.3.2 send: 0.17.1 setimmediate: 1.0.5 + shell-quote: 1.7.3 source-map: 0.6.1 + stacktrace-parser: 0.1.10 stream-browserify: 3.0.0 stream-http: 3.1.1 string_decoder: 1.3.0 @@ -7343,6 +7366,15 @@ packages: } dev: true + /@types/fontkit/2.0.0: + resolution: + { + integrity: sha512-Qe+6szpPLTNsqkDFs2MScJyB51d5Hpobyg/T0QoUWO53WuNOTNLsV8fkE4QQPOJbhOMN5wlwmNeDdsh/e6Uqdg==, + } + dependencies: + '@types/node': 17.0.21 + dev: true + /@types/fresh/0.5.0: resolution: { @@ -7670,6 +7702,13 @@ packages: path-to-regexp: 6.1.0 dev: true + /@types/platform/1.3.4: + resolution: + { + integrity: sha512-U0o4K+GNiK0PNxoDwd8xRnvLVe4kzei6opn3/FCjAriqaP+rfrDdSl1kP/hLL6Y3/Y3hhGnBwD4dCkkAqs1W/Q==, + } + dev: true + /@types/prettier/2.2.3: resolution: { @@ -7829,6 +7868,13 @@ packages: '@types/node': 17.0.21 dev: true + /@types/shell-quote/1.7.1: + resolution: + { + integrity: sha512-SWZ2Nom1pkyXCDohRSrkSKvDh8QOG9RfAsrt5/NsPQC4UQJ55eG0qClA40I+Gkez4KTQ0uDUT8ELRXThf3J5jw==, + } + dev: true + /@types/source-list-map/0.1.2: resolution: { @@ -8770,7 +8816,6 @@ packages: { integrity: sha512-AI+BjTeGt2+WFk4eWcqbQ7snZpDBt8SaLlj0RT2h5xfdWaiy51OjYvqwMrNzJLGy8iOAL6nKDITWO+rd4MkYEA==, } - dev: false /ansi-align/3.0.0: resolution: @@ -10029,6 +10074,15 @@ packages: duplexer: 0.1.1 dev: true + /brotli/1.3.3: + resolution: + { + integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==, + } + dependencies: + base64-js: 1.5.1 + dev: true + /browser-process-hrtime/1.0.0: resolution: { @@ -11089,6 +11143,14 @@ packages: engines: { node: '>=0.8' } dev: true + /clone/2.1.2: + resolution: + { + integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==, + } + engines: { node: '>=0.8' } + dev: true + /clor/5.2.0: resolution: { @@ -12209,8 +12271,10 @@ packages: dev: true /css.escape/1.5.1: - resolution: { integrity: sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= } - dev: false + resolution: + { + integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==, + } /css/3.0.0: resolution: @@ -12501,7 +12565,6 @@ packages: integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==, } engines: { node: '>= 6' } - dev: false /data-urls/2.0.0: resolution: @@ -12995,6 +13058,13 @@ packages: wrappy: 1.0.2 dev: true + /dfa/1.2.0: + resolution: + { + integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==, + } + dev: true + /diagnostics_channel/1.1.0: resolution: { @@ -15208,6 +15278,23 @@ packages: - supports-color dev: true + /fontkit/2.0.2: + resolution: + { + integrity: sha512-jc4k5Yr8iov8QfS6u8w2CnHWVmbOGtdBtOXMze5Y+QD966Rx6PEVWXSEGwXlsDlKtu1G12cJjcsybnqhSk/+LA==, + } + dependencies: + '@swc/helpers': 0.4.11 + brotli: 1.3.3 + clone: 2.1.2 + dfa: 1.2.0 + fast-deep-equal: 3.1.3 + restructure: 3.0.0 + tiny-inflate: 1.0.3 + unicode-properties: 1.4.1 + unicode-trie: 2.0.0 + dev: true + /for-in/1.0.2: resolution: { @@ -22820,6 +22907,13 @@ packages: - supports-color dev: true + /pako/0.2.9: + resolution: + { + integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==, + } + dev: true + /pako/1.0.11: resolution: { @@ -23398,7 +23492,6 @@ packages: { integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==, } - dev: false /playwright-chromium/1.22.2: resolution: @@ -26335,6 +26428,13 @@ packages: signal-exit: 3.0.3 dev: true + /restructure/3.0.0: + resolution: + { + integrity: sha512-Xj8/MEIhhfj9X2rmD9iJ4Gga9EFqVlpMj3vfLnV2r/Mh5jRMryNV+6lWh9GdJtDBcBSPIqzRdfBQ3wDtNFv/uw==, + } + dev: true + /ret/0.1.15: resolution: { @@ -27562,7 +27662,6 @@ packages: engines: { node: '>=6' } dependencies: type-fest: 0.7.1 - dev: false /state-toggle/1.0.2: resolution: @@ -28592,6 +28691,13 @@ packages: globrex: 0.1.2 dev: true + /tiny-inflate/1.0.3: + resolution: + { + integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==, + } + dev: true + /tinydate/1.2.0: resolution: { @@ -29083,7 +29189,6 @@ packages: integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==, } engines: { node: '>=8' } - dev: false /type-fest/0.8.1: resolution: @@ -29275,6 +29380,16 @@ packages: } engines: { node: '>=4' } + /unicode-properties/1.4.1: + resolution: + { + integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==, + } + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + dev: true + /unicode-property-aliases-ecmascript/1.0.5: resolution: { @@ -29290,6 +29405,16 @@ packages: } engines: { node: '>=4' } + /unicode-trie/2.0.0: + resolution: + { + integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==, + } + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + dev: true + /unified-diff/3.1.0: resolution: { diff --git a/test/e2e/app-dir/app-edge/app/app-edge/layout.tsx b/test/e2e/app-dir/app-edge/app/app-edge/layout.tsx index 0b279c322ccbc..940e1641eebc0 100644 --- a/test/e2e/app-dir/app-edge/app/app-edge/layout.tsx +++ b/test/e2e/app-dir/app-edge/app/app-edge/layout.tsx @@ -1,4 +1,4 @@ -'client' +'use client' // TODO-APP: support typing for useSelectedLayoutSegment // @ts-ignore diff --git a/test/e2e/app-dir/app/app/catch-all-link/page.js b/test/e2e/app-dir/app/app/catch-all-link/page.js new file mode 100644 index 0000000000000..59157f985f1e7 --- /dev/null +++ b/test/e2e/app-dir/app/app/catch-all-link/page.js @@ -0,0 +1,18 @@ +import Link from 'next/link' + +export default function Page() { + return ( + <> +
    + + To catch-all + +
    + + + ) +} diff --git a/test/e2e/app-dir/app/app/optional-catch-all/[[...slug]]/page.js b/test/e2e/app-dir/app/app/catch-all-optional/[[...slug]]/page.js similarity index 68% rename from test/e2e/app-dir/app/app/optional-catch-all/[[...slug]]/page.js rename to test/e2e/app-dir/app/app/catch-all-optional/[[...slug]]/page.js index c02675e1cc00a..7a7a7b4d92dd0 100644 --- a/test/e2e/app-dir/app/app/optional-catch-all/[[...slug]]/page.js +++ b/test/e2e/app-dir/app/app/catch-all-optional/[[...slug]]/page.js @@ -1,7 +1,7 @@ export default function Page({ params }) { return (

    - hello from /optional-catch-all/{params.slug?.join('/')} + hello from /catch-all-optional/{params.slug?.join('/')}

    ) } diff --git a/test/e2e/app-dir/app/app/client-component-route/page.js b/test/e2e/app-dir/app/app/client-component-route/page.js index 5e2c815adf49a..95f295253387a 100644 --- a/test/e2e/app-dir/app/app/client-component-route/page.js +++ b/test/e2e/app-dir/app/app/client-component-route/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { useState, useEffect } from 'react' diff --git a/test/e2e/app-dir/app/app/client-nested/layout.js b/test/e2e/app-dir/app/app/client-nested/layout.js index 506d2370b6e35..51cc84b7b669e 100644 --- a/test/e2e/app-dir/app/app/client-nested/layout.js +++ b/test/e2e/app-dir/app/app/client-nested/layout.js @@ -1,4 +1,4 @@ -'client' +'use client' import { useState, useEffect } from 'react' diff --git a/test/e2e/app-dir/app/app/client-with-errors/get-server-side-props/page.js b/test/e2e/app-dir/app/app/client-with-errors/get-server-side-props/page.js index 8ea1ccd889904..04ddb3c28ccf7 100644 --- a/test/e2e/app-dir/app/app/client-with-errors/get-server-side-props/page.js +++ b/test/e2e/app-dir/app/app/client-with-errors/get-server-side-props/page.js @@ -1,4 +1,4 @@ -'client' +'use client' // export function getServerSideProps() { { props: {} } } diff --git a/test/e2e/app-dir/app/app/client-with-errors/get-static-props/page.js b/test/e2e/app-dir/app/app/client-with-errors/get-static-props/page.js index 70911d5f16fb9..43d10f1ea56b7 100644 --- a/test/e2e/app-dir/app/app/client-with-errors/get-static-props/page.js +++ b/test/e2e/app-dir/app/app/client-with-errors/get-static-props/page.js @@ -1,4 +1,4 @@ -'client' +'use client' // export function getStaticProps() { return { props: {} }} diff --git a/test/e2e/app-dir/app/app/css/css-client/layout.js b/test/e2e/app-dir/app/app/css/css-client/layout.js index ab96419298190..f460e79edb3c6 100644 --- a/test/e2e/app-dir/app/app/css/css-client/layout.js +++ b/test/e2e/app-dir/app/app/css/css-client/layout.js @@ -1,4 +1,4 @@ -'client' +'use client' import './client-layout.css' diff --git a/test/e2e/app-dir/app/app/css/css-client/page.js b/test/e2e/app-dir/app/app/css/css-client/page.js index 1e92db88c5923..928ff6bf0e56f 100644 --- a/test/e2e/app-dir/app/app/css/css-client/page.js +++ b/test/e2e/app-dir/app/app/css/css-client/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import './client-page.css' diff --git a/test/e2e/app-dir/app/app/css/css-nested/layout.js b/test/e2e/app-dir/app/app/css/css-nested/layout.js index 8f2a9f56ada61..348b4473072e2 100644 --- a/test/e2e/app-dir/app/app/css/css-nested/layout.js +++ b/test/e2e/app-dir/app/app/css/css-nested/layout.js @@ -1,4 +1,4 @@ -'client' +'use client' import './style.css' import styles from './style.module.css' diff --git a/test/e2e/app-dir/app/app/css/css-nested/page.js b/test/e2e/app-dir/app/app/css/css-nested/page.js index baf7462f9d6a3..47bf2fce4f1ab 100644 --- a/test/e2e/app-dir/app/app/css/css-nested/page.js +++ b/test/e2e/app-dir/app/app/css/css-nested/page.js @@ -1,4 +1,4 @@ -'client' +'use client' export default function Page() { return null diff --git a/test/e2e/app-dir/app/app/css/css-page/unused-nested/inner/page.js b/test/e2e/app-dir/app/app/css/css-page/unused-nested/inner/page.js new file mode 100644 index 0000000000000..6878a343aa179 --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-page/unused-nested/inner/page.js @@ -0,0 +1,3 @@ +export default function Inner() { + return

    Inner Page

    +} diff --git a/test/e2e/app-dir/app/app/css/css-page/unused-nested/layout.js b/test/e2e/app-dir/app/app/css/css-page/unused-nested/layout.js new file mode 100644 index 0000000000000..38e985058500c --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-page/unused-nested/layout.js @@ -0,0 +1,10 @@ +import { layout } from './styles' + +export default function ServerLayout({ children }) { + return ( + <> +

    Layout

    + {children} + + ) +} diff --git a/test/e2e/app-dir/app/app/css/css-page/unused-nested/only-used-in-first-page.module.css b/test/e2e/app-dir/app/app/css/css-page/unused-nested/only-used-in-first-page.module.css new file mode 100644 index 0000000000000..c71dcc347dbe0 --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-page/unused-nested/only-used-in-first-page.module.css @@ -0,0 +1,3 @@ +.this_should_not_be_included_in_inner_path { + color: wheat; +} diff --git a/test/e2e/app-dir/app/app/css/css-page/unused-nested/only-used-in-layout.module.css b/test/e2e/app-dir/app/app/css/css-page/unused-nested/only-used-in-layout.module.css new file mode 100644 index 0000000000000..43b5b99d39ebc --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-page/unused-nested/only-used-in-layout.module.css @@ -0,0 +1,3 @@ +.mod { + color: tomato; +} diff --git a/test/e2e/app-dir/app/app/css/css-page/unused-nested/page.js b/test/e2e/app-dir/app/app/css/css-page/unused-nested/page.js new file mode 100644 index 0000000000000..531ae6e120a5f --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-page/unused-nested/page.js @@ -0,0 +1,5 @@ +import { page } from './styles' + +export default function Page() { + return

    Page

    +} diff --git a/test/e2e/app-dir/app/app/css/css-page/unused-nested/styles.js b/test/e2e/app-dir/app/app/css/css-page/unused-nested/styles.js new file mode 100644 index 0000000000000..f649cef70ea2e --- /dev/null +++ b/test/e2e/app-dir/app/app/css/css-page/unused-nested/styles.js @@ -0,0 +1,4 @@ +import layout from './only-used-in-layout.module.css' +import page from './only-used-in-first-page.module.css' + +export { layout, page } diff --git a/test/e2e/app-dir/app/app/dashboard/client-comp-client.jsx b/test/e2e/app-dir/app/app/dashboard/client-comp-client.jsx index 79c317369816f..30ff456074370 100644 --- a/test/e2e/app-dir/app/app/dashboard/client-comp-client.jsx +++ b/test/e2e/app-dir/app/app/dashboard/client-comp-client.jsx @@ -1,4 +1,4 @@ -'client' +'use client' import styles from './client-comp.module.css' diff --git a/test/e2e/app-dir/app/app/dashboard/deployments/info/[id]/page.js b/test/e2e/app-dir/app/app/dashboard/deployments/info/[id]/page.js new file mode 100644 index 0000000000000..df11944f5ad10 --- /dev/null +++ b/test/e2e/app-dir/app/app/dashboard/deployments/info/[id]/page.js @@ -0,0 +1,9 @@ +export default function Page({ params }) { + return ( + <> +

    + hello from app/dashboard/deployments/info/[id]. ID is: {params.id} +

    + + ) +} diff --git a/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-client.js b/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-client.js index 2d0f36e76be8e..bfa17f6a14b67 100644 --- a/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-client.js +++ b/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-client.js @@ -1,4 +1,4 @@ -'client' +'use client' import dynamic from 'next/dynamic' diff --git a/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/react-lazy-client.js b/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/react-lazy-client.js index 18fff4ea973d6..e8010fd28c73b 100644 --- a/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/react-lazy-client.js +++ b/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/react-lazy-client.js @@ -1,11 +1,11 @@ -'client' +'use client' import { useState, lazy } from 'react' const Lazy = lazy(() => import('../text-lazy-client.js')) export function LazyClientComponent() { - let [state] = useState('client') + let [state] = useState('use client') return ( <> diff --git a/test/e2e/app-dir/app/app/dashboard/index/text-dynamic-client.js b/test/e2e/app-dir/app/app/dashboard/index/text-dynamic-client.js index 6661c55534fcf..55479cd44bb72 100644 --- a/test/e2e/app-dir/app/app/dashboard/index/text-dynamic-client.js +++ b/test/e2e/app-dir/app/app/dashboard/index/text-dynamic-client.js @@ -1,4 +1,4 @@ -'client' +'use client' import { useState } from 'react' import styles from './dynamic.module.css' diff --git a/test/e2e/app-dir/app/app/dashboard/index/text-lazy-client.js b/test/e2e/app-dir/app/app/dashboard/index/text-lazy-client.js index ead59b0aa2305..53a7186881151 100644 --- a/test/e2e/app-dir/app/app/dashboard/index/text-lazy-client.js +++ b/test/e2e/app-dir/app/app/dashboard/index/text-lazy-client.js @@ -1,4 +1,4 @@ -'client' +'use client' import styles from './lazy.module.css' diff --git a/test/e2e/app-dir/app/app/error/clientcomponent/error.js b/test/e2e/app-dir/app/app/error/client-component/error.js similarity index 95% rename from test/e2e/app-dir/app/app/error/clientcomponent/error.js rename to test/e2e/app-dir/app/app/error/client-component/error.js index 57ad9184f1f81..a572d9d228398 100644 --- a/test/e2e/app-dir/app/app/error/clientcomponent/error.js +++ b/test/e2e/app-dir/app/app/error/client-component/error.js @@ -1,4 +1,4 @@ -'client' +'use client' export default function ErrorBoundary({ error, reset }) { return ( diff --git a/test/e2e/app-dir/app/app/error/clientcomponent/page.js b/test/e2e/app-dir/app/app/error/client-component/page.js similarity index 96% rename from test/e2e/app-dir/app/app/error/clientcomponent/page.js rename to test/e2e/app-dir/app/app/error/client-component/page.js index 5f4e73da9dc13..a7801e4f117be 100644 --- a/test/e2e/app-dir/app/app/error/clientcomponent/page.js +++ b/test/e2e/app-dir/app/app/error/client-component/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { useState } from 'react' diff --git a/test/e2e/app-dir/app/app/error/global-error-boundary/page.js b/test/e2e/app-dir/app/app/error/global-error-boundary/page.js new file mode 100644 index 0000000000000..a7801e4f117be --- /dev/null +++ b/test/e2e/app-dir/app/app/error/global-error-boundary/page.js @@ -0,0 +1,20 @@ +'use client' + +import { useState } from 'react' + +export default function Page() { + const [clicked, setClicked] = useState(false) + if (clicked) { + throw new Error('this is a test') + } + return ( + + ) +} diff --git a/test/e2e/app-dir/app/app/error/ssr-error-client-component/client-component.js b/test/e2e/app-dir/app/app/error/ssr-error-client-component/client-component.js index 7743a313b4754..92a343352a73f 100644 --- a/test/e2e/app-dir/app/app/error/ssr-error-client-component/client-component.js +++ b/test/e2e/app-dir/app/app/error/ssr-error-client-component/client-component.js @@ -1,4 +1,4 @@ -'client' +'use client' export default function Page() { throw new Error('Error during SSR') diff --git a/test/e2e/app-dir/app/app/hooks/use-cookies/client/page.js b/test/e2e/app-dir/app/app/hooks/use-cookies/client/page.js index 1b5398784bb1d..503fc70aef887 100644 --- a/test/e2e/app-dir/app/app/hooks/use-cookies/client/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-cookies/client/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { cookies } from 'next/dist/client/components/hooks-server' diff --git a/test/e2e/app-dir/app/app/hooks/use-headers/client/page.js b/test/e2e/app-dir/app/app/hooks/use-headers/client/page.js index 914cfe4817a8c..7d4faecb99448 100644 --- a/test/e2e/app-dir/app/app/hooks/use-headers/client/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-headers/client/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { headers } from 'next/dist/client/components/hooks-server' diff --git a/test/e2e/app-dir/app/app/hooks/use-layout-segments/server/page.js b/test/e2e/app-dir/app/app/hooks/use-layout-segments/server/page.js index 8ffb8c9e59cd4..fee7b8c7511d5 100644 --- a/test/e2e/app-dir/app/app/hooks/use-layout-segments/server/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-layout-segments/server/page.js @@ -1,4 +1,4 @@ -'client' +'use client' // TODO-APP: enable once test is not skipped. // import { useLayoutSegments } from 'next/dist/client/components/hooks-client' diff --git a/test/e2e/app-dir/app/app/hooks/use-pathname/page.js b/test/e2e/app-dir/app/app/hooks/use-pathname/page.js index fa8350a8533a8..537717dbb0859 100644 --- a/test/e2e/app-dir/app/app/hooks/use-pathname/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-pathname/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { usePathname } from 'next/dist/client/components/hooks-client' diff --git a/test/e2e/app-dir/app/app/hooks/use-preview-data/client/page.js b/test/e2e/app-dir/app/app/hooks/use-preview-data/client/page.js index 84ef213cbc0bf..15e459553b05f 100644 --- a/test/e2e/app-dir/app/app/hooks/use-preview-data/client/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-preview-data/client/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { previewData } from 'next/dist/client/components/hooks-server' diff --git a/test/e2e/app-dir/app/app/hooks/use-router/page.js b/test/e2e/app-dir/app/app/hooks/use-router/page.js index 51a14d940b8b8..fb60221475eac 100644 --- a/test/e2e/app-dir/app/app/hooks/use-router/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-router/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { useRouter } from 'next/dist/client/components/hooks-client' diff --git a/test/e2e/app-dir/app/app/hooks/use-router/sub-page/page.js b/test/e2e/app-dir/app/app/hooks/use-router/sub-page/page.js index 82c6347e59fb0..5d4d6fb4b4f13 100644 --- a/test/e2e/app-dir/app/app/hooks/use-router/sub-page/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-router/sub-page/page.js @@ -1,4 +1,4 @@ -'client' +'use client' export default function Page() { return

    hello from /hooks/use-router/sub-page

    diff --git a/test/e2e/app-dir/app/app/hooks/use-search-params/page.js b/test/e2e/app-dir/app/app/hooks/use-search-params/page.js index d9876c9c7b201..e4b71bca469c7 100644 --- a/test/e2e/app-dir/app/app/hooks/use-search-params/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-search-params/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { useSearchParams } from 'next/dist/client/components/hooks-client' diff --git a/test/e2e/app-dir/app/app/hooks/use-selected-layout-segment/server/page.js b/test/e2e/app-dir/app/app/hooks/use-selected-layout-segment/server/page.js index a5d718cbeaca6..5b80bef500d4b 100644 --- a/test/e2e/app-dir/app/app/hooks/use-selected-layout-segment/server/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-selected-layout-segment/server/page.js @@ -1,4 +1,4 @@ -'client' +'use client' // TODO-APP: enable once test is not skipped. // import { useSelectedLayoutSegment } from 'next/dist/client/components/hooks-client' diff --git a/test/e2e/app-dir/app/app/link-with-as/page.js b/test/e2e/app-dir/app/app/link-with-as/page.js new file mode 100644 index 0000000000000..12a33746cc9fe --- /dev/null +++ b/test/e2e/app-dir/app/app/link-with-as/page.js @@ -0,0 +1,14 @@ +import Link from 'next/link' + +export default function Page() { + return ( + <> + + To info 123 + + + ) +} diff --git a/test/e2e/app-dir/app/app/linking/about/page.js b/test/e2e/app-dir/app/app/linking/about/page.js new file mode 100644 index 0000000000000..dfc1aee0840f9 --- /dev/null +++ b/test/e2e/app-dir/app/app/linking/about/page.js @@ -0,0 +1,3 @@ +export default function AboutPage() { + return

    About page

    +} diff --git a/test/e2e/app-dir/app/app/linking/layout.js b/test/e2e/app-dir/app/app/linking/layout.js new file mode 100644 index 0000000000000..f836cd6b0cc41 --- /dev/null +++ b/test/e2e/app-dir/app/app/linking/layout.js @@ -0,0 +1,15 @@ +import Link from 'next/link' + +export default function Layout({ children }) { + return ( + <> +
    + +
    + {children} + + ) +} diff --git a/test/e2e/app-dir/app/app/linking/page.js b/test/e2e/app-dir/app/app/linking/page.js new file mode 100644 index 0000000000000..ee48946c44f5e --- /dev/null +++ b/test/e2e/app-dir/app/app/linking/page.js @@ -0,0 +1,3 @@ +export default function Page() { + return

    Home page

    +} diff --git a/test/e2e/app-dir/app/app/navigation/link.js b/test/e2e/app-dir/app/app/navigation/link.js index 9d39caab31586..0b1f2f0502eb6 100644 --- a/test/e2e/app-dir/app/app/navigation/link.js +++ b/test/e2e/app-dir/app/app/navigation/link.js @@ -1,4 +1,4 @@ -'client' +'use client' import { useRouter } from 'next/dist/client/components/hooks-client' import React from 'react' diff --git a/test/e2e/app-dir/app/app/nested-navigation/CategoryNav.js b/test/e2e/app-dir/app/app/nested-navigation/CategoryNav.js index 718d82ec07ffd..dd05a8cbb070a 100644 --- a/test/e2e/app-dir/app/app/nested-navigation/CategoryNav.js +++ b/test/e2e/app-dir/app/app/nested-navigation/CategoryNav.js @@ -1,4 +1,4 @@ -'client' +'use client' import { TabNavItem } from './TabNavItem' import { useSelectedLayoutSegment } from 'next/dist/client/components/hooks-client' diff --git a/test/e2e/app-dir/app/app/nested-navigation/[categorySlug]/SubCategoryNav.js b/test/e2e/app-dir/app/app/nested-navigation/[categorySlug]/SubCategoryNav.js index 4118c34c77fc4..8fb95716b235d 100644 --- a/test/e2e/app-dir/app/app/nested-navigation/[categorySlug]/SubCategoryNav.js +++ b/test/e2e/app-dir/app/app/nested-navigation/[categorySlug]/SubCategoryNav.js @@ -1,4 +1,4 @@ -'client' +'use client' import { TabNavItem } from '../TabNavItem' import { useSelectedLayoutSegment } from 'next/dist/client/components/hooks-client' diff --git a/test/e2e/app-dir/app/app/not-found/client-side/page.js b/test/e2e/app-dir/app/app/not-found/client-side/page.js index faca8e33aaf63..7391f6444614a 100644 --- a/test/e2e/app-dir/app/app/not-found/client-side/page.js +++ b/test/e2e/app-dir/app/app/not-found/client-side/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { notFound } from 'next/dist/client/components/not-found' import React from 'react' diff --git a/test/e2e/app-dir/app/app/not-found/clientcomponent/client-component.js b/test/e2e/app-dir/app/app/not-found/clientcomponent/client-component.js index 7469f1ca68690..53abafe80e120 100644 --- a/test/e2e/app-dir/app/app/not-found/clientcomponent/client-component.js +++ b/test/e2e/app-dir/app/app/not-found/clientcomponent/client-component.js @@ -1,4 +1,4 @@ -'client' +'use client' import { notFound } from 'next/dist/client/components/not-found' export default function ClientComp() { diff --git a/test/e2e/app-dir/app/app/old-router/client-router.js b/test/e2e/app-dir/app/app/old-router/client-router.js index 3dc229767c4fa..f9b0e84a71cba 100644 --- a/test/e2e/app-dir/app/app/old-router/client-router.js +++ b/test/e2e/app-dir/app/app/old-router/client-router.js @@ -1,4 +1,4 @@ -'client' +'use client' import { useRouter, withRouter } from 'next/router' import IsNull from './is-null' diff --git a/test/e2e/app-dir/app/app/param-and-query/[slug]/page.js b/test/e2e/app-dir/app/app/param-and-query/[slug]/page.js index c778751859472..fab82d7a8a270 100644 --- a/test/e2e/app-dir/app/app/param-and-query/[slug]/page.js +++ b/test/e2e/app-dir/app/app/param-and-query/[slug]/page.js @@ -1,4 +1,4 @@ -'client' +'use client' export default function Page({ params, searchParams }) { return ( diff --git a/test/e2e/app-dir/app/app/redirect/client-side/page.js b/test/e2e/app-dir/app/app/redirect/client-side/page.js index 1d11b4c62f302..3c0f69375be4b 100644 --- a/test/e2e/app-dir/app/app/redirect/client-side/page.js +++ b/test/e2e/app-dir/app/app/redirect/client-side/page.js @@ -1,4 +1,4 @@ -'client' +'use client' import { redirect } from 'next/dist/client/components/redirect' import React from 'react' diff --git a/test/e2e/app-dir/app/app/redirect/clientcomponent/client-component.js b/test/e2e/app-dir/app/app/redirect/clientcomponent/client-component.js index 5e7cb994fdc0f..4fad1581efdee 100644 --- a/test/e2e/app-dir/app/app/redirect/clientcomponent/client-component.js +++ b/test/e2e/app-dir/app/app/redirect/clientcomponent/client-component.js @@ -1,4 +1,4 @@ -'client' +'use client' import { redirect } from 'next/dist/client/components/redirect' export default function ClientComp() { diff --git a/test/e2e/app-dir/app/app/script/client.js b/test/e2e/app-dir/app/app/script/client.js new file mode 100644 index 0000000000000..5372bb0d483b4 --- /dev/null +++ b/test/e2e/app-dir/app/app/script/client.js @@ -0,0 +1,15 @@ +'use client' + +export default function Client() { + if (typeof window !== 'undefined') { + window._script_order = window._script_order || [] + + if (window._script_order[window._script_order.length - 1] !== 'render') { + window._script_order.push('render') + } + + console.log(window._script_order) + } + + return null +} diff --git a/test/e2e/app-dir/app/app/script/page.js b/test/e2e/app-dir/app/app/script/page.js new file mode 100644 index 0000000000000..2ee6b4c2734a2 --- /dev/null +++ b/test/e2e/app-dir/app/app/script/page.js @@ -0,0 +1,30 @@ +import Script from 'next/script' + +import Client from './client' + +export default function Page() { + return ( +
    +

    next/script

    + + +