diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index 4c0d08bc6cd61..2ecfe0c58326e 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -33,13 +33,13 @@ body: - 'ESLint (eslint-config-next)' - 'Font optimization (@next/font)' - 'Head component/file (next/head / head.js)' - - 'Internationalzation (i18n)' - - 'Image optmization (next/image, next/legacy/image)' + - 'Internationalization (i18n)' + - 'Image optimization (next/image, next/legacy/image)' - 'Jest (next/jest)' - 'Middleware / Edge (API routes, runtime)' - 'Package manager (npm, pnpm, Yarn)' - 'Routing (next/router, next/navigation, next/link)' - - 'Script optimizatzion (next/script)' + - 'Script optimization (next/script)' - 'Standalone mode (output: "standalone")' - 'Static HTML Export (next export)' - 'SWC minifier (swcMinify: true)' diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index a359ba20d137f..af7ec8598aa5b 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -261,15 +261,8 @@ jobs: fail-fast: false matrix: node: [16, 18] - group: [1, 2] + group: [1, 2, 3, 4] steps: - - name: Setup node - uses: actions/setup-node@v3 - if: ${{needs.build.outputs.docsChange == 'nope'}} - with: - node-version: ${{ matrix.node }} - check-latest: true - - run: echo ${{needs.build.outputs.docsChange}} # https://github.com/actions/virtual-environments/issues/1187 @@ -289,16 +282,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: npm i -g pnpm@${PNPM_VERSION} - if: ${{needs.build.outputs.docsChange == 'nope'}} - - - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 10 - if: ${{needs.build.outputs.docsChange == 'nope'}} - - # - run: npx @replayio/playwright install chromium - # if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: node run-tests.js --type development --timings -g ${{ matrix.group }}/2 + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.22.2-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --type development --timings -g ${{ matrix.group }}/4 >> /proc/1/fd/1" name: Run test/development if: ${{needs.build.outputs.docsChange == 'nope'}} # env: @@ -338,15 +322,8 @@ jobs: fail-fast: false matrix: node: [16, 18] - group: [1, 2, 3] + group: [1, 2, 3, 4, 5, 6, 7] steps: - - name: Setup node - uses: actions/setup-node@v3 - if: ${{needs.build.outputs.docsChange == 'nope'}} - with: - node-version: ${{ matrix.node }} - check-latest: true - - run: echo ${{needs.build.outputs.docsChange}} # https://github.com/actions/virtual-environments/issues/1187 @@ -366,16 +343,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: npm i -g pnpm@${PNPM_VERSION} - if: ${{needs.build.outputs.docsChange == 'nope'}} - - - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 10 - if: ${{needs.build.outputs.docsChange == 'nope'}} - - # - run: npx @replayio/playwright install chromium - # if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: node run-tests.js --type e2e --timings -g ${{ matrix.group }}/3 + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.22.2-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" name: Run test/e2e (dev) if: ${{needs.build.outputs.docsChange == 'nope'}} env: @@ -415,15 +383,8 @@ jobs: fail-fast: false matrix: node: [16, 18] - group: [1, 2] + group: [1, 2, 3] steps: - - name: Setup node - uses: actions/setup-node@v3 - if: ${{needs.build.outputs.docsChange == 'nope'}} - with: - node-version: ${{ matrix.node }} - check-latest: true - - run: echo ${{needs.build.outputs.docsChange}} # https://github.com/actions/virtual-environments/issues/1187 @@ -443,16 +404,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: npm i -g pnpm@${PNPM_VERSION} - if: ${{needs.build.outputs.docsChange == 'nope'}} - - - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 10 - if: ${{needs.build.outputs.docsChange == 'nope'}} - - # - run: npx @replayio/playwright install chromium - # if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: node run-tests.js --type production --timings -g ${{ matrix.group }}/2 + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.22.2-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --type production --timings -g ${{ matrix.group }}/3 >> /proc/1/fd/1" name: Run test/production if: ${{needs.build.outputs.docsChange == 'nope'}} # env: @@ -482,15 +434,8 @@ jobs: fail-fast: false matrix: node: [16, 18] - group: [1, 2, 3] + group: [1, 2, 3, 4, 5, 6, 7] steps: - - name: Setup node - uses: actions/setup-node@v3 - if: ${{needs.build.outputs.docsChange == 'nope'}} - with: - node-version: ${{ matrix.node }} - check-latest: true - - run: echo ${{needs.build.outputs.docsChange}} # https://github.com/actions/virtual-environments/issues/1187 @@ -510,16 +455,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: npm i -g pnpm@${PNPM_VERSION} - if: ${{needs.build.outputs.docsChange == 'nope'}} - - - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 10 - if: ${{needs.build.outputs.docsChange == 'nope'}} - - # - run: npx @replayio/playwright install chromium - # if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: node run-tests.js --type e2e --timings -g ${{ matrix.group }}/3 + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.22.2-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v${{ matrix.node }} | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --type e2e --timings -g ${{ matrix.group }}/7 >> /proc/1/fd/1" name: Run test/e2e (production) if: ${{needs.build.outputs.docsChange == 'nope'}} env: @@ -571,15 +507,13 @@ jobs: 18, 19, 20, + 21, + 22, + 23, + 24, + 25, ] steps: - - name: Setup node - uses: actions/setup-node@v3 - if: ${{needs.build.outputs.docsChange == 'nope'}} - with: - node-version: 16 - check-latest: true - - run: echo ${{needs.build.outputs.docsChange}} # https://github.com/actions/virtual-environments/issues/1187 @@ -599,16 +533,7 @@ jobs: name: next-swc-test-binary path: packages/next-swc/native - - run: npm i -g pnpm@${PNPM_VERSION} - if: ${{needs.build.outputs.docsChange == 'nope'}} - - - run: npm i -g playwright-chromium@1.22.2 && npx playwright install-deps - timeout-minutes: 10 - if: ${{needs.build.outputs.docsChange == 'nope'}} - - # - run: npx @replayio/playwright install chromium - # if: ${{needs.build.outputs.docsChange == 'nope'}} - - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/20 + - run: docker run --rm -v $(pwd):/work mcr.microsoft.com/playwright:v1.22.2-focal /bin/bash -c "cd /work && ls && curl https://install-node.vercel.app/v16 | FORCE=1 bash && node -v && npm i -g pnpm@${PNPM_VERSION} && xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/25 >> /proc/1/fd/1" if: ${{needs.build.outputs.docsChange == 'nope'}} # env: # RECORD_REPLAY_METADATA_TEST_RUN_TITLE: testIntegration / Group ${{ matrix.group }} diff --git a/.vscode/settings.json b/.vscode/settings.json index bf06a491f6cae..306e2cb06dd86 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ { "language": "typescriptreact", "autoFix": true } ], "debug.javascript.unmapMissingSources": true, + "jest.autoRun": "off", "files.exclude": { "**/node_modules": false, "node_modules": true, diff --git a/docs/deployment.md b/docs/deployment.md index fc09ac4108fda..eb18e43310f50 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -136,7 +136,18 @@ Next.js will automatically load the latest version of your application in the ba Sometimes you might want to run some cleanup code on process signals like `SIGTERM` or `SIGINT`. -You can do that by setting the env variable `NEXT_MANUAL_SIG_HANDLE` to `true` and then register a handler for that signal inside your `_document.js` file. +You can do that by setting the env variable `NEXT_MANUAL_SIG_HANDLE` to `true` and then register a handler for that signal inside your `_document.js` file. Please note that you need to register env variable directly in the system env variable, not in the `.env` file. + +```json +// package.json +{ + "scripts": { + "dev": "NEXT_MANUAL_SIG_HANDLE=true next dev", + "build": "next build", + "start": "NEXT_MANUAL_SIG_HANDLE=true next start" + } +} +``` ```js // pages/_document.js diff --git a/examples/with-cloudinary/package.json b/examples/with-cloudinary/package.json index f9c49ad231b43..0793a2b92cb51 100644 --- a/examples/with-cloudinary/package.json +++ b/examples/with-cloudinary/package.json @@ -26,6 +26,7 @@ "postcss": "^8.4.18", "prettier": "^2.7.1", "prettier-plugin-tailwindcss": "^0.1.13", - "tailwindcss": "^3.2.1" + "tailwindcss": "^3.2.1", + "typescript": "4.8.4" } } diff --git a/lerna.json b/lerna.json index 1f6b40fe6cab8..2f33e2eaba26e 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.0.7-canary.0" + "version": "13.0.7-canary.1" } diff --git a/package.json b/package.json index 10f3ca903bc14..89f0ef0cf5695 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@edge-runtime/jest-environment": "2.0.0", "@fullhuman/postcss-purgecss": "1.3.0", "@mdx-js/loader": "^1.5.1", + "@mdx-js/react": "^1.6.18", "@next/bundle-analyzer": "workspace:*", "@next/env": "workspace:*", "@next/eslint-plugin-next": "workspace:*", @@ -114,7 +115,9 @@ "alex": "9.1.0", "amphtml-validator": "1.0.35", "async-sema": "3.0.1", + "body-parser": "1.20.1", "browserslist": "4.20.2", + "buffer": "5.6.0", "chalk": "5.0.1", "cheerio": "0.22.0", "cookie": "0.4.1", @@ -140,6 +143,7 @@ "express": "4.17.0", "faker": "5.5.3", "faunadb": "2.6.1", + "find-up": "4.1.0", "firebase": "7.14.5", "flat": "5.0.2", "form-data": "4.0.0", @@ -149,15 +153,18 @@ "gzip-size": "5.1.1", "html-validator": "5.1.18", "husky": "8.0.0", + "http-proxy": "1.18.1", "image-size": "0.9.3", "is-animated": "2.0.2", "isomorphic-unfetch": "3.0.0", "jest": "27.0.6", "jest-extended": "1.2.1", + "json5": "2.2.1", "ky": "0.19.1", "ky-universal": "0.6.0", "lerna": "4.0.0", "lint-staged": "10.1.7", + "lodash": "4.17.20", "lost": "8.3.1", "minimatch": "3.0.4", "moment": "^2.24.0", @@ -197,7 +204,9 @@ "selenium-webdriver": "4.0.0-beta.4", "semver": "7.3.7", "shell-quote": "1.7.3", + "strip-ansi": "6.0.0", "styled-components": "6.0.0-beta.5", + "styled-jsx": "5.1.0", "styled-jsx-plugin-postcss": "3.0.2", "swr": "2.0.0-rc.0", "tailwindcss": "1.1.3", @@ -206,9 +215,12 @@ "tsec": "0.2.1", "turbo": "1.6.3", "typescript": "4.8.2", + "unfetch": "4.2.0", "wait-port": "0.2.2", + "whatwg-fetch": "3.0.0", "webpack": "5.74.0", - "webpack-bundle-analyzer": "4.7.0" + "webpack-bundle-analyzer": "4.7.0", + "ws": "8.2.3" }, "resolutions": { "browserslist": "4.20.2", diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index ffcd162f0f0e6..55c7e1b38f64b 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "keywords": [ "react", "next", diff --git a/packages/create-next-app/templates/app/js/README-template.md b/packages/create-next-app/templates/app/js/README-template.md index 331172e45f6d0..e0e00771d8796 100644 --- a/packages/create-next-app/templates/app/js/README-template.md +++ b/packages/create-next-app/templates/app/js/README-template.md @@ -12,9 +12,9 @@ yarn dev Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +You can start editing the page by modifying `app/page.jsx`. The page auto-updates as you edit the file. -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. diff --git a/packages/create-next-app/templates/app/js/app/globals.css b/packages/create-next-app/templates/app/js/app/globals.css index 4f1842163d222..3a54ccd80be42 100644 --- a/packages/create-next-app/templates/app/js/app/globals.css +++ b/packages/create-next-app/templates/app/js/app/globals.css @@ -1,9 +1,98 @@ -html, -body { +:root { + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', + 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', + 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; + + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; + + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient( + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0) + ); + + --tile-start-rgb: 239, 245, 249; + --tile-end-rgb: 228, 232, 233; + --tile-border: conic-gradient( + #00000080, + #00000040, + #00000030, + #00000020, + #00000010, + #00000010, + #00000080 + ); + + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: 180, 185, 188; + --card-border-rgb: 131, 134, 135; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + + --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); + + --tile-start-rgb: 2, 13, 46; + --tile-end-rgb: 2, 5, 19; + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); + + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } +} + +* { + box-sizing: border-box; padding: 0; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: rbg(--foreground-rgb); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); } a { @@ -11,16 +100,8 @@ a { text-decoration: none; } -* { - box-sizing: border-box; -} - @media (prefers-color-scheme: dark) { html { color-scheme: dark; } - body { - color: white; - background: black; - } } diff --git a/packages/create-next-app/templates/app/js/app/page.jsx b/packages/create-next-app/templates/app/js/app/page.jsx index 35bd2649d4cca..ca2f14fe4230b 100644 --- a/packages/create-next-app/templates/app/js/app/page.jsx +++ b/packages/create-next-app/templates/app/js/app/page.jsx @@ -1,57 +1,91 @@ import Image from 'next/image' +import { Inter } from '@next/font/google' import styles from './page.module.css' +const inter = Inter({ subsets: ['latin'] }) + export default function Home() { return ( -
-
-

- Welcome to Next.js 13! -

- -

- Get started by editing{' '} - app/page.tsx +

+
+

+ Get started by editing  + app/page.jsx

- -
- -

Documentation →

-

Find in-depth information about Next.js 13

-
- - -

Examples →

-

Explore the Next.js 13 playground.

-
- + -
+
+ +
+ Next.js Logo +
+ 13 +
+
+ +
+ +

+ Docs -> +

+

+ Find in-depth information about Next.js features and API. +

+
+ + +

+ Templates -> +

+

Explore the Next.js 13 playground.

+
- -
+ + ) } diff --git a/packages/create-next-app/templates/app/js/app/page.module.css b/packages/create-next-app/templates/app/js/app/page.module.css index a978c99de57b3..4732b55fdc237 100644 --- a/packages/create-next-app/templates/app/js/app/page.module.css +++ b/packages/create-next-app/templates/app/js/app/page.module.css @@ -1,146 +1,271 @@ -.container { - padding: 0 2rem; -} - .main { - min-height: 100vh; - padding: 4rem 0; - flex: 1; display: flex; flex-direction: column; - justify-content: center; + justify-content: space-between; align-items: center; + padding: 6rem; + min-height: 100vh; } -.footer { - display: flex; - flex: 1; - padding: 2rem 0; - border-top: 1px solid #eaeaea; - justify-content: center; - align-items: center; +.description { + display: inherit; + justify-content: inherit; + align-items: inherit; + font-size: 0.85rem; + max-width: var(--max-width); + width: 100%; + z-index: 2; + font-family: var(--font-mono); } -.footer a { +.description a { display: flex; - justify-content: center; align-items: center; - flex-grow: 1; + justify-content: center; + gap: 0.5rem; } -.title { +.description p { + position: relative; margin: 0; - line-height: 1.15; - font-size: 4rem; - font-style: normal; - font-weight: 800; - letter-spacing: -0.025em; + padding: 1rem; + background-color: rgba(var(--callout-rgb), 0.5); + border: 1px solid rgba(var(--callout-border-rgb), 0.3); + border-radius: var(--border-radius); } -.title a { - text-decoration: none; - color: #0070f3; +.code { + font-weight: 700; + font-family: var(--font-mono); } -.title a:hover, -.title a:focus, -.title a:active { - text-decoration: underline; +.grid { + display: grid; + grid-template-columns: repeat(3, minmax(33%, auto)); + width: var(--max-width); + max-width: 100%; } -.title, -.description { - text-align: center; +.card { + padding: 1rem 1.2rem; + border-radius: var(--border-radius); + background: rgba(var(--card-rgb), 0); + border: 1px solid rgba(var(--card-border-rgb), 0); + transition: background 200ms, border 200ms; } -.description { - margin: 4rem 0; - line-height: 1.5; - font-size: 1.5rem; +.card span { + display: inline-block; + transition: transform 200ms; } -.code { - background: #fafafa; - border-radius: 5px; - padding: 0.75rem; - font-size: 1.1rem; - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; +.card h2 { + font-weight: 600; + margin-bottom: 0.7rem; } -.grid { +.card p { + margin: 0; + opacity: 0.6; + font-size: 0.9rem; + line-height: 1.5; + max-width: 34ch; +} + +.center { display: flex; + justify-content: center; align-items: center; + position: relative; + padding: 4rem 0; +} + +.center::before { + background: var(--secondary-glow); + border-radius: 50%; + width: 480px; + height: 360px; + margin-left: -400px; +} + +.center::after { + background: var(--primary-glow); + width: 240px; + height: 180px; + z-index: -1; +} + +.center::before, +.center::after { + content: ''; + left: 50%; + position: absolute; + filter: blur(45px); + transform: translateZ(0); +} + +.logo, +.thirteen { + position: relative; +} + +.thirteen { + display: flex; justify-content: center; - flex-wrap: wrap; - max-width: 1200px; + align-items: center; + width: 75px; + height: 75px; + padding: 25px 10px; + margin-left: 16px; + transform: translateZ(0); + border-radius: var(--border-radius); + overflow: hidden; + box-shadow: 0px 2px 8px -1px #0000001a; } -.card { - margin: 1rem; - padding: 1.5rem; - text-align: left; - color: inherit; - text-decoration: none; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - max-width: 300px; +.thirteen::before, +.thirteen::after { + content: ''; + position: absolute; + z-index: -1; } -.card:hover, -.card:focus, -.card:active { - color: #0070f3; - border-color: #0070f3; +/* Conic Gradient Animation */ +.thirteen::before { + animation: 6s rotate linear infinite; + width: 200%; + height: 200%; + background: var(--tile-border); } -.card h2 { - margin: 0 0 1rem 0; - font-size: 1.5rem; +/* Inner Square */ +.thirteen::after { + inset: 0; + padding: 1px; + border-radius: var(--border-radius); + background: linear-gradient( + to bottom right, + rgba(var(--tile-start-rgb), 1), + rgba(var(--tile-end-rgb), 1) + ); + background-clip: content-box; } -.card p { - margin: 0; - font-size: 1.25rem; - line-height: 1.5; +/* Enable hover only on non-touch devices */ +@media (hover: hover) and (pointer: fine) { + .card:hover { + background: rgba(var(--card-rgb), 0.1); + border: 1px solid rgba(var(--card-border-rgb), 0.15); + } + + .card:hover span { + transform: translateX(4px); + } } -.logo { - height: 1em; - margin-left: 0.5rem; +@media (prefers-reduced-motion) { + .thirteen::before { + animation: none; + } + + .card:hover span { + transform: none; + } } -@media (max-width: 600px) { +/* Mobile and Tablet */ +@media (max-width: 1023px) { + .content { + padding: 4rem; + } + .grid { + grid-template-columns: 1fr; + margin-bottom: 120px; + max-width: 320px; + text-align: center; + } + + .card { + padding: 1rem 2.5rem; + } + + .card h2 { + margin-bottom: 0.5rem; + } + + .center { + padding: 8rem 0 6rem; + } + + .center::before { + transform: none; + height: 300px; + } + + .description { + font-size: 0.8rem; + } + + .description a { + padding: 1rem; + } + + .description p, + .description div { + display: flex; + justify-content: center; + position: fixed; width: 100%; - flex-direction: column; + } + + .description p { + align-items: center; + inset: 0 0 auto; + padding: 2rem 1rem 1.4rem; + border-radius: 0; + border: none; + border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); + background: linear-gradient( + to bottom, + rgba(var(--background-start-rgb), 1), + rgba(var(--callout-rgb), 0.5) + ); + background-clip: padding-box; + backdrop-filter: blur(24px); + } + + .description div { + align-items: flex-end; + pointer-events: none; + inset: auto 0 0; + padding: 2rem; + height: 200px; + background: linear-gradient( + to bottom, + transparent 0%, + rgb(var(--background-end-rgb)) 40% + ); + z-index: 1; } } @media (prefers-color-scheme: dark) { - .title { - background: linear-gradient(180deg, #ffffff 0%, #aaaaaa 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-fill-color: transparent; - } - .title a { - background: linear-gradient(180deg, #0070f3 0%, #0153af 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-fill-color: transparent; - } - .card, - .footer { - border-color: #222; - } - .code { - background: #111; - } - .logo img { + .vercelLogo { filter: invert(1); } + + .logo, + .thirteen img { + filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); + } +} + +@keyframes rotate { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } } diff --git a/packages/create-next-app/templates/app/js/gitignore b/packages/create-next-app/templates/app/js/gitignore index c87c9b392c020..55175ef867e0b 100644 --- a/packages/create-next-app/templates/app/js/gitignore +++ b/packages/create-next-app/templates/app/js/gitignore @@ -30,7 +30,3 @@ yarn-error.log* # vercel .vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/packages/create-next-app/templates/app/js/public/next.svg b/packages/create-next-app/templates/app/js/public/next.svg new file mode 100644 index 0000000000000..5174b28c565c2 --- /dev/null +++ b/packages/create-next-app/templates/app/js/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/create-next-app/templates/app/js/public/thirteen.svg b/packages/create-next-app/templates/app/js/public/thirteen.svg new file mode 100644 index 0000000000000..8977c1bd123cb --- /dev/null +++ b/packages/create-next-app/templates/app/js/public/thirteen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/create-next-app/templates/app/js/public/vercel.svg b/packages/create-next-app/templates/app/js/public/vercel.svg index fbf0e25a651c2..d2f84222734f2 100644 --- a/packages/create-next-app/templates/app/js/public/vercel.svg +++ b/packages/create-next-app/templates/app/js/public/vercel.svg @@ -1,4 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/create-next-app/templates/app/ts/app/globals.css b/packages/create-next-app/templates/app/ts/app/globals.css index 4f1842163d222..3a54ccd80be42 100644 --- a/packages/create-next-app/templates/app/ts/app/globals.css +++ b/packages/create-next-app/templates/app/ts/app/globals.css @@ -1,9 +1,98 @@ -html, -body { +:root { + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', + 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', + 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; + + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; + + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient( + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0) + ); + + --tile-start-rgb: 239, 245, 249; + --tile-end-rgb: 228, 232, 233; + --tile-border: conic-gradient( + #00000080, + #00000040, + #00000030, + #00000020, + #00000010, + #00000010, + #00000080 + ); + + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: 180, 185, 188; + --card-border-rgb: 131, 134, 135; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + + --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); + + --tile-start-rgb: 2, 13, 46; + --tile-end-rgb: 2, 5, 19; + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); + + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } +} + +* { + box-sizing: border-box; padding: 0; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: rbg(--foreground-rgb); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); } a { @@ -11,16 +100,8 @@ a { text-decoration: none; } -* { - box-sizing: border-box; -} - @media (prefers-color-scheme: dark) { html { color-scheme: dark; } - body { - color: white; - background: black; - } } diff --git a/packages/create-next-app/templates/app/ts/app/page.module.css b/packages/create-next-app/templates/app/ts/app/page.module.css index a978c99de57b3..4732b55fdc237 100644 --- a/packages/create-next-app/templates/app/ts/app/page.module.css +++ b/packages/create-next-app/templates/app/ts/app/page.module.css @@ -1,146 +1,271 @@ -.container { - padding: 0 2rem; -} - .main { - min-height: 100vh; - padding: 4rem 0; - flex: 1; display: flex; flex-direction: column; - justify-content: center; + justify-content: space-between; align-items: center; + padding: 6rem; + min-height: 100vh; } -.footer { - display: flex; - flex: 1; - padding: 2rem 0; - border-top: 1px solid #eaeaea; - justify-content: center; - align-items: center; +.description { + display: inherit; + justify-content: inherit; + align-items: inherit; + font-size: 0.85rem; + max-width: var(--max-width); + width: 100%; + z-index: 2; + font-family: var(--font-mono); } -.footer a { +.description a { display: flex; - justify-content: center; align-items: center; - flex-grow: 1; + justify-content: center; + gap: 0.5rem; } -.title { +.description p { + position: relative; margin: 0; - line-height: 1.15; - font-size: 4rem; - font-style: normal; - font-weight: 800; - letter-spacing: -0.025em; + padding: 1rem; + background-color: rgba(var(--callout-rgb), 0.5); + border: 1px solid rgba(var(--callout-border-rgb), 0.3); + border-radius: var(--border-radius); } -.title a { - text-decoration: none; - color: #0070f3; +.code { + font-weight: 700; + font-family: var(--font-mono); } -.title a:hover, -.title a:focus, -.title a:active { - text-decoration: underline; +.grid { + display: grid; + grid-template-columns: repeat(3, minmax(33%, auto)); + width: var(--max-width); + max-width: 100%; } -.title, -.description { - text-align: center; +.card { + padding: 1rem 1.2rem; + border-radius: var(--border-radius); + background: rgba(var(--card-rgb), 0); + border: 1px solid rgba(var(--card-border-rgb), 0); + transition: background 200ms, border 200ms; } -.description { - margin: 4rem 0; - line-height: 1.5; - font-size: 1.5rem; +.card span { + display: inline-block; + transition: transform 200ms; } -.code { - background: #fafafa; - border-radius: 5px; - padding: 0.75rem; - font-size: 1.1rem; - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; +.card h2 { + font-weight: 600; + margin-bottom: 0.7rem; } -.grid { +.card p { + margin: 0; + opacity: 0.6; + font-size: 0.9rem; + line-height: 1.5; + max-width: 34ch; +} + +.center { display: flex; + justify-content: center; align-items: center; + position: relative; + padding: 4rem 0; +} + +.center::before { + background: var(--secondary-glow); + border-radius: 50%; + width: 480px; + height: 360px; + margin-left: -400px; +} + +.center::after { + background: var(--primary-glow); + width: 240px; + height: 180px; + z-index: -1; +} + +.center::before, +.center::after { + content: ''; + left: 50%; + position: absolute; + filter: blur(45px); + transform: translateZ(0); +} + +.logo, +.thirteen { + position: relative; +} + +.thirteen { + display: flex; justify-content: center; - flex-wrap: wrap; - max-width: 1200px; + align-items: center; + width: 75px; + height: 75px; + padding: 25px 10px; + margin-left: 16px; + transform: translateZ(0); + border-radius: var(--border-radius); + overflow: hidden; + box-shadow: 0px 2px 8px -1px #0000001a; } -.card { - margin: 1rem; - padding: 1.5rem; - text-align: left; - color: inherit; - text-decoration: none; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - max-width: 300px; +.thirteen::before, +.thirteen::after { + content: ''; + position: absolute; + z-index: -1; } -.card:hover, -.card:focus, -.card:active { - color: #0070f3; - border-color: #0070f3; +/* Conic Gradient Animation */ +.thirteen::before { + animation: 6s rotate linear infinite; + width: 200%; + height: 200%; + background: var(--tile-border); } -.card h2 { - margin: 0 0 1rem 0; - font-size: 1.5rem; +/* Inner Square */ +.thirteen::after { + inset: 0; + padding: 1px; + border-radius: var(--border-radius); + background: linear-gradient( + to bottom right, + rgba(var(--tile-start-rgb), 1), + rgba(var(--tile-end-rgb), 1) + ); + background-clip: content-box; } -.card p { - margin: 0; - font-size: 1.25rem; - line-height: 1.5; +/* Enable hover only on non-touch devices */ +@media (hover: hover) and (pointer: fine) { + .card:hover { + background: rgba(var(--card-rgb), 0.1); + border: 1px solid rgba(var(--card-border-rgb), 0.15); + } + + .card:hover span { + transform: translateX(4px); + } } -.logo { - height: 1em; - margin-left: 0.5rem; +@media (prefers-reduced-motion) { + .thirteen::before { + animation: none; + } + + .card:hover span { + transform: none; + } } -@media (max-width: 600px) { +/* Mobile and Tablet */ +@media (max-width: 1023px) { + .content { + padding: 4rem; + } + .grid { + grid-template-columns: 1fr; + margin-bottom: 120px; + max-width: 320px; + text-align: center; + } + + .card { + padding: 1rem 2.5rem; + } + + .card h2 { + margin-bottom: 0.5rem; + } + + .center { + padding: 8rem 0 6rem; + } + + .center::before { + transform: none; + height: 300px; + } + + .description { + font-size: 0.8rem; + } + + .description a { + padding: 1rem; + } + + .description p, + .description div { + display: flex; + justify-content: center; + position: fixed; width: 100%; - flex-direction: column; + } + + .description p { + align-items: center; + inset: 0 0 auto; + padding: 2rem 1rem 1.4rem; + border-radius: 0; + border: none; + border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); + background: linear-gradient( + to bottom, + rgba(var(--background-start-rgb), 1), + rgba(var(--callout-rgb), 0.5) + ); + background-clip: padding-box; + backdrop-filter: blur(24px); + } + + .description div { + align-items: flex-end; + pointer-events: none; + inset: auto 0 0; + padding: 2rem; + height: 200px; + background: linear-gradient( + to bottom, + transparent 0%, + rgb(var(--background-end-rgb)) 40% + ); + z-index: 1; } } @media (prefers-color-scheme: dark) { - .title { - background: linear-gradient(180deg, #ffffff 0%, #aaaaaa 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-fill-color: transparent; - } - .title a { - background: linear-gradient(180deg, #0070f3 0%, #0153af 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-fill-color: transparent; - } - .card, - .footer { - border-color: #222; - } - .code { - background: #111; - } - .logo img { + .vercelLogo { filter: invert(1); } + + .logo, + .thirteen img { + filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); + } +} + +@keyframes rotate { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } } diff --git a/packages/create-next-app/templates/app/ts/app/page.tsx b/packages/create-next-app/templates/app/ts/app/page.tsx index 35bd2649d4cca..47fcc959cf52b 100644 --- a/packages/create-next-app/templates/app/ts/app/page.tsx +++ b/packages/create-next-app/templates/app/ts/app/page.tsx @@ -1,57 +1,91 @@ import Image from 'next/image' +import { Inter } from '@next/font/google' import styles from './page.module.css' +const inter = Inter({ subsets: ['latin'] }) + export default function Home() { return ( -
-
-

- Welcome to Next.js 13! -

- -

- Get started by editing{' '} +

+
+

+ Get started by editing  app/page.tsx

- -
- -

Documentation →

-

Find in-depth information about Next.js 13

-
- - -

Examples →

-

Explore the Next.js 13 playground.

-
- + -
+
+ +
+ Next.js Logo +
+ 13 +
+
+ +
+ +

+ Docs -> +

+

+ Find in-depth information about Next.js features and API. +

+
+ + +

+ Templates -> +

+

Explore the Next.js 13 playground.

+
- -
+ + ) } diff --git a/packages/create-next-app/templates/app/ts/public/next.svg b/packages/create-next-app/templates/app/ts/public/next.svg new file mode 100644 index 0000000000000..5174b28c565c2 --- /dev/null +++ b/packages/create-next-app/templates/app/ts/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/create-next-app/templates/app/ts/public/thirteen.svg b/packages/create-next-app/templates/app/ts/public/thirteen.svg new file mode 100644 index 0000000000000..8977c1bd123cb --- /dev/null +++ b/packages/create-next-app/templates/app/ts/public/thirteen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/create-next-app/templates/app/ts/public/vercel.svg b/packages/create-next-app/templates/app/ts/public/vercel.svg index fbf0e25a651c2..d2f84222734f2 100644 --- a/packages/create-next-app/templates/app/ts/public/vercel.svg +++ b/packages/create-next-app/templates/app/ts/public/vercel.svg @@ -1,4 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/create-next-app/templates/default/js/gitignore b/packages/create-next-app/templates/default/js/gitignore index c87c9b392c020..55175ef867e0b 100644 --- a/packages/create-next-app/templates/default/js/gitignore +++ b/packages/create-next-app/templates/default/js/gitignore @@ -30,7 +30,3 @@ yarn-error.log* # vercel .vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/packages/create-next-app/templates/default/js/next.config.js b/packages/create-next-app/templates/default/js/next.config.js index a843cbee09afa..ae887958d3c9c 100644 --- a/packages/create-next-app/templates/default/js/next.config.js +++ b/packages/create-next-app/templates/default/js/next.config.js @@ -1,6 +1,7 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, + swcMinify: true, } module.exports = nextConfig diff --git a/packages/create-next-app/templates/default/js/pages/_app.js b/packages/create-next-app/templates/default/js/pages/_app.js index 1e1cec92425c8..048541e25b1dd 100644 --- a/packages/create-next-app/templates/default/js/pages/_app.js +++ b/packages/create-next-app/templates/default/js/pages/_app.js @@ -1,7 +1,5 @@ import '../styles/globals.css' -function MyApp({ Component, pageProps }) { +export default function App({ Component, pageProps }) { return } - -export default MyApp diff --git a/packages/create-next-app/templates/default/js/pages/_document.js b/packages/create-next-app/templates/default/js/pages/_document.js new file mode 100644 index 0000000000000..54e8bf3e2a290 --- /dev/null +++ b/packages/create-next-app/templates/default/js/pages/_document.js @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/packages/create-next-app/templates/default/js/pages/index.js b/packages/create-next-app/templates/default/js/pages/index.js index 5bf5574341670..dc5a1cce79a7d 100644 --- a/packages/create-next-app/templates/default/js/pages/index.js +++ b/packages/create-next-app/templates/default/js/pages/index.js @@ -1,71 +1,123 @@ import Head from 'next/head' import Image from 'next/image' +import { Inter } from '@next/font/google' import styles from '../styles/Home.module.css' +const inter = Inter({ subsets: ['latin'] }) + export default function Home() { return ( -
+ <> Create Next App + -
-

- Welcome to Next.js! -

+
+

+ Get started by editing  + pages/index.js +

+ +
-

- Get started by editing{' '} - pages/index.js -

+
+ Next.js Logo +
+ 13 +
+
- - -
+ ) } diff --git a/packages/create-next-app/templates/default/js/public/next.svg b/packages/create-next-app/templates/default/js/public/next.svg new file mode 100644 index 0000000000000..5174b28c565c2 --- /dev/null +++ b/packages/create-next-app/templates/default/js/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/create-next-app/templates/default/js/public/thirteen.svg b/packages/create-next-app/templates/default/js/public/thirteen.svg new file mode 100644 index 0000000000000..8977c1bd123cb --- /dev/null +++ b/packages/create-next-app/templates/default/js/public/thirteen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/create-next-app/templates/default/js/public/vercel.svg b/packages/create-next-app/templates/default/js/public/vercel.svg index fbf0e25a651c2..d2f84222734f2 100644 --- a/packages/create-next-app/templates/default/js/public/vercel.svg +++ b/packages/create-next-app/templates/default/js/public/vercel.svg @@ -1,4 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/create-next-app/templates/default/js/styles/Home.module.css b/packages/create-next-app/templates/default/js/styles/Home.module.css index bd50f42ffe6a2..27dfff5ec4cf8 100644 --- a/packages/create-next-app/templates/default/js/styles/Home.module.css +++ b/packages/create-next-app/templates/default/js/styles/Home.module.css @@ -1,129 +1,278 @@ -.container { - padding: 0 2rem; -} - .main { - min-height: 100vh; - padding: 4rem 0; - flex: 1; display: flex; flex-direction: column; - justify-content: center; + justify-content: space-between; align-items: center; + padding: 6rem; + min-height: 100vh; } -.footer { - display: flex; - flex: 1; - padding: 2rem 0; - border-top: 1px solid #eaeaea; - justify-content: center; - align-items: center; +.description { + display: inherit; + justify-content: inherit; + align-items: inherit; + font-size: 0.85rem; + max-width: var(--max-width); + width: 100%; + z-index: 2; + font-family: var(--font-mono); } -.footer a { +.description a { display: flex; justify-content: center; align-items: center; - flex-grow: 1; + gap: 0.5rem; } -.title a { - color: #0070f3; - text-decoration: none; +.description p { + position: relative; + margin: 0; + padding: 1rem; + background-color: rgba(var(--callout-rgb), 0.5); + border: 1px solid rgba(var(--callout-border-rgb), 0.3); + border-radius: var(--border-radius); } -.title a:hover, -.title a:focus, -.title a:active { - text-decoration: underline; +.code { + font-weight: 700; + font-family: var(--font-mono); } -.title { - margin: 0; - line-height: 1.15; - font-size: 4rem; +.grid { + display: grid; + grid-template-columns: repeat(4, minmax(25%, auto)); + width: var(--max-width); + max-width: 100%; } -.title, -.description { - text-align: center; +.card { + padding: 1rem 1.2rem; + border-radius: var(--border-radius); + background: rgba(var(--card-rgb), 0); + border: 1px solid rgba(var(--card-border-rgb), 0); + transition: background 200ms, border 200ms; } -.description { - margin: 4rem 0; - line-height: 1.5; - font-size: 1.5rem; +.card span { + display: inline-block; + transition: transform 200ms; } -.code { - background: #fafafa; - border-radius: 5px; - padding: 0.75rem; - font-size: 1.1rem; - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; +.card h2 { + font-weight: 600; + margin-bottom: 0.7rem; } -.grid { +.card p { + margin: 0; + opacity: 0.6; + font-size: 0.9rem; + line-height: 1.5; + max-width: 30ch; +} + +.center { display: flex; + justify-content: center; align-items: center; + position: relative; + padding: 4rem 0; +} + +.center::before { + background: var(--secondary-glow); + border-radius: 50%; + width: 480px; + height: 360px; + margin-left: -400px; +} + +.center::after { + background: var(--primary-glow); + width: 240px; + height: 180px; + z-index: -1; +} + +.center::before, +.center::after { + content: ''; + left: 50%; + position: absolute; + filter: blur(45px); + transform: translateZ(0); +} + +.logo, +.thirteen { + position: relative; +} + +.thirteen { + display: flex; justify-content: center; - flex-wrap: wrap; - max-width: 800px; + align-items: center; + width: 75px; + height: 75px; + padding: 25px 10px; + margin-left: 16px; + transform: translateZ(0); + border-radius: var(--border-radius); + overflow: hidden; + box-shadow: 0px 2px 8px -1px #0000001a; } -.card { - margin: 1rem; - padding: 1.5rem; - text-align: left; - color: inherit; - text-decoration: none; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - max-width: 300px; +.thirteen::before, +.thirteen::after { + content: ''; + position: absolute; + z-index: -1; } -.card:hover, -.card:focus, -.card:active { - color: #0070f3; - border-color: #0070f3; +/* Conic Gradient Animation */ +.thirteen::before { + animation: 6s rotate linear infinite; + width: 200%; + height: 200%; + background: var(--tile-border); } -.card h2 { - margin: 0 0 1rem 0; - font-size: 1.5rem; +/* Inner Square */ +.thirteen::after { + inset: 0; + padding: 1px; + border-radius: var(--border-radius); + background: linear-gradient( + to bottom right, + rgba(var(--tile-start-rgb), 1), + rgba(var(--tile-end-rgb), 1) + ); + background-clip: content-box; } -.card p { - margin: 0; - font-size: 1.25rem; - line-height: 1.5; +/* Enable hover only on non-touch devices */ +@media (hover: hover) and (pointer: fine) { + .card:hover { + background: rgba(var(--card-rgb), 0.1); + border: 1px solid rgba(var(--card-border-rgb), 0.15); + } + + .card:hover span { + transform: translateX(4px); + } } -.logo { - height: 1em; - margin-left: 0.5rem; +@media (prefers-reduced-motion) { + .thirteen::before { + animation: none; + } + + .card:hover span { + transform: none; + } } -@media (max-width: 600px) { +/* Mobile */ +@media (max-width: 700px) { + .content { + padding: 4rem; + } + .grid { + grid-template-columns: 1fr; + margin-bottom: 120px; + max-width: 320px; + text-align: center; + } + + .card { + padding: 1rem 2.5rem; + } + + .card h2 { + margin-bottom: 0.5rem; + } + + .center { + padding: 8rem 0 6rem; + } + + .center::before { + transform: none; + height: 300px; + } + + .description { + font-size: 0.8rem; + } + + .description a { + padding: 1rem; + } + + .description p, + .description div { + display: flex; + justify-content: center; + position: fixed; width: 100%; - flex-direction: column; + } + + .description p { + align-items: center; + inset: 0 0 auto; + padding: 2rem 1rem 1.4rem; + border-radius: 0; + border: none; + border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); + background: linear-gradient( + to bottom, + rgba(var(--background-start-rgb), 1), + rgba(var(--callout-rgb), 0.5) + ); + background-clip: padding-box; + backdrop-filter: blur(24px); + } + + .description div { + align-items: flex-end; + pointer-events: none; + inset: auto 0 0; + padding: 2rem; + height: 200px; + background: linear-gradient( + to bottom, + transparent 0%, + rgb(var(--background-end-rgb)) 40% + ); + z-index: 1; + } +} + +/* Tablet and Smaller Desktop */ +@media (min-width: 701px) and (max-width: 1120px) { + .grid { + grid-template-columns: repeat(2, 50%); } } @media (prefers-color-scheme: dark) { - .card, - .footer { - border-color: #222; + .vercelLogo { + filter: invert(1); } - .code { - background: #111; + + .logo, + .thirteen img { + filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); } - .logo img { - filter: invert(1); +} + +@keyframes rotate { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); } } diff --git a/packages/create-next-app/templates/default/js/styles/globals.css b/packages/create-next-app/templates/default/js/styles/globals.css index 4f1842163d222..3a54ccd80be42 100644 --- a/packages/create-next-app/templates/default/js/styles/globals.css +++ b/packages/create-next-app/templates/default/js/styles/globals.css @@ -1,9 +1,98 @@ -html, -body { +:root { + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', + 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', + 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; + + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; + + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient( + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0) + ); + + --tile-start-rgb: 239, 245, 249; + --tile-end-rgb: 228, 232, 233; + --tile-border: conic-gradient( + #00000080, + #00000040, + #00000030, + #00000020, + #00000010, + #00000010, + #00000080 + ); + + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: 180, 185, 188; + --card-border-rgb: 131, 134, 135; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + + --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); + + --tile-start-rgb: 2, 13, 46; + --tile-end-rgb: 2, 5, 19; + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); + + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } +} + +* { + box-sizing: border-box; padding: 0; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: rbg(--foreground-rgb); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); } a { @@ -11,16 +100,8 @@ a { text-decoration: none; } -* { - box-sizing: border-box; -} - @media (prefers-color-scheme: dark) { html { color-scheme: dark; } - body { - color: white; - background: black; - } } diff --git a/packages/create-next-app/templates/default/ts/pages/_document.tsx b/packages/create-next-app/templates/default/ts/pages/_document.tsx new file mode 100644 index 0000000000000..54e8bf3e2a290 --- /dev/null +++ b/packages/create-next-app/templates/default/ts/pages/_document.tsx @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} diff --git a/packages/create-next-app/templates/default/ts/pages/index.tsx b/packages/create-next-app/templates/default/ts/pages/index.tsx index cdffa57ca8254..c08b1b34a0f94 100644 --- a/packages/create-next-app/templates/default/ts/pages/index.tsx +++ b/packages/create-next-app/templates/default/ts/pages/index.tsx @@ -1,71 +1,123 @@ import Head from 'next/head' import Image from 'next/image' +import { Inter } from '@next/font/google' import styles from '../styles/Home.module.css' +const inter = Inter({ subsets: ['latin'] }) + export default function Home() { return ( -
+ <> Create Next App + -
-

- Welcome to Next.js! -

+
+

+ Get started by editing  + pages/index.tsx +

+ +
-

- Get started by editing{' '} - pages/index.tsx -

+
+ Next.js Logo +
+ 13 +
+
- - -
+ ) } diff --git a/packages/create-next-app/templates/default/ts/public/next.svg b/packages/create-next-app/templates/default/ts/public/next.svg new file mode 100644 index 0000000000000..5174b28c565c2 --- /dev/null +++ b/packages/create-next-app/templates/default/ts/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/create-next-app/templates/default/ts/public/thirteen.svg b/packages/create-next-app/templates/default/ts/public/thirteen.svg new file mode 100644 index 0000000000000..8977c1bd123cb --- /dev/null +++ b/packages/create-next-app/templates/default/ts/public/thirteen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/create-next-app/templates/default/ts/public/vercel.svg b/packages/create-next-app/templates/default/ts/public/vercel.svg index fbf0e25a651c2..d2f84222734f2 100644 --- a/packages/create-next-app/templates/default/ts/public/vercel.svg +++ b/packages/create-next-app/templates/default/ts/public/vercel.svg @@ -1,4 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/create-next-app/templates/default/ts/styles/Home.module.css b/packages/create-next-app/templates/default/ts/styles/Home.module.css index bd50f42ffe6a2..27dfff5ec4cf8 100644 --- a/packages/create-next-app/templates/default/ts/styles/Home.module.css +++ b/packages/create-next-app/templates/default/ts/styles/Home.module.css @@ -1,129 +1,278 @@ -.container { - padding: 0 2rem; -} - .main { - min-height: 100vh; - padding: 4rem 0; - flex: 1; display: flex; flex-direction: column; - justify-content: center; + justify-content: space-between; align-items: center; + padding: 6rem; + min-height: 100vh; } -.footer { - display: flex; - flex: 1; - padding: 2rem 0; - border-top: 1px solid #eaeaea; - justify-content: center; - align-items: center; +.description { + display: inherit; + justify-content: inherit; + align-items: inherit; + font-size: 0.85rem; + max-width: var(--max-width); + width: 100%; + z-index: 2; + font-family: var(--font-mono); } -.footer a { +.description a { display: flex; justify-content: center; align-items: center; - flex-grow: 1; + gap: 0.5rem; } -.title a { - color: #0070f3; - text-decoration: none; +.description p { + position: relative; + margin: 0; + padding: 1rem; + background-color: rgba(var(--callout-rgb), 0.5); + border: 1px solid rgba(var(--callout-border-rgb), 0.3); + border-radius: var(--border-radius); } -.title a:hover, -.title a:focus, -.title a:active { - text-decoration: underline; +.code { + font-weight: 700; + font-family: var(--font-mono); } -.title { - margin: 0; - line-height: 1.15; - font-size: 4rem; +.grid { + display: grid; + grid-template-columns: repeat(4, minmax(25%, auto)); + width: var(--max-width); + max-width: 100%; } -.title, -.description { - text-align: center; +.card { + padding: 1rem 1.2rem; + border-radius: var(--border-radius); + background: rgba(var(--card-rgb), 0); + border: 1px solid rgba(var(--card-border-rgb), 0); + transition: background 200ms, border 200ms; } -.description { - margin: 4rem 0; - line-height: 1.5; - font-size: 1.5rem; +.card span { + display: inline-block; + transition: transform 200ms; } -.code { - background: #fafafa; - border-radius: 5px; - padding: 0.75rem; - font-size: 1.1rem; - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; +.card h2 { + font-weight: 600; + margin-bottom: 0.7rem; } -.grid { +.card p { + margin: 0; + opacity: 0.6; + font-size: 0.9rem; + line-height: 1.5; + max-width: 30ch; +} + +.center { display: flex; + justify-content: center; align-items: center; + position: relative; + padding: 4rem 0; +} + +.center::before { + background: var(--secondary-glow); + border-radius: 50%; + width: 480px; + height: 360px; + margin-left: -400px; +} + +.center::after { + background: var(--primary-glow); + width: 240px; + height: 180px; + z-index: -1; +} + +.center::before, +.center::after { + content: ''; + left: 50%; + position: absolute; + filter: blur(45px); + transform: translateZ(0); +} + +.logo, +.thirteen { + position: relative; +} + +.thirteen { + display: flex; justify-content: center; - flex-wrap: wrap; - max-width: 800px; + align-items: center; + width: 75px; + height: 75px; + padding: 25px 10px; + margin-left: 16px; + transform: translateZ(0); + border-radius: var(--border-radius); + overflow: hidden; + box-shadow: 0px 2px 8px -1px #0000001a; } -.card { - margin: 1rem; - padding: 1.5rem; - text-align: left; - color: inherit; - text-decoration: none; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - max-width: 300px; +.thirteen::before, +.thirteen::after { + content: ''; + position: absolute; + z-index: -1; } -.card:hover, -.card:focus, -.card:active { - color: #0070f3; - border-color: #0070f3; +/* Conic Gradient Animation */ +.thirteen::before { + animation: 6s rotate linear infinite; + width: 200%; + height: 200%; + background: var(--tile-border); } -.card h2 { - margin: 0 0 1rem 0; - font-size: 1.5rem; +/* Inner Square */ +.thirteen::after { + inset: 0; + padding: 1px; + border-radius: var(--border-radius); + background: linear-gradient( + to bottom right, + rgba(var(--tile-start-rgb), 1), + rgba(var(--tile-end-rgb), 1) + ); + background-clip: content-box; } -.card p { - margin: 0; - font-size: 1.25rem; - line-height: 1.5; +/* Enable hover only on non-touch devices */ +@media (hover: hover) and (pointer: fine) { + .card:hover { + background: rgba(var(--card-rgb), 0.1); + border: 1px solid rgba(var(--card-border-rgb), 0.15); + } + + .card:hover span { + transform: translateX(4px); + } } -.logo { - height: 1em; - margin-left: 0.5rem; +@media (prefers-reduced-motion) { + .thirteen::before { + animation: none; + } + + .card:hover span { + transform: none; + } } -@media (max-width: 600px) { +/* Mobile */ +@media (max-width: 700px) { + .content { + padding: 4rem; + } + .grid { + grid-template-columns: 1fr; + margin-bottom: 120px; + max-width: 320px; + text-align: center; + } + + .card { + padding: 1rem 2.5rem; + } + + .card h2 { + margin-bottom: 0.5rem; + } + + .center { + padding: 8rem 0 6rem; + } + + .center::before { + transform: none; + height: 300px; + } + + .description { + font-size: 0.8rem; + } + + .description a { + padding: 1rem; + } + + .description p, + .description div { + display: flex; + justify-content: center; + position: fixed; width: 100%; - flex-direction: column; + } + + .description p { + align-items: center; + inset: 0 0 auto; + padding: 2rem 1rem 1.4rem; + border-radius: 0; + border: none; + border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); + background: linear-gradient( + to bottom, + rgba(var(--background-start-rgb), 1), + rgba(var(--callout-rgb), 0.5) + ); + background-clip: padding-box; + backdrop-filter: blur(24px); + } + + .description div { + align-items: flex-end; + pointer-events: none; + inset: auto 0 0; + padding: 2rem; + height: 200px; + background: linear-gradient( + to bottom, + transparent 0%, + rgb(var(--background-end-rgb)) 40% + ); + z-index: 1; + } +} + +/* Tablet and Smaller Desktop */ +@media (min-width: 701px) and (max-width: 1120px) { + .grid { + grid-template-columns: repeat(2, 50%); } } @media (prefers-color-scheme: dark) { - .card, - .footer { - border-color: #222; + .vercelLogo { + filter: invert(1); } - .code { - background: #111; + + .logo, + .thirteen img { + filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); } - .logo img { - filter: invert(1); +} + +@keyframes rotate { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); } } diff --git a/packages/create-next-app/templates/default/ts/styles/globals.css b/packages/create-next-app/templates/default/ts/styles/globals.css index 4f1842163d222..3a54ccd80be42 100644 --- a/packages/create-next-app/templates/default/ts/styles/globals.css +++ b/packages/create-next-app/templates/default/ts/styles/globals.css @@ -1,9 +1,98 @@ -html, -body { +:root { + --max-width: 1100px; + --border-radius: 12px; + --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', + 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', + 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; + + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; + + --primary-glow: conic-gradient( + from 180deg at 50% 50%, + #16abff33 0deg, + #0885ff33 55deg, + #54d6ff33 120deg, + #0071ff33 160deg, + transparent 360deg + ); + --secondary-glow: radial-gradient( + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0) + ); + + --tile-start-rgb: 239, 245, 249; + --tile-end-rgb: 228, 232, 233; + --tile-border: conic-gradient( + #00000080, + #00000040, + #00000030, + #00000020, + #00000010, + #00000010, + #00000080 + ); + + --callout-rgb: 238, 240, 241; + --callout-border-rgb: 172, 175, 176; + --card-rgb: 180, 185, 188; + --card-border-rgb: 131, 134, 135; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + + --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); + --secondary-glow: linear-gradient( + to bottom right, + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0), + rgba(1, 65, 255, 0.3) + ); + + --tile-start-rgb: 2, 13, 46; + --tile-end-rgb: 2, 5, 19; + --tile-border: conic-gradient( + #ffffff80, + #ffffff40, + #ffffff30, + #ffffff20, + #ffffff10, + #ffffff10, + #ffffff80 + ); + + --callout-rgb: 20, 20, 20; + --callout-border-rgb: 108, 108, 108; + --card-rgb: 100, 100, 100; + --card-border-rgb: 200, 200, 200; + } +} + +* { + box-sizing: border-box; padding: 0; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + color: rbg(--foreground-rgb); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); } a { @@ -11,16 +100,8 @@ a { text-decoration: none; } -* { - box-sizing: border-box; -} - @media (prefers-color-scheme: dark) { html { color-scheme: dark; } - body { - color: white; - background: black; - } } diff --git a/packages/create-next-app/templates/index.ts b/packages/create-next-app/templates/index.ts index 0863fc87f46cb..77cb9bd066642 100644 --- a/packages/create-next-app/templates/index.ts +++ b/packages/create-next-app/templates/index.ts @@ -62,7 +62,7 @@ export const installTemplate = async ({ /** * Default dependencies. */ - const dependencies = ['react', 'react-dom', 'next'] + const dependencies = ['react', 'react-dom', 'next', '@next/font'] /** * TypeScript projects will have type definitions and other devDependencies. */ diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 47086fd1b4763..714546a439e17 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "description": "ESLint configuration used by NextJS.", "main": "index.js", "license": "MIT", @@ -9,7 +9,7 @@ "directory": "packages/eslint-config-next" }, "dependencies": { - "@next/eslint-plugin-next": "13.0.7-canary.0", + "@next/eslint-plugin-next": "13.0.7-canary.1", "@rushstack/eslint-patch": "^1.1.3", "@typescript-eslint/parser": "^5.42.0", "eslint-import-resolver-node": "^0.3.6", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index bfc45f16a5c46..ee0eabd541361 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 2336f0e622f60..c7ffc7b8a6145 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 52502b564cfed..af87dfcb43bb1 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 677499b7ca04f..a7c0266a507cd 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 2e61458eec8e0..94b718992f34a 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 08773d8f49b4e..9bad890f09d95 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 7350790fb2693..2585a62e1c745 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index b12f852947b35..d9aede9326438 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index a94252a80ec4a..67003ea9da198 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index afdef09838eb1..d18896a57b925 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "private": true, "scripts": { "build-native": "napi build --platform -p next-swc-napi --cargo-name next_swc_napi --features plugin --js false native", diff --git a/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts b/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts index 577fc98d4b17d..03c53fcf7e4eb 100644 --- a/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts +++ b/packages/next/build/webpack/loaders/next-font-loader/postcss-font-loader.ts @@ -115,8 +115,8 @@ const postcssFontLoaderPlugn = ({ const isRange = (value: string) => value.trim().includes(' ') const formattedFontFamilies = [ formatFamily(fontFamily), - ...fallbackFonts, ...(adjustFontFallbackFamily ? [adjustFontFallbackFamily] : []), + ...fallbackFonts, ].join(', ') // Add class with family, weight and style const classRule = new postcss.Rule({ selector: '.className' }) diff --git a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts index 622d837a01b04..57e2ff6ee13a3 100644 --- a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts @@ -561,7 +561,6 @@ export class FlightClientEntryPlugin { return shouldInvalidate } - // TODO-APP: make sure dependsOn is added for layouts/pages addEntry( compilation: any, context: string, diff --git a/packages/next/client/components/app-router-headers.ts b/packages/next/client/components/app-router-headers.ts index de00963e625e7..12d2a3dccd025 100644 --- a/packages/next/client/components/app-router-headers.ts +++ b/packages/next/client/components/app-router-headers.ts @@ -1,6 +1,7 @@ export const RSC = 'RSC' as const export const NEXT_ROUTER_STATE_TREE = 'Next-Router-State-Tree' as const export const NEXT_ROUTER_PREFETCH = 'Next-Router-Prefetch' as const +export const FETCH_CACHE_HEADER = 'x-vercel-sc-headers' as const export const RSC_VARY_HEADER = `${RSC}, ${NEXT_ROUTER_STATE_TREE}, ${NEXT_ROUTER_PREFETCH}` as const diff --git a/packages/next/client/components/app-router.tsx b/packages/next/client/components/app-router.tsx index 15b1420fa5c94..7de15e24bdd31 100644 --- a/packages/next/client/components/app-router.tsx +++ b/packages/next/client/components/app-router.tsx @@ -95,7 +95,6 @@ export async function fetchServerResponse( } // Ensure the initialParallelRoutes are not combined because of double-rendering in the browser with Strict Mode. -// TODO-APP: move this back into AppRouter let initialParallelRoutes: CacheNode['parallelRoutes'] = typeof window === 'undefined' ? null! : new Map() @@ -254,7 +253,6 @@ function Router({ const routerInstance: AppRouterInstance = { back: () => window.history.back(), forward: () => window.history.forward(), - // TODO-APP: implement prefetching of flight prefetch: async (href) => { // If prefetch has already been triggered, don't trigger it again. if (prefetched.has(href)) { @@ -264,7 +262,6 @@ function Router({ const url = new URL(href, location.origin) try { const routerTree = window.history.state?.tree || initialTree - // TODO-APP: handle case where history.state is not the new router history entry const serverResponse = await fetchServerResponse( url, // initialTree is used when history.state.tree is missing because the history state is set in `useEffect` below, it being missing means this is the hydration case. @@ -301,8 +298,6 @@ function Router({ React.startTransition(() => { dispatch({ type: ACTION_REFRESH, - - // TODO-APP: revisit if this needs to be passed. cache: { status: CacheStates.LAZY_INITIALIZED, data: null, @@ -363,8 +358,7 @@ function Router({ return } - // TODO-APP: this case happens when pushState/replaceState was called outside of Next.js or when the history entry was pushed by the old router. - // It reloads the page in this case but we might have to revisit this as the old router ignores it. + // This case happens when the history entry was pushed by the `pages` router. if (!state.__NA) { window.location.reload() return diff --git a/packages/next/client/components/layout-router.tsx b/packages/next/client/components/layout-router.tsx index 0c6b265239ef5..6f85280383185 100644 --- a/packages/next/client/components/layout-router.tsx +++ b/packages/next/client/components/layout-router.tsx @@ -209,7 +209,6 @@ export function InnerLayoutRouter({ // If cache node has a data request we have to unwrap response by `use` and update the cache. if (childNode.data) { - // TODO-APP: error case /** * Flight response data */ @@ -229,7 +228,6 @@ export function InnerLayoutRouter({ setTimeout(() => { // @ts-ignore startTransition exists React.startTransition(() => { - // TODO-APP: handle redirect changeByServerResponse(fullTree, flightData, overrideCanonicalUrl) }) }) diff --git a/packages/next/client/components/navigation.ts b/packages/next/client/components/navigation.ts index 99bc8f4c8d463..2c8990a9e64a8 100644 --- a/packages/next/client/components/navigation.ts +++ b/packages/next/client/components/navigation.ts @@ -109,7 +109,6 @@ export { useServerInsertedHTML, } from '../../shared/lib/server-inserted-html' -// TODO-APP: Move the other router context over to this one /** * Get the router methods. For example router.push('/dashboard') */ diff --git a/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx b/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx index bb27bc94aedbd..84d83cd46f58c 100644 --- a/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx +++ b/packages/next/client/components/react-dev-overlay/internal/container/Errors.tsx @@ -139,7 +139,7 @@ export const Errors: React.FC = function Errors({ errors }) { const [displayState, setDisplayState] = React.useState< 'minimized' | 'fullscreen' | 'hidden' - >('minimized') + >('fullscreen') const [activeIdx, setActiveIndex] = React.useState(0) const previous = React.useCallback((e?: MouseEvent | TouchEvent) => { e?.preventDefault() diff --git a/packages/next/client/components/react-dev-overlay/internal/container/RootLayoutError.tsx b/packages/next/client/components/react-dev-overlay/internal/container/RootLayoutError.tsx index e9d9648b87f67..5e68fb856175e 100644 --- a/packages/next/client/components/react-dev-overlay/internal/container/RootLayoutError.tsx +++ b/packages/next/client/components/react-dev-overlay/internal/container/RootLayoutError.tsx @@ -14,7 +14,7 @@ export type RootLayoutErrorProps = { missingTags: string[] } export const RootLayoutError: React.FC = function BuildError({ missingTags }) { const message = - 'Please make sure to include the following tags in your root layout: , .\n\n' + + 'Please make sure to include the following tags in your root layout: , , .\n\n' + `Missing required root layout tag${ missingTags.length === 1 ? '' : 's' }: ` + diff --git a/packages/next/client/components/react-dev-overlay/internal/helpers/launchEditor.ts b/packages/next/client/components/react-dev-overlay/internal/helpers/launchEditor.ts index 350163fe646fe..6828faf6eb0a2 100644 --- a/packages/next/client/components/react-dev-overlay/internal/helpers/launchEditor.ts +++ b/packages/next/client/components/react-dev-overlay/internal/helpers/launchEditor.ts @@ -214,7 +214,6 @@ function getArgumentsForLineNumber( function guessEditor(): string[] { // Explicit config always wins if (process.env.REACT_EDITOR) { - // TODO-APP: fix this type return shellQuote.parse(process.env.REACT_EDITOR) as any } diff --git a/packages/next/client/components/react-dev-overlay/internal/helpers/use-websocket.ts b/packages/next/client/components/react-dev-overlay/internal/helpers/use-websocket.ts index 91405df55e1b6..615e7cddbda08 100644 --- a/packages/next/client/components/react-dev-overlay/internal/helpers/use-websocket.ts +++ b/packages/next/client/components/react-dev-overlay/internal/helpers/use-websocket.ts @@ -55,7 +55,6 @@ export function useWebsocketPing( sendMessage( JSON.stringify({ event: 'ping', - // TODO-APP: fix case for dynamic parameters, this will be resolved wrong currently. tree, appDirRoute: true, }) diff --git a/packages/next/client/components/reducer.ts b/packages/next/client/components/reducer.ts index 1a3760124e47d..93898186419ee 100644 --- a/packages/next/client/components/reducer.ts +++ b/packages/next/client/components/reducer.ts @@ -54,7 +54,6 @@ export function createHrefFromUrl( /** * Invalidate cache one level down from the router state. */ -// TODO-APP: Verify if this needs to be recursive. function invalidateCacheByRouterState( newCache: CacheNode, existingCache: CacheNode, @@ -472,12 +471,6 @@ function createOptimisticTree( result[3] = 'refetch' } - // TODO-APP: Revisit - // Add url into the tree - // if (isFirstSegment) { - // result[2] = href - // } - return result } @@ -495,10 +488,6 @@ function applyRouterStatePatchToTree( if (flightSegmentPath.length === 1) { const tree: FlightRouterState = [...treePatch] - // TODO-APP: revisit - // if (url) { - // tree[2] = url - // } return tree } @@ -539,11 +528,6 @@ function applyRouterStatePatchToTree( tree[4] = true } - // TODO-APP: Revisit - // if (url) { - // tree[2] = url - // } - return tree } @@ -812,7 +796,6 @@ function clientReducer( canonicalUrl: mutable.canonicalUrlOverride ? mutable.canonicalUrlOverride : href, - // TODO-APP: verify mpaNavigation not being set is correct here. pushRef: { pendingPush, mpaNavigation: mutable.mpaNavigation, @@ -834,7 +817,6 @@ function clientReducer( canonicalUrl: mutable.canonicalUrlOverride ? mutable.canonicalUrlOverride : href, - // TODO-APP: verify mpaNavigation not being set is correct here. pushRef: { pendingPush, mpaNavigation: false, @@ -877,7 +859,6 @@ function clientReducer( ) if (hardNavigate) { - // TODO-APP: segments.slice(1) strips '', we can get rid of '' altogether. // Copy subTreeData for the root node of the cache. cache.subTreeData = state.cache.subTreeData @@ -1333,7 +1314,6 @@ function clientReducer( const { url, serverResponse } = action const [flightData, canonicalUrlOverride] = serverResponse - // TODO-APP: Implement prefetch for hard navigation if (typeof flightData === 'string') { return state } diff --git a/packages/next/client/components/static-generation-async-storage.ts b/packages/next/client/components/static-generation-async-storage.ts index 7a43c8ad70bba..570f6c4dcf247 100644 --- a/packages/next/client/components/static-generation-async-storage.ts +++ b/packages/next/client/components/static-generation-async-storage.ts @@ -7,6 +7,9 @@ export interface StaticGenerationStore { fetchRevalidate?: number isStaticGeneration?: boolean forceStatic?: boolean + incrementalCache?: import('../../server/lib/incremental-cache').IncrementalCache + pendingRevalidates?: Promise[] + isRevalidate?: boolean } export let staticGenerationAsyncStorage: diff --git a/packages/next/client/link.tsx b/packages/next/client/link.tsx index 80d34bc9b0dda..967a9cda6c200 100644 --- a/packages/next/client/link.tsx +++ b/packages/next/client/link.tsx @@ -522,6 +522,11 @@ const Link = React.forwardRef( // Prefetch the URL if we haven't already and it's visible. React.useEffect(() => { + // in dev, we only prefetch on hover to avoid wasting resources as the prefetch will trigger compiling the page. + if (process.env.NODE_ENV !== 'production') { + return + } + if (!router) { return } diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts index 98cfab1b32c46..e99098f073429 100644 --- a/packages/next/export/worker.ts +++ b/packages/next/export/worker.ts @@ -12,7 +12,7 @@ import '../server/node-polyfill-fetch' import { loadRequireHook } from '../build/webpack/require-hook' import { extname, join, dirname, sep } from 'path' -import { promises } from 'fs' +import fs, { promises } from 'fs' import AmpHtmlValidator from 'next/dist/compiled/amphtml-validator' import { loadComponents } from '../server/load-components' import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic' @@ -33,6 +33,7 @@ import { REDIRECT_ERROR_CODE } from '../client/components/redirect' import { DYNAMIC_ERROR_CODE } from '../client/components/hooks-server-context' import { NOT_FOUND_ERROR_CODE } from '../client/components/not-found' import { NEXT_DYNAMIC_NO_SSR_CODE } from '../shared/lib/no-ssr-error' +import { IncrementalCache } from '../server/lib/incremental-cache' loadRequireHook() @@ -99,6 +100,7 @@ interface RenderOpts { domainLocales?: DomainLocale[] trailingSlash?: boolean supportsDynamicHTML?: boolean + incrementalCache?: import('../server/lib/incremental-cache').IncrementalCache } // expose AsyncLocalStorage on globalThis for react usage @@ -311,6 +313,31 @@ export default async function exportPage({ // and bail when dynamic dependencies are detected // only fully static paths are fully generated here if (isAppDir) { + curRenderOpts.incrementalCache = new IncrementalCache({ + dev: false, + requestHeaders: {}, + flushToDisk: true, + maxMemoryCacheSize: 50 * 1024 * 1024, + getPrerenderManifest: () => ({ + version: 3, + routes: {}, + dynamicRoutes: {}, + preview: { + previewModeEncryptionKey: '', + previewModeId: '', + previewModeSigningKey: '', + }, + notFoundRoutes: [], + }), + fs: { + readFile: (f) => fs.promises.readFile(f, 'utf8'), + readFileSync: (f) => fs.readFileSync(f, 'utf8'), + writeFile: (f, d) => fs.promises.writeFile(f, d, 'utf8'), + mkdir: (dir) => fs.promises.mkdir(dir, { recursive: true }), + stat: (f) => fs.promises.stat(f), + }, + serverDistDir: join(distDir, 'server'), + }) const { renderToHTMLOrFlight } = require('../server/app-render') as typeof import('../server/app-render') diff --git a/packages/next/package.json b/packages/next/package.json index 1d259c42396ee..fd90ccf9f450d 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -75,7 +75,7 @@ ] }, "dependencies": { - "@next/env": "13.0.7-canary.0", + "@next/env": "13.0.7-canary.1", "@swc/helpers": "0.4.14", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", @@ -125,11 +125,11 @@ "@hapi/accept": "5.0.2", "@napi-rs/cli": "2.12.0", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.0.7-canary.0", - "@next/polyfill-nomodule": "13.0.7-canary.0", - "@next/react-dev-overlay": "13.0.7-canary.0", - "@next/react-refresh-utils": "13.0.7-canary.0", - "@next/swc": "13.0.7-canary.0", + "@next/polyfill-module": "13.0.7-canary.1", + "@next/polyfill-nomodule": "13.0.7-canary.1", + "@next/react-dev-overlay": "13.0.7-canary.1", + "@next/react-refresh-utils": "13.0.7-canary.1", + "@next/swc": "13.0.7-canary.1", "@segment/ajv-human-errors": "2.1.2", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 3f084381a02fc..472103a98c974 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -81,6 +81,7 @@ function preloadComponent(Component: any, props: any) { return Component } +const CACHE_ONE_YEAR = 31536000 const INTERNAL_HEADERS_INSTANCE = Symbol('internal for headers readonly') function readonlyHeadersError() { @@ -177,6 +178,8 @@ export type RenderOptsPartial = { assetPrefix?: string fontLoaderManifest?: FontLoaderManifest isBot?: boolean + incrementalCache?: import('./lib/incremental-cache').IncrementalCache + isRevalidate?: boolean } export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial @@ -248,12 +251,115 @@ function patchFetch(ComponentMod: any) { const originFetch = globalThis.fetch globalThis.fetch = async (input, init) => { const staticGenerationStore = - 'getStore' in staticGenerationAsyncStorage + ('getStore' in staticGenerationAsyncStorage ? staticGenerationAsyncStorage.getStore() - : staticGenerationAsyncStorage + : staticGenerationAsyncStorage) || {} + + const { + isStaticGeneration, + fetchRevalidate, + pathname, + incrementalCache, + isRevalidate, + } = (staticGenerationStore || {}) as StaticGenerationStore + + let revalidate: number | undefined | boolean + + if (typeof init?.next?.revalidate === 'number') { + revalidate = init.next.revalidate + } + if (init?.next?.revalidate === false) { + revalidate = CACHE_ONE_YEAR + } + + if ( + !staticGenerationStore.fetchRevalidate || + (typeof revalidate === 'number' && + revalidate < staticGenerationStore.fetchRevalidate) + ) { + staticGenerationStore.fetchRevalidate = revalidate + } + + let cacheKey: string | undefined + + const doOriginalFetch = async () => { + return originFetch(input, init).then(async (res) => { + if ( + incrementalCache && + cacheKey && + typeof revalidate === 'number' && + revalidate > 0 + ) { + const clonedRes = res.clone() + + let base64Body = '' + + if (process.env.NEXT_RUNTIME === 'edge') { + let string = '' + new Uint8Array(await clonedRes.arrayBuffer()).forEach((byte) => { + string += String.fromCharCode(byte) + }) + base64Body = btoa(string) + } else { + base64Body = Buffer.from(await clonedRes.arrayBuffer()).toString( + 'base64' + ) + } + + await incrementalCache.set( + cacheKey, + { + kind: 'FETCH', + isStale: false, + age: 0, + data: { + headers: Object.fromEntries(clonedRes.headers.entries()), + body: base64Body, + }, + revalidate, + }, + revalidate, + true + ) + } + return res + }) + } - const { isStaticGeneration, fetchRevalidate, pathname } = - staticGenerationStore || {} + if (incrementalCache && typeof revalidate === 'number' && revalidate > 0) { + cacheKey = await incrementalCache?.fetchCacheKey(input.toString(), init) + const entry = await incrementalCache.get(cacheKey, true) + + if (entry?.value && entry.value.kind === 'FETCH') { + // when stale and is revalidating we wait for fresh data + // so the revalidated entry has the updated data + if (!isRevalidate || !entry.isStale) { + if (entry.isStale) { + if (!staticGenerationStore.pendingRevalidates) { + staticGenerationStore.pendingRevalidates = [] + } + staticGenerationStore.pendingRevalidates.push( + doOriginalFetch().catch(console.error) + ) + } + + const resData = entry.value.data + let decodedBody = '' + + // TODO: handle non-text response bodies + if (process.env.NEXT_RUNTIME === 'edge') { + decodedBody = atob(resData.body) + } else { + decodedBody = Buffer.from(resData.body, 'base64').toString() + } + + return new Response(decodedBody, { + headers: resData.headers, + status: resData.status, + }) + } + } + } if (staticGenerationStore && isStaticGeneration) { if (init && typeof init === 'object') { @@ -295,7 +401,7 @@ function patchFetch(ComponentMod: any) { if (hasNextConfig) delete init.next } } - return originFetch(input, init) + return doOriginalFetch() } } @@ -774,9 +880,7 @@ export async function renderToHTMLOrFlight( supportsDynamicHTML, } = renderOpts - if (process.env.NODE_ENV === 'production') { - patchFetch(ComponentMod) - } + patchFetch(ComponentMod) const generateStaticHTML = supportsDynamicHTML !== true const staticGenerationAsyncStorage = ComponentMod.staticGenerationAsyncStorage @@ -1114,7 +1218,7 @@ export async function renderToHTMLOrFlight( // otherwise if (layoutOrPageMod.dynamic === 'force-static') { staticGenerationStore.forceStatic = true - } else { + } else if (layoutOrPageMod.dynamic !== 'error') { staticGenerationStore.forceStatic = false } } @@ -1485,7 +1589,6 @@ export async function renderToHTMLOrFlight( // Flight data that is going to be passed to the browser. // Currently a single item array but in the future multiple patches might be combined in a single request. const flightData: FlightData = [ - // TODO-APP: change walk to output without '' ( await walkTreeWithFlightRouterState({ createSegmentPath: (child) => child, @@ -1691,7 +1794,6 @@ export async function renderToHTMLOrFlight( res.statusCode = 404 } - // TODO-APP: show error overlay in development. `element` should probably be wrapped in AppRouter for this case. const renderStream = await renderToInitialStream({ ReactDOMServer, element: ( @@ -1729,6 +1831,10 @@ export async function renderToHTMLOrFlight( } const renderResult = new RenderResult(await bodyResult()) + if (staticGenerationStore.pendingRevalidates) { + await Promise.all(staticGenerationStore.pendingRevalidates) + } + if (isStaticGeneration) { const htmlResult = await streamToBufferedResult(renderResult) @@ -1747,6 +1853,9 @@ export async function renderToHTMLOrFlight( await generateFlight() ) + if (staticGenerationStore?.forceStatic === false) { + staticGenerationStore.fetchRevalidate = 0 + } ;(renderOpts as any).pageData = filteredFlightData ;(renderOpts as any).revalidate = typeof staticGenerationStore?.fetchRevalidate === 'undefined' @@ -1763,6 +1872,8 @@ export async function renderToHTMLOrFlight( isStaticGeneration, inUse: true, pathname, + incrementalCache: renderOpts.incrementalCache, + isRevalidate: renderOpts.isRevalidate, } const tryGetPreviewData = diff --git a/packages/next/server/base-server.ts b/packages/next/server/base-server.ts index b91508e5ad1ba..3cf525acb8e55 100644 --- a/packages/next/server/base-server.ts +++ b/packages/next/server/base-server.ts @@ -78,6 +78,7 @@ import { RSC, RSC_VARY_HEADER, FLIGHT_PARAMETERS, + FETCH_CACHE_HEADER, } from '../client/components/app-router-headers' export type FindComponentsResult = { @@ -310,6 +311,10 @@ export default abstract class Server { res: BaseNextResponse ): void + protected abstract getIncrementalCache(options: { + requestHeaders: Record + }): import('./lib/incremental-cache').IncrementalCache + protected abstract getResponseCache(options: { dev: boolean }): ResponseCacheBase @@ -1283,11 +1288,22 @@ export default abstract class Server { ssgCacheKey = ssgCacheKey === '/index' && pathname === '/' ? '/' : ssgCacheKey } + const incrementalCache = this.getIncrementalCache({ + requestHeaders: Object.assign({}, req.headers), + }) + if ( + this.nextConfig.experimental.fetchCache && + (opts.runtime !== 'experimental-edge' || + (this.serverOptions as any).webServerConfig) + ) { + delete req.headers[FETCH_CACHE_HEADER] + } + let isRevalidate = false const doRender: () => Promise = async () => { let pageData: any let body: RenderResult | null - let sprRevalidate: number | false + let isrRevalidate: number | false let isNotFound: boolean | undefined let isRedirect: boolean | undefined @@ -1312,6 +1328,12 @@ export default abstract class Server { const renderOpts: RenderOpts = { ...components, ...opts, + ...(isAppPath && this.nextConfig.experimental.fetchCache + ? { + incrementalCache, + isRevalidate: this.minimalMode || isRevalidate, + } + : {}), isDataReq, resolvedUrl, locale, @@ -1346,7 +1368,7 @@ export default abstract class Server { body = renderResult // TODO: change this to a different passing mechanism pageData = (renderOpts as any).pageData - sprRevalidate = (renderOpts as any).revalidate + isrRevalidate = (renderOpts as any).revalidate isNotFound = (renderOpts as any).isNotFound isRedirect = (renderOpts as any).isRedirect @@ -1361,7 +1383,7 @@ export default abstract class Server { } value = { kind: 'PAGE', html: body, pageData } } - return { revalidate: sprRevalidate, value } + return { revalidate: isrRevalidate, value } } const cacheEntry = await this.responseCache.get( @@ -1371,6 +1393,10 @@ export default abstract class Server { const isDynamicPathname = isDynamicRoute(pathname) const didRespond = hasResolved || res.sent + if (hadCache) { + isRevalidate = true + } + if (!staticPaths) { ;({ staticPaths, fallbackMode } = hasStaticPaths ? await this.getStaticPaths({ pathname }) @@ -1486,6 +1512,7 @@ export default abstract class Server { } }, { + incrementalCache, isManualRevalidate, isPrefetch: req.headers.purpose === 'prefetch', } diff --git a/packages/next/server/config-schema.ts b/packages/next/server/config-schema.ts index 2567e5298d615..ba1128cf18b3a 100644 --- a/packages/next/server/config-schema.ts +++ b/packages/next/server/config-schema.ts @@ -277,6 +277,9 @@ const configSchema = { fallbackNodePolyfills: { type: 'boolean', }, + fetchCache: { + type: 'boolean', + }, forceSwcTransforms: { type: 'boolean', }, diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 8659f99f4f020..0a56331d28ceb 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -79,6 +79,7 @@ export interface NextJsWebpackConfig { } export interface ExperimentalConfig { + fetchCache?: boolean allowMiddlewareResponseBody?: boolean skipMiddlewareUrlNormalize?: boolean skipTrailingSlashRedirect?: boolean @@ -565,6 +566,7 @@ export const defaultConfig: NextConfig = { swcMinify: true, output: !!process.env.NEXT_PRIVATE_STANDALONE ? 'standalone' : undefined, experimental: { + fetchCache: false, middlewarePrefetch: 'flexible', optimisticClientCache: true, runtime: undefined, diff --git a/packages/next/server/lib/incremental-cache/fetch-cache.ts b/packages/next/server/lib/incremental-cache/fetch-cache.ts new file mode 100644 index 0000000000000..613f50c22ca6c --- /dev/null +++ b/packages/next/server/lib/incremental-cache/fetch-cache.ts @@ -0,0 +1,162 @@ +import LRUCache from 'next/dist/compiled/lru-cache' +import { FETCH_CACHE_HEADER } from '../../../client/components/app-router-headers' +import type { CacheHandler, CacheHandlerContext, CacheHandlerValue } from './' + +let memoryCache: LRUCache | undefined + +export default class FetchCache implements CacheHandler { + private headers: Record + private cacheEndpoint: string + private debug: boolean + + constructor(ctx: CacheHandlerContext) { + if (ctx.maxMemoryCacheSize && !memoryCache) { + memoryCache = new LRUCache({ + max: ctx.maxMemoryCacheSize, + length({ value }) { + if (!value) { + return 25 + } else if (value.kind === 'REDIRECT') { + return JSON.stringify(value.props).length + } else if (value.kind === 'IMAGE') { + throw new Error('invariant image should not be incremental-cache') + } else if (value.kind === 'FETCH') { + return JSON.stringify(value.data || '').length + } + // rough estimate of size of cache value + return ( + value.html.length + (JSON.stringify(value.pageData)?.length || 0) + ) + }, + }) + } + this.debug = !!process.env.NEXT_PRIVATE_DEBUG_CACHE + this.headers = {} + this.headers['Content-Type'] = 'application/json' + + if (FETCH_CACHE_HEADER in ctx._requestHeaders) { + const newHeaders = JSON.parse( + ctx._requestHeaders[FETCH_CACHE_HEADER] as string + ) + for (const k in newHeaders) { + this.headers[k] = newHeaders[k] + } + } + this.cacheEndpoint = `https://${ctx._requestHeaders['x-vercel-sc-host']}${ + ctx._requestHeaders['x-vercel-sc-basepath'] || '' + }` + if (this.debug) { + console.log('using cache endpoint', this.cacheEndpoint) + } + } + + public async get(key: string, fetchCache?: boolean) { + if (!fetchCache) return null + + let data = memoryCache?.get(key) + + // get data from fetch cache + if (!data) { + try { + const start = Date.now() + const res = await fetch( + `${this.cacheEndpoint}/v1/suspense-cache/getItems`, + { + method: 'POST', + body: JSON.stringify([key]), + headers: this.headers, + } + ) + + if (!res.ok) { + console.error(await res.text()) + throw new Error(`invalid response from cache ${res.status}`) + } + + const items = await res.json() + const item = items[key] + + if (!item || !item.value) { + console.log({ item }) + throw new Error(`invalid item returned`) + } + + const cached = JSON.parse(item.value) + + if (!cached || cached.kind !== 'FETCH') { + this.debug && console.log({ cached }) + throw new Error(`invalid cache value`) + } + + data = { + lastModified: Date.now() - item.age * 1000, + value: cached, + } + if (this.debug) { + console.log( + 'got fetch cache entry duration:', + Date.now() - start, + data + ) + } + + if (data) { + memoryCache?.set(key, data) + } + } catch (err) { + // unable to get data from fetch-cache + console.error(`Failed to get from fetch-cache`, err) + } + } + return data || null + } + + public async set( + key: string, + data: CacheHandlerValue['value'], + fetchCache?: boolean + ) { + if (!fetchCache) return + + memoryCache?.set(key, { + value: data, + lastModified: Date.now(), + }) + + try { + const start = Date.now() + const body = JSON.stringify([ + { + id: key, + value: JSON.stringify(data), + }, + ]) + + const res = await fetch( + `${this.cacheEndpoint}/v1/suspense-cache/setItems`, + { + method: 'POST', + headers: this.headers, + body: body, + } + ) + + if (!res.ok) { + this.debug && console.log(await res.text()) + throw new Error(`invalid response ${res.status}`) + } + + if (this.debug) { + console.log( + 'successfully set to fetch-cache duration:', + Date.now() - start, + body + ) + } + } catch (err) { + // unable to set to fetch-cache + console.error(`Failed to update fetch cache`, err) + } + return + } +} diff --git a/packages/next/server/lib/incremental-cache/file-system-cache.ts b/packages/next/server/lib/incremental-cache/file-system-cache.ts index 85f43051ba260..fcba747e46e70 100644 --- a/packages/next/server/lib/incremental-cache/file-system-cache.ts +++ b/packages/next/server/lib/incremental-cache/file-system-cache.ts @@ -2,11 +2,12 @@ import LRUCache from 'next/dist/compiled/lru-cache' import path from '../../../shared/lib/isomorphic/path' import type { CacheHandler, CacheHandlerContext, CacheHandlerValue } from './' +let memoryCache: LRUCache | undefined + export default class FileSystemCache implements CacheHandler { private fs: CacheHandlerContext['fs'] private flushToDisk?: CacheHandlerContext['flushToDisk'] private serverDistDir: CacheHandlerContext['serverDistDir'] - private memoryCache?: LRUCache private appDir: boolean constructor(ctx: CacheHandlerContext) { @@ -15,8 +16,8 @@ export default class FileSystemCache implements CacheHandler { this.serverDistDir = ctx.serverDistDir this.appDir = !!ctx._appDir - if (ctx.maxMemoryCacheSize) { - this.memoryCache = new LRUCache({ + if (ctx.maxMemoryCacheSize && !memoryCache) { + memoryCache = new LRUCache({ max: ctx.maxMemoryCacheSize, length({ value }) { if (!value) { @@ -25,6 +26,8 @@ export default class FileSystemCache implements CacheHandler { return JSON.stringify(value.props).length } else if (value.kind === 'IMAGE') { throw new Error('invariant image should not be incremental-cache') + } else if (value.kind === 'FETCH') { + return JSON.stringify(value.data || '').length } // rough estimate of size of cache value return ( @@ -35,40 +38,55 @@ export default class FileSystemCache implements CacheHandler { } } - public async get(key: string) { - let data = this.memoryCache?.get(key) + public async get(key: string, fetchCache?: boolean) { + let data = memoryCache?.get(key) // let's check the disk for seed data if (!data) { try { - const { filePath: htmlPath, isAppPath } = await this.getFsPath( - `${key}.html` - ) - const html = await this.fs.readFile(htmlPath) - const pageData = isAppPath - ? await this.fs.readFile( - ( - await this.getFsPath(`${key}.rsc`, true) - ).filePath - ) - : JSON.parse( - await this.fs.readFile( - await ( - await this.getFsPath(`${key}.json`, false) + const { filePath, isAppPath } = await this.getFsPath({ + pathname: fetchCache ? key : `${key}.html`, + fetchCache, + }) + const fileData = await this.fs.readFile(filePath) + const { mtime } = await this.fs.stat(filePath) + + if (fetchCache) { + const lastModified = mtime.getTime() + data = { + lastModified, + value: JSON.parse(fileData), + } + } else { + const pageData = isAppPath + ? await this.fs.readFile( + ( + await this.getFsPath({ pathname: `${key}.rsc`, appDir: true }) ).filePath ) - ) - const { mtime } = await this.fs.stat(htmlPath) + : JSON.parse( + await this.fs.readFile( + await ( + await this.getFsPath({ + pathname: `${key}.json`, + appDir: false, + }) + ).filePath + ) + ) + data = { + lastModified: mtime.getTime(), + value: { + kind: 'PAGE', + html: fileData, + pageData, + }, + } + } - data = { - lastModified: mtime.getTime(), - value: { - kind: 'PAGE', - html, - pageData, - }, + if (data) { + memoryCache?.set(key, data) } - this.memoryCache?.set(key, data) } catch (_) { // unable to get data from disk } @@ -77,41 +95,66 @@ export default class FileSystemCache implements CacheHandler { } public async set(key: string, data: CacheHandlerValue['value']) { - if (!this.flushToDisk) return - - this.memoryCache?.set(key, { + memoryCache?.set(key, { value: data, lastModified: Date.now(), }) + if (!this.flushToDisk) return if (data?.kind === 'PAGE') { const isAppPath = typeof data.pageData === 'string' - const { filePath: htmlPath } = await this.getFsPath( - `${key}.html`, - isAppPath - ) + const { filePath: htmlPath } = await this.getFsPath({ + pathname: `${key}.html`, + appDir: isAppPath, + }) await this.fs.mkdir(path.dirname(htmlPath)) await this.fs.writeFile(htmlPath, data.html) await this.fs.writeFile( ( - await this.getFsPath( - `${key}.${isAppPath ? 'rsc' : 'json'}`, - isAppPath - ) + await this.getFsPath({ + pathname: `${key}.${isAppPath ? 'rsc' : 'json'}`, + appDir: isAppPath, + }) ).filePath, isAppPath ? data.pageData : JSON.stringify(data.pageData) ) + } else if (data?.kind === 'FETCH') { + const { filePath } = await this.getFsPath({ + pathname: key, + fetchCache: true, + }) + await this.fs.mkdir(path.dirname(filePath)) + await this.fs.writeFile(filePath, JSON.stringify(data)) } } - private async getFsPath( - pathname: string, + private async getFsPath({ + pathname, + appDir, + fetchCache, + }: { + pathname: string appDir?: boolean - ): Promise<{ + fetchCache?: boolean + }): Promise<{ filePath: string isAppPath: boolean }> { + if (fetchCache) { + // we store in .next/cache/fetch-cache so it can be persisted + // across deploys + return { + filePath: path.join( + this.serverDistDir, + '..', + 'cache', + 'fetch-cache', + pathname + ), + isAppPath: false, + } + } let isAppPath = false let filePath = path.join(this.serverDistDir, 'pages', pathname) diff --git a/packages/next/server/lib/incremental-cache/index.ts b/packages/next/server/lib/incremental-cache/index.ts index 81a325ff59509..3805d418f101a 100644 --- a/packages/next/server/lib/incremental-cache/index.ts +++ b/packages/next/server/lib/incremental-cache/index.ts @@ -3,6 +3,7 @@ import FileSystemCache from './file-system-cache' import { PrerenderManifest } from '../../../build' import path from '../../../shared/lib/isomorphic/path' import { normalizePagePath } from '../../../shared/lib/page-path/normalize-page-path' +import FetchCache from './fetch-cache' import { IncrementalCacheValue, IncrementalCacheEntry, @@ -18,7 +19,8 @@ export interface CacheHandlerContext { flushToDisk?: boolean serverDistDir: string maxMemoryCacheSize?: number - _appDir?: boolean + _appDir: boolean + _requestHeaders: IncrementalCache['requestHeaders'] } export interface CacheHandlerValue { @@ -30,13 +32,17 @@ export class CacheHandler { // eslint-disable-next-line constructor(_ctx: CacheHandlerContext) {} - public async get(_key: string): Promise { + public async get( + _key: string, + _fetchCache?: boolean + ): Promise { return {} as any } public async set( _key: string, - _data: IncrementalCacheValue | null + _data: IncrementalCacheValue | null, + _fetchCache?: boolean ): Promise {} } @@ -44,13 +50,18 @@ export class IncrementalCache { dev?: boolean cacheHandler: CacheHandler prerenderManifest: PrerenderManifest + requestHeaders: Record + minimalMode?: boolean constructor({ fs, dev, appDir, flushToDisk, + fetchCache, + minimalMode, serverDistDir, + requestHeaders, maxMemoryCacheSize, getPrerenderManifest, incrementalCacheHandlerPath, @@ -58,8 +69,11 @@ export class IncrementalCache { fs: CacheFs dev: boolean appDir?: boolean + fetchCache?: boolean + minimalMode?: boolean serverDistDir: string flushToDisk?: boolean + requestHeaders: IncrementalCache['requestHeaders'] maxMemoryCacheSize?: number incrementalCacheHandlerPath?: string getPrerenderManifest: () => PrerenderManifest @@ -71,11 +85,17 @@ export class IncrementalCache { cacheHandlerMod = cacheHandlerMod.default || cacheHandlerMod } + if (minimalMode && fetchCache) { + cacheHandlerMod = FetchCache + } + if (process.env.__NEXT_TEST_MAX_ISR_CACHE) { // Allow cache size to be overridden for testing purposes maxMemoryCacheSize = parseInt(process.env.__NEXT_TEST_MAX_ISR_CACHE, 10) } this.dev = dev + this.minimalMode = minimalMode + this.requestHeaders = requestHeaders this.prerenderManifest = getPrerenderManifest() this.cacheHandler = new (cacheHandlerMod as typeof CacheHandler)({ dev, @@ -83,7 +103,8 @@ export class IncrementalCache { flushToDisk, serverDistDir, maxMemoryCacheSize, - _appDir: appDir, + _appDir: !!appDir, + _requestHeaders: requestHeaders, }) } @@ -110,19 +131,75 @@ export class IncrementalCache { return revalidateAfter } - _getPathname(pathname: string) { - return normalizePagePath(pathname) + _getPathname(pathname: string, fetchCache?: boolean) { + return fetchCache ? pathname : normalizePagePath(pathname) + } + + // x-ref: https://github.com/facebook/react/blob/2655c9354d8e1c54ba888444220f63e836925caa/packages/react/src/ReactFetch.js#L23 + async fetchCacheKey(url: string, init: RequestInit = {}): Promise { + const cacheString = JSON.stringify([ + url, + init.method, + init.headers, + init.mode, + init.redirect, + init.credentials, + init.referrer, + init.referrerPolicy, + init.integrity, + init.next, + init.cache, + ]) + let cacheKey: string + + if (process.env.NEXT_RUNTIME === 'edge') { + function bufferToHex(buffer: ArrayBuffer): string { + return Array.prototype.map + .call(new Uint8Array(buffer), (b) => b.toString(16).padStart(2, '0')) + .join('') + } + const buffer = new TextEncoder().encode(cacheString) + cacheKey = bufferToHex(await crypto.subtle.digest('SHA-256', buffer)) + } else { + const crypto = require('crypto') as typeof import('crypto') + cacheKey = crypto.createHash('sha256').update(cacheString).digest('hex') + } + return cacheKey } // get data from cache if available - async get(pathname: string): Promise { + async get( + pathname: string, + fetchCache?: boolean + ): Promise { // we don't leverage the prerender cache in dev mode // so that getStaticProps is always called for easier debugging if (this.dev) return null - pathname = this._getPathname(pathname) + pathname = this._getPathname(pathname, fetchCache) let entry: IncrementalCacheEntry | null = null - const cacheData = await this.cacheHandler.get(pathname) + const cacheData = await this.cacheHandler.get(pathname, fetchCache) + + if (cacheData?.value?.kind === 'FETCH') { + const data = cacheData.value.data + const age = Math.round( + (Date.now() - (cacheData.lastModified || 0)) / 1000 + ) + const revalidate = cacheData.value.revalidate + + return { + isStale: age > revalidate, + value: { + kind: 'FETCH', + data, + age, + revalidate, + isStale: age > revalidate, + }, + revalidateAfter: + (cacheData.lastModified || Date.now()) + revalidate * 1000, + } + } const curRevalidate = this.prerenderManifest.routes[toRoute(pathname)]?.initialRevalidateSeconds @@ -159,7 +236,7 @@ export class IncrementalCache { curRevalidate, revalidateAfter, } - this.set(pathname, entry.value, curRevalidate) + this.set(pathname, entry.value, curRevalidate, fetchCache) } return entry } @@ -168,16 +245,17 @@ export class IncrementalCache { async set( pathname: string, data: IncrementalCacheValue | null, - revalidateSeconds?: number | false + revalidateSeconds?: number | false, + fetchCache?: boolean ) { if (this.dev) return - pathname = this._getPathname(pathname) + pathname = this._getPathname(pathname, fetchCache) try { // we use the prerender manifest memory instance // to store revalidate timings for calculating // revalidateAfter values so we update this on set - if (typeof revalidateSeconds !== 'undefined') { + if (typeof revalidateSeconds !== 'undefined' && !fetchCache) { this.prerenderManifest.routes[pathname] = { dataRoute: path.posix.join( '/_next/data', @@ -187,7 +265,7 @@ export class IncrementalCache { initialRevalidateSeconds: revalidateSeconds, } } - await this.cacheHandler.set(pathname, data) + await this.cacheHandler.set(pathname, data, fetchCache) } catch (error) { console.warn('Failed to update prerender cache for', pathname, error) } diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index db89c1cfe9dc2..058f23239ac2e 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -228,15 +228,7 @@ export default class NextNodeServer extends BaseServer { } if (!this.minimalMode) { - const { ImageOptimizerCache } = - require('./image-optimizer') as typeof import('./image-optimizer') - this.imageResponseCache = new ResponseCache( - new ImageOptimizerCache({ - distDir: this.distDir, - nextConfig: this.nextConfig, - }), - this.minimalMode - ) + this.imageResponseCache = new ResponseCache(this.minimalMode) } if (!options.dev) { @@ -278,12 +270,23 @@ export default class NextNodeServer extends BaseServer { loadEnvConfig(this.dir, dev, Log, forceReload) } - protected getResponseCache({ dev }: { dev: boolean }) { - const incrementalCache = new IncrementalCache({ + protected getIncrementalCache({ + requestHeaders, + }: { + requestHeaders: IncrementalCache['requestHeaders'] + }) { + const dev = !!this.renderOpts.dev + // incremental-cache is request specific with a shared + // although can have shared caches in module scope + // per-cache handler + return new IncrementalCache({ fs: this.getCacheFilesystem(), dev, - serverDistDir: this.serverDistDir, + requestHeaders, appDir: this.hasAppDir, + minimalMode: this.minimalMode, + serverDistDir: this.serverDistDir, + fetchCache: this.nextConfig.experimental.fetchCache, maxMemoryCacheSize: this.nextConfig.experimental.isrMemoryCacheSize, flushToDisk: !this.minimalMode && this.nextConfig.experimental.isrFlushToDisk, @@ -303,8 +306,10 @@ export default class NextNodeServer extends BaseServer { } }, }) + } - return new ResponseCache(incrementalCache, this.minimalMode) + protected getResponseCache() { + return new ResponseCache(this.minimalMode) } protected getPublicDir(): string { @@ -383,7 +388,15 @@ export default class NextNodeServer extends BaseServer { finished: true, } } - const { getHash, ImageOptimizerCache, sendResponse, ImageError } = + const { ImageOptimizerCache } = + require('./image-optimizer') as typeof import('./image-optimizer') + + const imageOptimizerCache = new ImageOptimizerCache({ + distDir: this.distDir, + nextConfig: this.nextConfig, + }) + + const { getHash, sendResponse, ImageError } = require('./image-optimizer') as typeof import('./image-optimizer') if (!this.imageResponseCache) { @@ -391,7 +404,6 @@ export default class NextNodeServer extends BaseServer { 'invariant image optimizer cache was not initialized' ) } - const imagesConfig = this.nextConfig.images if (imagesConfig.loader !== 'default') { @@ -434,7 +446,9 @@ export default class NextNodeServer extends BaseServer { revalidate: maxAge, } }, - {} + { + incrementalCache: imageOptimizerCache, + } ) if (cacheEntry?.value?.kind !== 'IMAGE') { diff --git a/packages/next/server/node-web-streams-helper.ts b/packages/next/server/node-web-streams-helper.ts index c82ff473824f2..a2062a66778f2 100644 --- a/packages/next/server/node-web-streams-helper.ts +++ b/packages/next/server/node-web-streams-helper.ts @@ -288,15 +288,19 @@ export function createRootLayoutValidatorStream( getTree: () => FlightRouterState ): TransformStream { let foundHtml = false + let foundHead = false let foundBody = false return new TransformStream({ async transform(chunk, controller) { - if (!foundHtml || !foundBody) { + if (!foundHtml || !foundHead || !foundBody) { const content = decodeText(chunk) if (!foundHtml && content.includes('> previousCacheItem?: { key: string @@ -19,8 +18,7 @@ export default class ResponseCache { } minimalMode?: boolean - constructor(incrementalCache: IncrementalCache, minimalMode: boolean) { - this.incrementalCache = incrementalCache + constructor(minimalMode: boolean) { this.pendingResponses = new Map() this.minimalMode = minimalMode } @@ -31,8 +29,10 @@ export default class ResponseCache { context: { isManualRevalidate?: boolean isPrefetch?: boolean + incrementalCache: IncrementalCache } ): Promise { + const { incrementalCache } = context // ensure manual revalidate doesn't block normal requests const pendingResponseKey = key ? `${key}-${context.isManualRevalidate ? '1' : '0'}` @@ -41,6 +41,7 @@ export default class ResponseCache { const pendingResponse = pendingResponseKey ? this.pendingResponses.get(pendingResponseKey) : null + if (pendingResponse) { return pendingResponse } @@ -92,9 +93,15 @@ export default class ResponseCache { let cachedResponse: IncrementalCacheItem = null try { cachedResponse = - key && !this.minimalMode ? await this.incrementalCache.get(key) : null + key && !this.minimalMode ? await incrementalCache.get(key) : null if (cachedResponse && !context.isManualRevalidate) { + if (cachedResponse.value?.kind === 'FETCH') { + throw new Error( + `invariant: unexpected cachedResponse of kind fetch in response cache` + ) + } + resolve({ isStale: cachedResponse.isStale, revalidate: cachedResponse.curRevalidate, @@ -136,7 +143,7 @@ export default class ResponseCache { expiresAt: Date.now() + 1000, } } else { - await this.incrementalCache.set( + await incrementalCache.set( key, cacheEntry.value?.kind === 'PAGE' ? { @@ -159,7 +166,7 @@ export default class ResponseCache { // when a getStaticProps path is erroring we automatically re-set the // existing cache under a new expiration to prevent non-stop retrying if (cachedResponse && key) { - await this.incrementalCache.set( + await incrementalCache.set( key, cachedResponse.value, Math.min(Math.max(cachedResponse.revalidate || 3, 3), 30) diff --git a/packages/next/server/response-cache/types.ts b/packages/next/server/response-cache/types.ts index 63c2d37c4f659..a6459121b62cf 100644 --- a/packages/next/server/response-cache/types.ts +++ b/packages/next/server/response-cache/types.ts @@ -7,10 +7,19 @@ export interface ResponseCacheBase { context: { isManualRevalidate?: boolean isPrefetch?: boolean + incrementalCache: IncrementalCache } ): Promise } +export interface CachedFetchValue { + kind: 'FETCH' + data: any + isStale: boolean + age: number + revalidate: number +} + export interface CachedRedirectValue { kind: 'REDIRECT' props: Object @@ -53,6 +62,7 @@ export type IncrementalCacheValue = | CachedRedirectValue | IncrementalCachedPageValue | CachedImageValue + | CachedFetchValue export type ResponseCacheValue = | CachedRedirectValue diff --git a/packages/next/server/response-cache/web.ts b/packages/next/server/response-cache/web.ts index 619b0bd81f18a..0fea0028a5e0f 100644 --- a/packages/next/server/response-cache/web.ts +++ b/packages/next/server/response-cache/web.ts @@ -24,6 +24,7 @@ export default class WebResponseCache { context: { isManualRevalidate?: boolean isPrefetch?: boolean + incrementalCache: any } ): Promise { // ensure manual revalidate doesn't block normal requests diff --git a/packages/next/server/web-server.ts b/packages/next/server/web-server.ts index 5cfdaca5c6331..e236f6b881f75 100644 --- a/packages/next/server/web-server.ts +++ b/packages/next/server/web-server.ts @@ -54,6 +54,9 @@ export default class NextWebServer extends BaseServer { // For the web server layer, compression is automatically handled by the // upstream proxy (edge runtime or node server) and we can simply skip here. } + protected getIncrementalCache() { + return {} as any + } protected getResponseCache() { return new WebResponseCache(this.minimalMode) } diff --git a/packages/next/server/web/adapter.ts b/packages/next/server/web/adapter.ts index 062dca353ae0b..9595dd6208461 100644 --- a/packages/next/server/web/adapter.ts +++ b/packages/next/server/web/adapter.ts @@ -11,6 +11,7 @@ import { NextURL } from './next-url' import { stripInternalSearchParams } from '../internal-utils' import { normalizeRscPath } from '../../shared/lib/router/utils/app-paths' import { + FETCH_CACHE_HEADER, NEXT_ROUTER_PREFETCH, NEXT_ROUTER_STATE_TREE, RSC, @@ -45,6 +46,7 @@ const FLIGHT_PARAMETERS = [ [RSC], [NEXT_ROUTER_STATE_TREE], [NEXT_ROUTER_PREFETCH], + [FETCH_CACHE_HEADER], ] as const export async function adapter(params: { diff --git a/packages/next/shared/lib/router/adapters.tsx b/packages/next/shared/lib/router/adapters.tsx index 9bf0096abad2b..ed237d3525fa0 100644 --- a/packages/next/shared/lib/router/adapters.tsx +++ b/packages/next/shared/lib/router/adapters.tsx @@ -117,7 +117,14 @@ export function PathnameContextProviderAdapter({ // any query strings), so it should have that stripped. Read more about the // `asPath` option over at: // https://nextjs.org/docs/api-reference/next/router#router-object - const url = new URL(router.asPath, 'http://f') + let url: URL + try { + url = new URL(router.asPath, 'http://f') + } catch (_) { + // fallback to / for invalid asPath values e.g. // + return '/' + } + return url.pathname }, [router.asPath, router.isFallback, router.isReady, router.pathname]) diff --git a/packages/next/types/global.d.ts b/packages/next/types/global.d.ts index e07479561b08e..2332e513e6729 100644 --- a/packages/next/types/global.d.ts +++ b/packages/next/types/global.d.ts @@ -39,7 +39,7 @@ interface Window { } interface NextFetchRequestConfig { - revalidate?: number + revalidate?: number | false } interface RequestInit { diff --git a/packages/next/types/index.d.ts b/packages/next/types/index.d.ts index c2550b9bf3e8d..f1186464bb04d 100644 --- a/packages/next/types/index.d.ts +++ b/packages/next/types/index.d.ts @@ -40,7 +40,6 @@ declare module 'react' { nonce?: string } - // TODO-APP: check if this is the right type. function use(promise: Promise | React.Context): T function cache(fn: T): T } diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index eb26abaab334c..5e82ebe994746 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 5b509c84a7895..987a5c924d18a 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "13.0.7-canary.0", + "version": "13.0.7-canary.1", "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 7ada15e64a742..a116c08f070ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,7 @@ importers: '@edge-runtime/jest-environment': 2.0.0 '@fullhuman/postcss-purgecss': 1.3.0 '@mdx-js/loader': ^1.5.1 + '@mdx-js/react': ^1.6.18 '@next/bundle-analyzer': workspace:* '@next/env': workspace:* '@next/eslint-plugin-next': workspace:* @@ -79,7 +80,9 @@ importers: alex: 9.1.0 amphtml-validator: 1.0.35 async-sema: 3.0.1 + body-parser: 1.20.1 browserslist: 4.20.2 + buffer: 5.6.0 chalk: 5.0.1 cheerio: 0.22.0 cookie: 0.4.1 @@ -105,6 +108,7 @@ importers: express: 4.17.0 faker: 5.5.3 faunadb: 2.6.1 + find-up: 4.1.0 firebase: 7.14.5 flat: 5.0.2 form-data: 4.0.0 @@ -113,16 +117,19 @@ importers: glob: 7.1.6 gzip-size: 5.1.1 html-validator: 5.1.18 + http-proxy: 1.18.1 husky: 8.0.0 image-size: 0.9.3 is-animated: 2.0.2 isomorphic-unfetch: 3.0.0 jest: 27.0.6 jest-extended: 1.2.1 + json5: 2.2.1 ky: 0.19.1 ky-universal: 0.6.0 lerna: 4.0.0 lint-staged: 10.1.7 + lodash: 4.17.20 lost: 8.3.1 minimatch: 3.0.4 moment: ^2.24.0 @@ -162,7 +169,9 @@ importers: selenium-webdriver: 4.0.0-beta.4 semver: 7.3.7 shell-quote: 1.7.3 + strip-ansi: 6.0.0 styled-components: 6.0.0-beta.5 + styled-jsx: 5.1.0 styled-jsx-plugin-postcss: 3.0.2 swr: 2.0.0-rc.0 tailwindcss: 1.1.3 @@ -171,9 +180,12 @@ importers: tsec: 0.2.1 turbo: 1.6.3 typescript: 4.8.2 + unfetch: 4.2.0 wait-port: 0.2.2 webpack: 5.74.0 webpack-bundle-analyzer: 4.7.0 + whatwg-fetch: 3.0.0 + ws: 8.2.3 devDependencies: '@babel/core': 7.18.0 '@babel/eslint-parser': 7.18.2_uxoojzahptggrua2tvdiqlh7xm @@ -185,6 +197,7 @@ importers: '@edge-runtime/jest-environment': 2.0.0 '@fullhuman/postcss-purgecss': 1.3.0 '@mdx-js/loader': 1.6.22_react@18.2.0 + '@mdx-js/react': 1.6.22_react@18.2.0 '@next/bundle-analyzer': link:packages/next-bundle-analyzer '@next/env': link:packages/next-env '@next/eslint-plugin-next': link:packages/eslint-plugin-next @@ -242,7 +255,9 @@ importers: alex: 9.1.0 amphtml-validator: 1.0.35 async-sema: 3.0.1 + body-parser: 1.20.1 browserslist: 4.20.2 + buffer: 5.6.0 chalk: 5.0.1 cheerio: 0.22.0 cookie: 0.4.1 @@ -268,6 +283,7 @@ importers: express: 4.17.0 faker: 5.5.3 faunadb: 2.6.1 + find-up: 4.1.0 firebase: 7.14.5 flat: 5.0.2 form-data: 4.0.0 @@ -276,16 +292,19 @@ importers: glob: 7.1.6 gzip-size: 5.1.1 html-validator: 5.1.18 + http-proxy: 1.18.1 husky: 8.0.0 image-size: 0.9.3 is-animated: 2.0.2 isomorphic-unfetch: 3.0.0 jest: 27.0.6_node-notifier@8.0.1 jest-extended: 1.2.1 + json5: 2.2.1 ky: 0.19.1 ky-universal: 0.6.0_ky@0.19.1 lerna: 4.0.0 lint-staged: 10.1.7 + lodash: 4.17.20 lost: 8.3.1 minimatch: 3.0.4 moment: 2.24.0 @@ -325,7 +344,9 @@ importers: selenium-webdriver: 4.0.0-beta.4 semver: 7.3.7 shell-quote: 1.7.3 + strip-ansi: 6.0.0 styled-components: 6.0.0-beta.5_biqbaboplfbrettd7655fr4n2y + styled-jsx: 5.1.0_uuaxwgga6hqycsez5ok7v2wg4i styled-jsx-plugin-postcss: 3.0.2 swr: 2.0.0-rc.0_react@18.2.0 tailwindcss: 1.1.3 @@ -334,9 +355,12 @@ importers: tsec: 0.2.1_sbe2uaqno6akssxfwbhgeg7v2q turbo: 1.6.3 typescript: 4.8.2 + unfetch: 4.2.0 wait-port: 0.2.2 webpack: 5.74.0_@swc+core@1.2.203 webpack-bundle-analyzer: 4.7.0 + whatwg-fetch: 3.0.0 + ws: 8.2.3 bench/vercel: specifiers: @@ -410,7 +434,7 @@ importers: packages/eslint-config-next: specifiers: - '@next/eslint-plugin-next': 13.0.7-canary.0 + '@next/eslint-plugin-next': 13.0.7-canary.1 '@rushstack/eslint-patch': ^1.1.3 '@typescript-eslint/parser': ^5.42.0 eslint-import-resolver-node: ^0.3.6 @@ -478,12 +502,12 @@ importers: '@hapi/accept': 5.0.2 '@napi-rs/cli': 2.12.0 '@napi-rs/triples': 1.1.0 - '@next/env': 13.0.7-canary.0 - '@next/polyfill-module': 13.0.7-canary.0 - '@next/polyfill-nomodule': 13.0.7-canary.0 - '@next/react-dev-overlay': 13.0.7-canary.0 - '@next/react-refresh-utils': 13.0.7-canary.0 - '@next/swc': 13.0.7-canary.0 + '@next/env': 13.0.7-canary.1 + '@next/polyfill-module': 13.0.7-canary.1 + '@next/polyfill-nomodule': 13.0.7-canary.1 + '@next/react-dev-overlay': 13.0.7-canary.1 + '@next/react-refresh-utils': 13.0.7-canary.1 + '@next/swc': 13.0.7-canary.1 '@segment/ajv-human-errors': 2.1.2 '@swc/helpers': 0.4.14 '@taskr/clear': 1.1.0 @@ -8823,6 +8847,26 @@ packages: - supports-color dev: true + /body-parser/1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.4 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: true + /boolbase/1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -9083,6 +9127,11 @@ packages: engines: {node: '>= 0.8'} dev: true + /bytes/3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: true + /cacache/15.3.0: resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} engines: {node: '>= 10'} @@ -9594,7 +9643,6 @@ packages: /client-only/0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - dev: false /cliui/5.0.0: resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==} @@ -10915,6 +10963,11 @@ packages: engines: {node: '>= 0.6'} dev: true + /depd/2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: true + /deprecation/2.3.1: resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} dev: true @@ -10930,6 +10983,11 @@ packages: resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==} dev: true + /destroy/1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: true + /detab/2.0.4: resolution: {integrity: sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==} dependencies: @@ -11253,7 +11311,7 @@ packages: dev: true /ee-first/1.1.1: - resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: true /ejs/3.1.8: @@ -12532,7 +12590,7 @@ packages: dev: true /find-up/2.1.0: - resolution: {integrity: sha1-RdG35QbHF93UgndaK3eSCjwMV6c=} + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} engines: {node: '>=4'} dependencies: locate-path: 2.0.0 @@ -13815,6 +13873,17 @@ packages: toidentifier: 1.0.0 dev: true + /http-errors/2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: true + /http-parser-js/0.4.10: resolution: {integrity: sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=} dev: true @@ -14814,7 +14883,7 @@ packages: resolution: {integrity: sha512-V0tmJSYfkKokZ5mgl0cmfQMTb7MLHsBMngTkbLY0eXvKqiVRRoZP04Ly+KhKrJfKtzC9E6Pp15Jo+bwh7Vi2XQ==} dependencies: node-fetch: 2.6.7 - unfetch: 4.1.0 + unfetch: 4.2.0 transitivePeerDependencies: - encoding dev: true @@ -16455,6 +16524,10 @@ packages: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: true + /lodash/4.17.20: + resolution: {integrity: sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==} + dev: true + /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -16796,7 +16869,7 @@ packages: dev: true /media-typer/0.3.0: - resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} dev: true @@ -19951,6 +20024,13 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} dev: true + /qs/6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: true + /qs/6.5.2: resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==} engines: {node: '>=0.6'} @@ -19961,11 +20041,6 @@ packages: engines: {node: '>=0.6'} dev: true - /qs/6.9.1: - resolution: {integrity: sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==} - engines: {node: '>=0.6'} - dev: true - /querystring-es3/0.2.1: resolution: {integrity: sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=} engines: {node: '>=0.4.x'} @@ -20056,6 +20131,16 @@ packages: unpipe: 1.0.0 dev: true + /raw-body/2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: true + /rc/1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -20817,7 +20902,7 @@ packages: dev: true /requires-port/1.0.0: - resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=} + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true /reselect/4.1.6: @@ -21278,7 +21363,7 @@ packages: jszip: 3.7.1 rimraf: 3.0.2 tmp: 0.2.1 - ws: 8.2.3 + ws: 8.4.2 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -21406,6 +21491,10 @@ packages: resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} dev: true + /setprototypeof/1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: true + /sha.js/2.4.11: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} hasBin: true @@ -21820,6 +21909,11 @@ packages: engines: {node: '>= 0.6'} dev: true + /statuses/2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: true + /stream-browserify/3.0.0: resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} dependencies: @@ -21928,8 +22022,8 @@ packages: call-bind: 1.0.2 define-properties: 1.1.3 es-abstract: 1.19.1 - get-intrinsic: 1.1.1 - has-symbols: 1.0.2 + get-intrinsic: 1.1.2 + has-symbols: 1.0.3 internal-slot: 1.0.3 regexp.prototype.flags: 1.3.1 side-channel: 1.0.4 @@ -22165,7 +22259,6 @@ packages: '@babel/core': 7.18.0 client-only: 0.0.1 react: 18.2.0 - dev: false /stylehacks/4.0.3: resolution: {integrity: sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==} @@ -22193,7 +22286,7 @@ packages: formidable: 1.2.1 methods: 1.1.2 mime: 1.6.0 - qs: 6.9.1 + qs: 6.11.0 readable-stream: 2.3.7 transitivePeerDependencies: - supports-color @@ -22707,6 +22800,11 @@ packages: engines: {node: '>=0.6'} dev: true + /toidentifier/1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: true + /totalist/1.1.0: resolution: {integrity: sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==} engines: {node: '>=6'} @@ -23032,8 +23130,8 @@ packages: busboy: 1.6.0 dev: true - /unfetch/4.1.0: - resolution: {integrity: sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==} + /unfetch/4.2.0: + resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==} dev: true /unherit/1.1.2: @@ -23312,7 +23410,7 @@ packages: dev: true /unpipe/1.0.0: - resolution: {integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=} + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} dev: true diff --git a/test/development/acceptance-app/ReactRefreshLogBox.test.ts b/test/development/acceptance-app/ReactRefreshLogBox.test.ts index b12561077e0ef..d78957b0d31a3 100644 --- a/test/development/acceptance-app/ReactRefreshLogBox.test.ts +++ b/test/development/acceptance-app/ReactRefreshLogBox.test.ts @@ -49,7 +49,7 @@ describe('ReactRefreshLogBox app', () => { ) await session.evaluate(() => document.querySelector('a').click()) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) expect(await session.getRedboxSource()).toMatchSnapshot() await cleanup() @@ -228,7 +228,7 @@ describe('ReactRefreshLogBox app', () => { ` ) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) expect(await session.getRedboxSource()).toMatchSnapshot() // TODO-APP: re-enable when error recovery doesn't reload the page. @@ -325,7 +325,13 @@ describe('ReactRefreshLogBox app', () => { export default ClassDefault; ` ) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) + + await check(async () => { + const source = await session.getRedboxSource() + return source?.length > 1 ? 'success' : source + }, 'success') + expect(await session.getRedboxSource()).toMatchSnapshot() await cleanup() @@ -370,7 +376,7 @@ describe('ReactRefreshLogBox app', () => { ` ) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) if (process.platform === 'win32') { expect(await session.getRedboxSource()).toMatchSnapshot() } else { @@ -423,7 +429,7 @@ describe('ReactRefreshLogBox app', () => { ) // We get an error because Foo didn't import React. Fair. - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) expect(await session.getRedboxSource()).toMatchSnapshot() // Let's add that to Foo. @@ -475,7 +481,7 @@ describe('ReactRefreshLogBox app', () => { ) await new Promise((resolve) => setTimeout(resolve, 1000)) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) if (process.platform === 'win32') { expect(await session.getRedboxSource()).toMatchSnapshot() } else { @@ -562,7 +568,7 @@ describe('ReactRefreshLogBox app', () => { `export default function FunctionDefault() { throw new Error('no'); }` ) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) expect(await session.getRedboxSource()).toMatchSnapshot() expect( await session.evaluate(() => document.querySelector('h2').textContent) @@ -664,7 +670,7 @@ describe('ReactRefreshLogBox app', () => { ` ) - expect(await session.hasErrorToast()).toBe(false) + expect(await session.hasRedbox()).toBe(false) expect( await session.evaluate(() => document.querySelector('p').textContent) ).toBe('hello') @@ -681,7 +687,7 @@ describe('ReactRefreshLogBox app', () => { ` ) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) expect(await session.getRedboxSource()).toMatchSnapshot() await session.patch( @@ -764,9 +770,9 @@ describe('ReactRefreshLogBox app', () => { ` ) - expect(await session.hasErrorToast()).toBe(false) + expect(await session.hasRedbox()).toBe(false) await session.evaluate(() => document.querySelector('button').click()) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) const header = await session.getRedboxDescription() expect(header).toMatchSnapshot() @@ -810,9 +816,9 @@ describe('ReactRefreshLogBox app', () => { ` ) - expect(await session.hasErrorToast()).toBe(false) + expect(await session.hasRedbox()).toBe(false) await session.evaluate(() => document.querySelector('button').click()) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) const header2 = await session.getRedboxDescription() expect(header2).toMatchSnapshot() @@ -856,9 +862,9 @@ describe('ReactRefreshLogBox app', () => { ` ) - expect(await session.hasErrorToast()).toBe(false) + expect(await session.hasRedbox()).toBe(false) await session.evaluate(() => document.querySelector('button').click()) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) const header3 = await session.getRedboxDescription() expect(header3).toMatchSnapshot() @@ -902,9 +908,9 @@ describe('ReactRefreshLogBox app', () => { ` ) - expect(await session.hasErrorToast()).toBe(false) + expect(await session.hasRedbox()).toBe(false) await session.evaluate(() => document.querySelector('button').click()) - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) const header4 = await session.getRedboxDescription() expect(header4).toMatchInlineSnapshot( diff --git a/test/development/acceptance-app/ReactRefreshRegression.test.ts b/test/development/acceptance-app/ReactRefreshRegression.test.ts index 9725b9b0580d2..88c19bf0cd53b 100644 --- a/test/development/acceptance-app/ReactRefreshRegression.test.ts +++ b/test/development/acceptance-app/ReactRefreshRegression.test.ts @@ -280,7 +280,7 @@ describe('ReactRefreshRegression app', () => { await browser.refresh() - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) const source = await session.getRedboxSource() expect(source.split(/\r?\n/g).slice(2).join('\n')).toMatchInlineSnapshot(` @@ -301,7 +301,7 @@ describe('ReactRefreshRegression app', () => { await browser.refresh() - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) const source = await session.getRedboxSource() expect(source.split(/\r?\n/g).slice(2).join('\n')).toMatchInlineSnapshot(` @@ -323,7 +323,7 @@ describe('ReactRefreshRegression app', () => { await browser.refresh() - await session.waitForAndOpenRuntimeError() + expect(await session.hasRedbox(true)).toBe(true) const source = await session.getRedboxSource() expect(source.split(/\r?\n/g).slice(2).join('\n')).toMatchInlineSnapshot(` diff --git a/test/development/acceptance-app/helpers.ts b/test/development/acceptance-app/helpers.ts index 0ad5c8db77e1c..f0787fb84b76d 100644 --- a/test/development/acceptance-app/helpers.ts +++ b/test/development/acceptance-app/helpers.ts @@ -2,9 +2,7 @@ import { getRedboxDescription, getRedboxHeader, getRedboxSource, - hasErrorToast, hasRedbox, - waitForAndOpenRuntimeError, } from 'next-test-utils' import webdriver from 'next-webdriver' import { NextInstance } from 'test/lib/next-modes/base' @@ -102,12 +100,6 @@ export async function sandbox( async hasRedbox(expected = false) { return hasRedbox(browser, expected) }, - async hasErrorToast(expected = false) { - return hasErrorToast(browser, expected) - }, - async waitForAndOpenRuntimeError() { - await waitForAndOpenRuntimeError(browser) - }, async getRedboxDescription() { return getRedboxDescription(browser) }, diff --git a/test/development/typescript-auto-install/index.test.ts b/test/development/typescript-auto-install/index.test.ts index ec794f3a4aeb2..acfed68a05f33 100644 --- a/test/development/typescript-auto-install/index.test.ts +++ b/test/development/typescript-auto-install/index.test.ts @@ -2,7 +2,6 @@ import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import { check, renderViaHTTP } from 'next-test-utils' import webdriver from 'next-webdriver' -// @ts-expect-error missing types import stripAnsi from 'strip-ansi' describe('typescript-auto-install', () => { diff --git a/test/e2e/app-dir/app-static.test.ts b/test/e2e/app-dir/app-static.test.ts index c3f3f3a66eaf7..66a08b6c3a807 100644 --- a/test/e2e/app-dir/app-static.test.ts +++ b/test/e2e/app-dir/app-static.test.ts @@ -76,6 +76,10 @@ describe('app-dir static/dynamic handling', () => { 'ssr-auto/cache-no-store/page.js', 'ssr-auto/fetch-revalidate-zero/page.js', 'ssr-forced/page.js', + 'variable-revalidate/no-store/page.js', + 'variable-revalidate/revalidate-3.html', + 'variable-revalidate/revalidate-3.rsc', + 'variable-revalidate/revalidate-3/page.js', ]) }) @@ -167,6 +171,11 @@ describe('app-dir static/dynamic handling', () => { initialRevalidateSeconds: false, srcRoute: '/ssg-preview/[[...route]]', }, + '/variable-revalidate/revalidate-3': { + dataRoute: '/variable-revalidate/revalidate-3.rsc', + initialRevalidateSeconds: 3, + srcRoute: '/variable-revalidate/revalidate-3', + }, }) expect(manifest.dynamicRoutes).toEqual({ '/blog/[author]/[slug]': { diff --git a/test/e2e/app-dir/app-static/app/ssr-auto/fetch-revalidate-zero/page.js b/test/e2e/app-dir/app-static/app/ssr-auto/fetch-revalidate-zero/page.js index 34706f5284a4a..823d8131ba653 100644 --- a/test/e2e/app-dir/app-static/app/ssr-auto/fetch-revalidate-zero/page.js +++ b/test/e2e/app-dir/app-static/app/ssr-auto/fetch-revalidate-zero/page.js @@ -19,6 +19,3 @@ export default function Page() { ) } - -// TODO-APP: remove revalidate config once next.revalidate is supported -export const revalidate = 0 diff --git a/test/e2e/app-dir/app-static/app/variable-revalidate/layout.js b/test/e2e/app-dir/app-static/app/variable-revalidate/layout.js new file mode 100644 index 0000000000000..3f9ed053c5ad2 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/variable-revalidate/layout.js @@ -0,0 +1,18 @@ +import { cache, use } from 'react' + +export default function Layout({ children }) { + const getData = cache(() => + fetch('https://next-data-api-endpoint.vercel.app/api/random?layout', { + next: { revalidate: 10 }, + }).then((res) => res.text()) + ) + const dataPromise = getData() + const data = use(dataPromise) + + return ( + <> +

revalidate 10: {data}

+ {children} + + ) +} diff --git a/test/e2e/app-dir/app-static/app/variable-revalidate/no-store/page.js b/test/e2e/app-dir/app-static/app/variable-revalidate/no-store/page.js new file mode 100644 index 0000000000000..b0c99184ef876 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/variable-revalidate/no-store/page.js @@ -0,0 +1,19 @@ +import { cache, use } from 'react' + +export default function Page() { + const getData = cache(() => + fetch('https://next-data-api-endpoint.vercel.app/api/random?page', { + cache: 'no-store', + }).then((res) => res.text()) + ) + const dataPromise = getData() + const data = use(dataPromise) + + return ( + <> +

/variable-revalidate/no-cache

+

no-store: {data}

+

{Date.now()}

+ + ) +} diff --git a/test/e2e/app-dir/app-static/app/variable-revalidate/revalidate-3/page.js b/test/e2e/app-dir/app-static/app/variable-revalidate/revalidate-3/page.js new file mode 100644 index 0000000000000..da03c9760ef2d --- /dev/null +++ b/test/e2e/app-dir/app-static/app/variable-revalidate/revalidate-3/page.js @@ -0,0 +1,19 @@ +import { cache, use } from 'react' + +export default function Page() { + const getData = cache(() => + fetch('https://next-data-api-endpoint.vercel.app/api/random?page', { + next: { revalidate: 3 }, + }).then((res) => res.text()) + ) + const dataPromise = getData() + const data = use(dataPromise) + + return ( + <> +

/variable-revalidate/revalidate-3

+

revalidate 3: {data}

+

{Date.now()}

+ + ) +} diff --git a/test/e2e/app-dir/app-static/next.config.js b/test/e2e/app-dir/app-static/next.config.js index 771ea0e5638f9..e03d15af21fbd 100644 --- a/test/e2e/app-dir/app-static/next.config.js +++ b/test/e2e/app-dir/app-static/next.config.js @@ -1,6 +1,7 @@ module.exports = { experimental: { appDir: true, + fetchCache: true, }, // assetPrefix: '/assets', rewrites: async () => { diff --git a/test/e2e/app-dir/dynamic-href.test.ts b/test/e2e/app-dir/dynamic-href.test.ts index 3337e29d7098f..6615538c55e26 100644 --- a/test/e2e/app-dir/dynamic-href.test.ts +++ b/test/e2e/app-dir/dynamic-href.test.ts @@ -1,9 +1,6 @@ import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' -import { - getRedboxDescription, - waitForAndOpenRuntimeError, -} from 'next-test-utils' +import { getRedboxDescription, hasRedbox } from 'next-test-utils' import path from 'path' import webdriver from 'next-webdriver' @@ -32,7 +29,7 @@ describe('dynamic-href', () => { const browser = await webdriver(next.url, '/object') // Error should show up - await waitForAndOpenRuntimeError(browser) + expect(await hasRedbox(browser, true)).toBeTrue() expect(await getRedboxDescription(browser)).toMatchInlineSnapshot( `"Error: Dynamic href \`/object/[slug]\` found in while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"` ) @@ -60,7 +57,7 @@ describe('dynamic-href', () => { const browser = await webdriver(next.url, '/string') // Error should show up - await waitForAndOpenRuntimeError(browser) + expect(await hasRedbox(browser, true)).toBeTrue() expect(await getRedboxDescription(browser)).toMatchInlineSnapshot( `"Error: Dynamic href \`/object/[slug]\` found in while using the \`/app\` router, this is not supported. Read more: https://nextjs.org/docs/messages/app-dir-dynamic-href"` ) diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index a4e0278080c98..3bf7b92d542f8 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -5,9 +5,9 @@ import { check, fetchViaHTTP, getRedboxHeader, + hasRedbox, renderViaHTTP, waitFor, - waitForAndOpenRuntimeError, } from 'next-test-utils' import path from 'path' import cheerio from 'cheerio' @@ -2055,7 +2055,7 @@ describe('app dir', () => { await browser.elementByCss('#error-trigger-button').click() if (isDev) { - await waitForAndOpenRuntimeError(browser) + expect(await hasRedbox(browser)).toBe(true) expect(await getRedboxHeader(browser)).toMatch(/this is a test/) } else { await browser @@ -2107,7 +2107,7 @@ describe('app dir', () => { await browser.elementByCss('#error-trigger-button').click() if (isDev) { - await waitForAndOpenRuntimeError(browser) + expect(await hasRedbox(browser)).toBe(true) expect(await getRedboxHeader(browser)).toMatch(/this is a test/) } else { expect( diff --git a/test/e2e/app-dir/root-layout.test.ts b/test/e2e/app-dir/root-layout.test.ts index 976ab8318c414..e75e15e1721cc 100644 --- a/test/e2e/app-dir/root-layout.test.ts +++ b/test/e2e/app-dir/root-layout.test.ts @@ -40,9 +40,9 @@ describe('app-dir root layout', () => { expect(await hasRedbox(browser, true)).toBe(true) expect(await getRedboxSource(browser)).toMatchInlineSnapshot(` - "Please make sure to include the following tags in your root layout: , . + "Please make sure to include the following tags in your root layout: , , . - Missing required root layout tags: html, body" + Missing required root layout tags: html, head, body" `) }) @@ -54,9 +54,9 @@ describe('app-dir root layout', () => { expect(await hasRedbox(browser, true)).toBe(true) expect(await getRedboxSource(browser)).toMatchInlineSnapshot(` - "Please make sure to include the following tags in your root layout: , . + "Please make sure to include the following tags in your root layout: , , . - Missing required root layout tags: html, body" + Missing required root layout tags: html, head, body" `) }) @@ -67,9 +67,9 @@ describe('app-dir root layout', () => { expect(await hasRedbox(browser, true)).toBe(true) expect(await getRedboxSource(browser)).toMatchInlineSnapshot(` - "Please make sure to include the following tags in your root layout: , . + "Please make sure to include the following tags in your root layout: , , . - Missing required root layout tags: html, body" + Missing required root layout tags: html, head, body" `) }) }) diff --git a/test/e2e/app-dir/root-layout/app/(required-tags)/has-tags/layout.js b/test/e2e/app-dir/root-layout/app/(required-tags)/has-tags/layout.js index 7519206f5c7b3..f8fd9c6d9b3ea 100644 --- a/test/e2e/app-dir/root-layout/app/(required-tags)/has-tags/layout.js +++ b/test/e2e/app-dir/root-layout/app/(required-tags)/has-tags/layout.js @@ -4,6 +4,9 @@ export const revalidate = 0 export default function Root({ children }) { return ( + + Hello World + {children} ) diff --git a/test/e2e/next-font/app/pages/with-fallback.js b/test/e2e/next-font/app/pages/with-fallback.js index e944fb3807dc7..8a5e258266c52 100644 --- a/test/e2e/next-font/app/pages/with-fallback.js +++ b/test/e2e/next-font/app/pages/with-fallback.js @@ -2,6 +2,7 @@ import { Open_Sans } from '@next/font/google' const openSans = Open_Sans({ fallback: ['system-ui', 'Arial'], variable: '--open-sans', + adjustFontFallback: false, }) export default function WithFonts() { diff --git a/test/e2e/next-font/index.test.ts b/test/e2e/next-font/index.test.ts index 2298bcb133449..746a0d938d61d 100644 --- a/test/e2e/next-font/index.test.ts +++ b/test/e2e/next-font/index.test.ts @@ -108,7 +108,7 @@ describe('@next/font/google', () => { className: expect.stringMatching(/__className_.{6}/), style: { fontFamily: expect.stringMatching( - /^'__myFont1_.{6}', system-ui, '__myFont1_Fallback_.{6}'$/ + /^'__myFont1_.{6}', '__myFont1_Fallback_.{6}', system-ui$/ ), fontStyle: 'italic', fontWeight: 100, @@ -275,27 +275,21 @@ describe('@next/font/google', () => { await browser.eval( 'getComputedStyle(document.querySelector("#with-fallback-fonts-classname")).fontFamily' ) - ).toMatch( - /^__Open_Sans_.{6}, system-ui, Arial, __Open_Sans_Fallback_.{6}$/ - ) + ).toMatch(/^__Open_Sans_.{6}, system-ui, Arial$/) // .style expect( await browser.eval( 'getComputedStyle(document.querySelector("#with-fallback-fonts-style")).fontFamily' ) - ).toMatch( - /^__Open_Sans_.{6}, system-ui, Arial, __Open_Sans_Fallback_.{6}$/ - ) + ).toMatch(/^__Open_Sans_.{6}, system-ui, Arial$/) // .variable expect( await browser.eval( 'getComputedStyle(document.querySelector("#with-fallback-fonts-variable")).fontFamily' ) - ).toMatch( - /^__Open_Sans_.{6}, system-ui, Arial, __Open_Sans_Fallback_.{6}$/ - ) + ).toMatch(/^__Open_Sans_.{6}, system-ui, Arial$/) }) }) diff --git a/test/e2e/trailingslash-with-rewrite/app/next.config.js b/test/e2e/trailingslash-with-rewrite/app/next.config.js new file mode 100644 index 0000000000000..9f2a0ec8f0ac3 --- /dev/null +++ b/test/e2e/trailingslash-with-rewrite/app/next.config.js @@ -0,0 +1,6 @@ +module.exports = { + trailingSlash: true, + async rewrites() { + return [{ source: '/country/', destination: '/' }] + }, +} diff --git a/test/e2e/trailingslash-with-rewrite/app/pages/index.js b/test/e2e/trailingslash-with-rewrite/app/pages/index.js new file mode 100644 index 0000000000000..1236c5fdca1ae --- /dev/null +++ b/test/e2e/trailingslash-with-rewrite/app/pages/index.js @@ -0,0 +1,7 @@ +export default function Home() { + return

Welcome home

+} + +export async function getStaticProps() { + return { props: {} } +} diff --git a/test/e2e/trailingslash-with-rewrite/index.test.ts b/test/e2e/trailingslash-with-rewrite/index.test.ts new file mode 100644 index 0000000000000..6929351f72fb3 --- /dev/null +++ b/test/e2e/trailingslash-with-rewrite/index.test.ts @@ -0,0 +1,25 @@ +import { join } from 'path' +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { fetchViaHTTP } from 'next-test-utils' + +describe('trailingSlash:true with rewrites and getStaticProps', () => { + let next: NextInstance + + if ((global as any).isNextDeploy) { + it('should skip for deploy mode for now', () => {}) + return + } + + beforeAll(async () => { + next = await createNext({ + files: new FileRef(join(__dirname, './app')), + }) + }) + afterAll(() => next.destroy()) + + it('should work', async () => { + const res = await fetchViaHTTP(next.url, '/country') + expect(await res.text()).toContain('Welcome home') + }) +}) diff --git a/test/integration/create-next-app/index.test.ts b/test/integration/create-next-app/index.test.ts index 9fa555dc0f9c6..464b8e7a58e31 100644 --- a/test/integration/create-next-app/index.test.ts +++ b/test/integration/create-next-app/index.test.ts @@ -301,22 +301,29 @@ describe('create next app', () => { it('should exit if the folder is not writable', async () => { await useTempDir(async (cwd) => { const projectName = 'not-writable' + + // if the folder isn't able to be write restricted we can't test + // this so skip + if ( + await fs + .writeFile(path.join(cwd, 'test'), 'hello') + .then(() => true) + .catch(() => false) + ) { + console.warn( + `Test folder is not write restricted skipping write permission test` + ) + return + } const res = await run([projectName, '--js', '--eslint'], { cwd, reject: false, }) - if (process.platform === 'win32') { - expect(res.exitCode).toBe(0) - const files = ['package.json'] - projectFilesShouldExist({ cwd, projectName, files }) - return - } - - expect(res.exitCode).toBe(1) expect(res.stderr).toMatch( /you do not have write permissions for this folder/ ) + expect(res.exitCode).toBe(1) }, 0o500) }) diff --git a/test/integration/create-next-app/lib/specification.ts b/test/integration/create-next-app/lib/specification.ts index dbdfd610aab09..ea529cabe9aec 100644 --- a/test/integration/create-next-app/lib/specification.ts +++ b/test/integration/create-next-app/lib/specification.ts @@ -28,7 +28,14 @@ export const projectSpecification: ProjectSpecification = { 'node_modules/next', '.gitignore', ], - deps: ['next', 'react', 'react-dom', 'eslint', 'eslint-config-next'], + deps: [ + 'next', + '@next/font', + 'react', + 'react-dom', + 'eslint', + 'eslint-config-next', + ], devDeps: [], }, default: { diff --git a/test/integration/getserversideprops-preview/test/index.test.js b/test/integration/getserversideprops-preview/test/index.test.js index 7c7f68bbdf063..ff42f77db3a4c 100644 --- a/test/integration/getserversideprops-preview/test/index.test.js +++ b/test/integration/getserversideprops-preview/test/index.test.js @@ -197,6 +197,7 @@ describe('ServerSide Props Preview Mode', () => { it('should return cookies to be expired after dev server reboot', async () => { await killApp(app) + appPort = await findPort() app = await launchApp(appDir, appPort) const res = await fetchViaHTTP( diff --git a/test/integration/initial-ref/pages/index.js b/test/integration/initial-ref/pages/index.js index a0b6dab37292a..69b41de2d2b17 100644 --- a/test/integration/initial-ref/pages/index.js +++ b/test/integration/initial-ref/pages/index.js @@ -20,7 +20,7 @@ class App extends React.Component { const { refHeight } = this.state return ( -
+

DOM Ref test using 9.2.0

{`this component is ${refHeight}px tall`}
diff --git a/test/integration/prerender-preview/test/index.test.js b/test/integration/prerender-preview/test/index.test.js index 32cc96ae23f6f..7ae614c826f63 100644 --- a/test/integration/prerender-preview/test/index.test.js +++ b/test/integration/prerender-preview/test/index.test.js @@ -290,6 +290,7 @@ describe('Prerender Preview Mode', () => { it('should return cookies to be expired after dev server reboot', async () => { await killApp(app) + appPort = await findPort() app = await launchApp(appDir, appPort) const res = await fetchViaHTTP( diff --git a/test/integration/typescript/test/index.test.js b/test/integration/typescript/test/index.test.js index 3268404f6c7d5..5f59da2fc2ba7 100644 --- a/test/integration/typescript/test/index.test.js +++ b/test/integration/typescript/test/index.test.js @@ -92,6 +92,7 @@ export default function EvilPage(): JSX.Element { } ` ) + appPort = await findPort() app = await launchApp(appDir, appPort) const $ = await get$('/hello') diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index c98686689a2c2..ec4592e1287c6 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -643,28 +643,6 @@ export async function hasRedbox(browser, expected = true) { return false } -export async function hasErrorToast(browser, expected = true) { - for (let i = 0; i < 30; i++) { - const result = await evaluate(browser, () => { - return Boolean( - [].slice - .call(document.querySelectorAll('nextjs-portal')) - .find((p) => p.shadowRoot.querySelector('[data-nextjs-toast]')) - ) - }) - - if (result === expected) { - return result - } - await waitFor(1000) - } - return false -} - -export async function waitForAndOpenRuntimeError(browser) { - return browser.waitForElementByCss('[data-nextjs-toast]').click() -} - export async function getRedboxHeader(browser) { return retry( () => diff --git a/test/lib/use-temp-dir.ts b/test/lib/use-temp-dir.ts index f53fcc6aebfc2..32db47b6c42a1 100644 --- a/test/lib/use-temp-dir.ts +++ b/test/lib/use-temp-dir.ts @@ -17,7 +17,7 @@ export async function useTempDir( await fs.mkdirp(folder) if (mode) { - fs.chmod(folder, mode) + await fs.chmod(folder, mode) } try {