diff --git a/.alexignore b/.alexignore new file mode 100644 index 0000000000000..1bf6581c26b1e --- /dev/null +++ b/.alexignore @@ -0,0 +1,2 @@ +CODE_OF_CONDUCT.md +examples/ diff --git a/.alexrc b/.alexrc new file mode 100644 index 0000000000000..157d1da8cca53 --- /dev/null +++ b/.alexrc @@ -0,0 +1,21 @@ +{ + "allow": [ + "attacks", + "color", + "dead", + "execute", + "executed", + "executes", + "execution", + "executions", + "failed", + "failure", + "failures", + "fire", + "fires", + "hook", + "hooks", + "host-hostess", + "invalid" + ] +} diff --git a/.eslintignore b/.eslintignore index 37e0a229afb6d..5908d01da8937 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,8 +2,15 @@ node_modules **/.next/** **/_next/** **/dist/** +e2e-tests/** +examples/with-eslint/** examples/with-typescript-eslint-jest/** examples/with-kea/** +examples/with-custom-babel-config/** +examples/with-flow/** +examples/with-jest/** +examples/with-mobx-state-tree/** +examples/with-mobx/** packages/next/bundles/webpack/packages/*.runtime.js packages/next/compiled/**/* packages/react-refresh-utils/**/*.js @@ -15,5 +22,9 @@ packages/next-codemod/transforms/__tests__/**/* packages/next-codemod/**/*.js packages/next-codemod/**/*.d.ts packages/next-env/**/*.d.ts -test/integration/async-modules/** -test-timings.json \ No newline at end of file +packages/create-next-app/templates/** +test/integration/eslint/** +test-timings.json +packages/next/build/swc/tests/fixture/** +bench/nested-deps/pages/** +bench/nested-deps/components/** \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 2a93dc6785917..1cee5507a5901 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,6 @@ { "root": true, - "parser": "babel-eslint", + "parser": "@babel/eslint-parser", "plugins": ["react", "react-hooks", "jest", "import"], "env": { "browser": true, @@ -9,10 +9,17 @@ "node": true }, "parserOptions": { - "ecmaVersion": 2018, + "requireConfigFile": false, "sourceType": "module", "ecmaFeatures": { "jsx": true + }, + "babelOptions": { + "presets": ["@babel/preset-env", "@babel/preset-react"], + "caller": { + // Eslint supports top level await when a parser for it is included. We enable the parser by default for Babel. + "supportsTopLevelAwait": true + } } }, "settings": { @@ -27,7 +34,10 @@ "extends": ["plugin:jest/recommended"], "rules": { "jest/expect-expect": "off", - "jest/no-disabled-tests": "off" + "jest/no-disabled-tests": "off", + "jest/no-conditional-expect": "off", + "jest/valid-title": "off", + "jest/no-interpolation-in-snapshots": "off" } }, { "files": ["**/__tests__/**"], "env": { "jest": true } }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d7462e3488f93..5b64af494dbb6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,6 @@ # Learn how to add code owners here: # https://help.github.com/en/articles/about-code-owners -* @timneutkens @Timer @ijjk @lfades @divmain -/docs/ @timneutkens @Timer @ijjk @lfades @divmain @leerob -/examples/ @timneutkens @Timer @ijjk @lfades @divmain @leerob +* @timneutkens @ijjk @shuding @styfle @huozhi @padmaia +/docs/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia @leerob @lfades +/examples/ @timneutkens @ijjk @shuding @leerob @lfades diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index 6da3f732c59d8..440f9434bf332 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -1,8 +1,6 @@ name: Bug Report -about: Create a bug report for the Next.js core -title: '' +description: Create a bug report for the Next.js core labels: 'template: bug' -issue_body: true body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml index be3ea542b5196..8535a441bf4a7 100644 --- a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml @@ -1,8 +1,6 @@ name: Example Bug Report -about: Create a bug report for the examples -title: '' +description: Create a bug report for the examples labels: 'type: example,template: bug' -issue_body: true body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/3.feature_request.yml b/.github/ISSUE_TEMPLATE/3.feature_request.yml index 8f2ee2da1fc8d..2655aff44d149 100644 --- a/.github/ISSUE_TEMPLATE/3.feature_request.yml +++ b/.github/ISSUE_TEMPLATE/3.feature_request.yml @@ -1,8 +1,6 @@ name: Feature Request -about: Create a feature request for the Next.js core -title: '' +description: Create a feature request for the Next.js core labels: 'template: story' -issue_body: true body: - type: markdown attributes: diff --git a/.github/actions/next-stats-action/Dockerfile b/.github/actions/next-stats-action/Dockerfile index bf86727921a9b..f533f918d9d27 100644 --- a/.github/actions/next-stats-action/Dockerfile +++ b/.github/actions/next-stats-action/Dockerfile @@ -1,8 +1,8 @@ -FROM node:10-buster +FROM node:14-buster LABEL com.github.actions.name="Next.js PR Stats" LABEL com.github.actions.description="Compares stats of a PR with the main branch" -LABEL repository="https://github.com/zeit/next-stats-action" +LABEL repository="https://github.com/vercel/next-stats-action" COPY . /next-stats diff --git a/.github/actions/next-stats-action/src/add-comment.js b/.github/actions/next-stats-action/src/add-comment.js index e483ffcfe64ba..2f2d9afc15785 100644 --- a/.github/actions/next-stats-action/src/add-comment.js +++ b/.github/actions/next-stats-action/src/add-comment.js @@ -83,7 +83,7 @@ module.exports = async function addComment( else if (!isGzipItem && !groupKey.match(gzipIgnoreRegex)) return if ( - itemKey !== 'buildDuration' || + !itemKey.startsWith('buildDuration') || (isBenchmark && itemKey.match(/req\/sec/)) ) { if (typeof mainItemVal === 'number') mainRepoTotal += mainItemVal diff --git a/.github/actions/next-stats-action/src/index.js b/.github/actions/next-stats-action/src/index.js index 0b39ed8a2e67b..64b1b1c18e05d 100644 --- a/.github/actions/next-stats-action/src/index.js +++ b/.github/actions/next-stats-action/src/index.js @@ -102,13 +102,17 @@ if (!allowedActions.has(actionInfo.actionName) && !actionInfo.isRelease) { logger(`Running initial build for ${dir}`) if (!actionInfo.skipClone) { let buildCommand = `cd ${dir}${ - !statsConfig.skipInitialInstall ? ' && yarn install' : '' + !statsConfig.skipInitialInstall + ? ' && yarn install --network-timeout 1000000' + : '' }` if (statsConfig.initialBuildCommand) { buildCommand += ` && ${statsConfig.initialBuildCommand}` } - await exec(buildCommand) + // allow 5 minutes node_modules install + building all packages + // in case of noisy environment slowing down initial repo build + await exec(buildCommand, false, { timeout: 5 * 60 * 1000 }) } logger(`Linking packages in ${dir}`) diff --git a/.github/actions/next-stats-action/src/prepare/repo-setup.js b/.github/actions/next-stats-action/src/prepare/repo-setup.js index 534ab34229e53..734204855b9fd 100644 --- a/.github/actions/next-stats-action/src/prepare/repo-setup.js +++ b/.github/actions/next-stats-action/src/prepare/repo-setup.js @@ -93,6 +93,10 @@ module.exports = (actionInfo) => { if (!pkgData.dependencies || !pkgData.dependencies[pkg]) continue pkgData.dependencies[pkg] = packedPkgPath } + // make sure native binaries are included in local linking + if (pkg === 'next') { + pkgData.files.push('native') + } await fs.writeFile( pkgDataPath, JSON.stringify(pkgData, null, 2), @@ -104,7 +108,7 @@ module.exports = (actionInfo) => { // to the correct versions for (const pkgName of pkgDatas.keys()) { const { pkg, pkgPath } = pkgDatas.get(pkgName) - await exec(`cd ${pkgPath} && yarn pack -f ${pkg}-packed.tgz`) + await exec(`cd ${pkgPath} && yarn pack -f ${pkg}-packed.tgz`, true) } return pkgPaths }, diff --git a/.github/actions/next-stats-action/src/run/index.js b/.github/actions/next-stats-action/src/run/index.js index e8ddf32dea70f..e2159e9a89bf2 100644 --- a/.github/actions/next-stats-action/src/run/index.js +++ b/.github/actions/next-stats-action/src/run/index.js @@ -26,6 +26,7 @@ async function runConfigs( let curStats = { General: { buildDuration: null, + buildDurationCached: null, nodeModulesSize: null, }, } @@ -55,20 +56,25 @@ async function runConfigs( ) } - const buildStart = new Date().getTime() + const buildStart = Date.now() await exec(`cd ${statsAppDir} && ${statsConfig.appBuildCommand}`, false, { env: yarnEnvValues, }) - curStats.General.buildDuration = new Date().getTime() - buildStart + curStats.General.buildDuration = Date.now() - buildStart // apply renames to get deterministic output names for (const rename of config.renames) { const results = await glob(rename.srcGlob, { cwd: statsAppDir }) - if (results.length === 0 || results[0] === rename.dest) continue - await fs.move( - path.join(statsAppDir, results[0]), - path.join(statsAppDir, rename.dest) - ) + for (const result of results) { + let dest = rename.removeHash + ? result.replace(/(\.|-)[0-9a-f]{20}(\.|-)/g, '$1HASH$2') + : rename.dest + if (result === dest) continue + await fs.move( + path.join(statsAppDir, result), + path.join(statsAppDir, dest) + ) + } } const collectedStats = await collectStats(config, statsConfig) @@ -80,19 +86,17 @@ async function runConfigs( const applyRenames = (renames, stats) => { if (renames) { for (const rename of renames) { + let { cur, prev } = rename + cur = path.basename(cur) + prev = path.basename(prev) + Object.keys(stats).forEach((group) => { - Object.keys(stats[group]).forEach((item) => { - let { cur, prev } = rename - cur = path.basename(cur) - prev = path.basename(prev) - - if (cur === item) { - stats[group][prev] = stats[group][item] - stats[group][prev + ' gzip'] = stats[group][item + ' gzip'] - delete stats[group][item] - delete stats[group][item + ' gzip'] - } - }) + if (stats[group][cur]) { + stats[group][prev] = stats[group][cur] + stats[group][prev + ' gzip'] = stats[group][cur + ' gzip'] + delete stats[group][cur] + delete stats[group][cur + ' gzip'] + } }) } } @@ -146,6 +150,12 @@ async function runConfigs( /* eslint-disable-next-line */ mainRepoStats = curStats } + + const secondBuildStart = Date.now() + await exec(`cd ${statsAppDir} && ${statsConfig.appBuildCommand}`, false, { + env: yarnEnvValues, + }) + curStats.General.buildDurationCached = Date.now() - secondBuildStart } logger(`Finished running: ${config.title}`) diff --git a/.github/labeler.json b/.github/labeler.json index da8660336f90b..4da78e9152291 100644 --- a/.github/labeler.json +++ b/.github/labeler.json @@ -7,7 +7,29 @@ "packages/next/**", "packages/react-dev-overlay/**", "packages/react-refresh-utils/**", - "packages/next-codemod/**" + "packages/next-codemod/**", + "packages/eslint-plugin-next/**", + "packages/eslint-config-next/**", + "packages/next-env/**" + ], + "created-by: Chrome Aurora": [ + { "type": "user", "pattern": "spanicker" }, + { "type": "user", "pattern": "housseindjirdeh" }, + { "type": "user", "pattern": "devknoll" }, + { "type": "user", "pattern": "janicklas-ralph" }, + { "type": "user", "pattern": "atcastle" }, + { "type": "user", "pattern": "kyliau" }, + { "type": "user", "pattern": "kara" } + ], + "created-by: Next.js team": [ + { "type": "user", "pattern": "ijjk" }, + { "type": "user", "pattern": "padmaia" }, + { "type": "user", "pattern": "huozhi" }, + { "type": "user", "pattern": "shuding" }, + { "type": "user", "pattern": "sokra" }, + { "type": "user", "pattern": "styfle" }, + { "type": "user", "pattern": "leerob" }, + { "type": "user", "pattern": "timneutkens" } ] } } diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000000..9e9b995c92dd8 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,24 @@ + + +## Bug + +- [ ] Related issues linked using `fixes #number` +- [ ] Integration tests added +- [ ] Errors have helpful link attached, see `contributing.md` + +## Feature + +- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. +- [ ] Related issues linked using `fixes #number` +- [ ] Integration tests added +- [ ] Documentation added +- [ ] Telemetry added. In case of a feature if it's used or not. +- [ ] Errors have helpful link attached, see `contributing.md` + +## Documentation / Examples + +- [ ] Make sure the linting passes diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml new file mode 100644 index 0000000000000..4f660a10ff74b --- /dev/null +++ b/.github/workflows/build_native.yml @@ -0,0 +1,94 @@ +on: workflow_dispatch + +name: Build next-swc native binaries + +jobs: + build-native: + strategy: + matrix: + os: [ubuntu-18.04, macos-latest, windows-latest] + description: [default] + include: + - os: ubuntu-18.04 + target: x86_64-unknown-linux-gnu + name: linux-x64-gnu + - os: windows-latest + target: x86_64-pc-windows-msvc + name: win32-x64-msvc + - os: macos-latest + target: x86_64-apple-darwin + name: darwin-x64 + - os: macos-latest + target: aarch64-apple-darwin + name: darwin-arm64 + description: m1 + + name: next-swc - ${{ matrix.os }} - ${{ matrix.target }} - node@14 + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + check-latest: true + - name: Install + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-08-12 + target: ${{ matrix.target }} + - name: Cache cargo registry + uses: actions/cache@v1 + with: + path: ~/.cargo/registry + key: stable-${{ matrix.os }}-node@14-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@v1 + with: + path: ~/.cargo/git + key: stable-${{ matrix.os }}-node@14-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next/native/** + key: next-swc-nightly-2021-08-12-${{ matrix.target }}-${{ hashFiles('.github/workflows/build_native.yml', 'packages/next/build/swc/**') }} + - name: Cross build aarch64 setup + if: ${{ matrix.target == 'aarch64-apple-darwin' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: | + sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*; + export CC=$(xcrun -f clang); + export CXX=$(xcrun -f clang++); + SYSROOT=$(xcrun --sdk macosx --show-sdk-path); + export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT"; + - name: 'Build' + if: steps.binary-cache.outputs.cache-hit != true + run: yarn build-native --target ${{ matrix.target }} + env: + MACOSX_DEPLOYMENT_TARGET: '10.13' + working-directory: packages/next + - name: Upload artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: next-swc-binaries + path: packages/next/native/next-swc.${{ matrix.name }}.node + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache + commit: + needs: build-native + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + - uses: actions/download-artifact@v2.0.10 + with: + name: next-swc-binaries + path: packages/next/native + - uses: EndBug/add-and-commit@v7 + with: + add: 'packages/next/native --force' + message: 'Build next-swc binaries' diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 176952e1e6a65..df6017cbad782 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -7,17 +7,32 @@ on: name: Build, test, and deploy jobs: + check-examples: + name: Check examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install moreutils + run: sudo apt install moreutils + - name: Check examples + run: ./scripts/check-examples.sh + build: runs-on: ubuntu-latest env: NEXT_TELEMETRY_DISABLED: 1 outputs: docsChange: ${{ steps.docs-change.outputs.DOCS_CHANGE }} + isRelease: ${{ steps.check-release.outputs.IS_RELEASE }} steps: - uses: actions/checkout@v2 with: fetch-depth: 25 + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - run: yarn install --frozen-lockfile --check-files - run: node run-tests.js --timings --write-timings -g 1/1 @@ -25,6 +40,16 @@ jobs: run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') id: docs-change - run: echo ${{steps.docs-change.outputs.DOCS_CHANGE}} + - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - run: git describe + - id: check-release + run: | + if [[ $(git describe --exact-match 2> /dev/null || :) = v* ]]; + then + echo "::set-output name=IS_RELEASE::true" + else + echo "::set-output name=IS_RELEASE::false" + fi - uses: actions/cache@v2 id: cache-build with: @@ -40,22 +65,37 @@ jobs: with: path: ./* key: ${{ github.sha }} + - run: ./scripts/check-manifests.js - run: yarn lint checkPrecompiled: name: Check Pre-compiled runs-on: ubuntu-latest - needs: build + needs: [build, build-native] env: NEXT_TELEMETRY_DISABLED: 1 steps: + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + - uses: actions/cache@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* key: ${{ github.sha }} - - run: ./check-pre-compiled.sh + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-binaries + path: packages/next/build/swc/dist + # Only check linux build for now, mac builds can sometimes be different even with the same code + - run: | + mv ./packages/next/build/swc/dist/next-swc.linux-x64-gnu.node \ + ./packages/next/native/next-swc.linux-x64-gnu.node + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - run: ./scripts/check-pre-compiled.sh if: ${{needs.build.outputs.docsChange != 'docs only change'}} testUnit: @@ -65,7 +105,6 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 - HEADLESS: true steps: - uses: actions/cache@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -74,7 +113,81 @@ jobs: path: ./* key: ${{ github.sha }} - - run: node run-tests.js --timings --type unit -g 1/1 + - run: node run-tests.js --type unit + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + testDev: + name: Test Development + runs-on: ubuntu-latest + needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + steps: + - run: echo ${{needs.build.outputs.docsChange}} + + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + # TODO: remove after we fix watchpack watching too much + - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + # docker run is used here to fix jest snapshots failing inside of the + # bare GitHub actions environment for the acceptance tests + - run: docker run --ipc=host -e NEXT_TEST_JOB=1 -e NEXT_TELEMETRY_DISABLED=1 -v $(pwd):/next.js mcr.microsoft.com/playwright:focal /bin/bash -c 'cd /next.js && node run-tests.js --type development' + name: Run test/development + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - run: NEXT_TEST_MODE=dev node run-tests.js --type e2e + name: Run test/e2e (dev) + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + testProd: + name: Test Production + runs-on: ubuntu-latest + needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + steps: + - run: echo ${{needs.build.outputs.docsChange}} + + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + # TODO: remove after we fix watchpack watching too much + - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - run: node run-tests.js --type production + name: Run test/production + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - run: NEXT_TEST_MODE=start node run-tests.js --type e2e + name: Run test/e2e (production) if: ${{needs.build.outputs.docsChange != 'docs only change'}} testIntegration: @@ -84,13 +197,17 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 - HEADLESS: true strategy: fail-fast: false matrix: group: [1, 2, 3, 4, 5, 6] steps: - run: echo ${{needs.build.outputs.docsChange}} + + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + - uses: actions/cache@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build @@ -98,11 +215,14 @@ jobs: path: ./* key: ${{ github.sha }} + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + # TODO: remove after we fix watchpack watching too much - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p if: ${{needs.build.outputs.docsChange != 'docs only change'}} - - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3 + - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/6 if: ${{needs.build.outputs.docsChange != 'docs only change'}} testElectron: @@ -112,7 +232,6 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 - HEADLESS: true TEST_ELECTRON: 1 steps: - uses: actions/cache@v2 @@ -126,7 +245,7 @@ jobs: - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p if: ${{needs.build.outputs.docsChange != 'docs only change'}} - - run: yarn add -W --dev spectron@7.0.0 electron@5.0.0 + - run: cd test/integration/with-electron/app && yarn if: ${{needs.build.outputs.docsChange != 'docs only change'}} - run: xvfb-run node run-tests.js test/integration/with-electron/test/index.test.js @@ -134,97 +253,69 @@ jobs: testYarnPnP: runs-on: ubuntu-latest + needs: build env: NODE_OPTIONS: '--unhandled-rejections=strict' YARN_COMPRESSION_LEVEL: '0' steps: - - uses: actions/checkout@v2 + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build with: - fetch-depth: 25 - - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') - id: docs-change - - - run: yarn install --frozen-lockfile --check-files - if: ${{steps.docs-change.outputs.DOCS_CHANGE != 'docs only change'}} + path: ./* + key: ${{ github.sha }} - - run: bash ./test-pnp.sh - if: ${{steps.docs-change.outputs.DOCS_CHANGE != 'docs only change'}} + - run: bash ./scripts/test-pnp.sh + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testsPass: name: thank you, next runs-on: ubuntu-latest - needs: [lint, checkPrecompiled, testIntegration, testUnit, testYarnPnP] + needs: + [ + lint, + checkPrecompiled, + testIntegration, + testUnit, + testYarnPnP, + testDev, + testProd, + ] steps: - run: exit 0 - testFutureDependencies: - name: Webpack 5 (Basic, Production, Acceptance) + testLegacyWebpack: + name: Webpack 4 (Basic, Production, Acceptance) runs-on: ubuntu-latest + needs: build env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 - HEADLESS: true - NEXT_PRIVATE_TEST_WEBPACK5_MODE: 1 + NEXT_PRIVATE_TEST_WEBPACK4_MODE: 1 steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 25 - - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') - id: docs-change - - - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - - - run: yarn install --check-files - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - - - run: xvfb-run node run-tests.js test/integration/{link-ref,production,basic,async-modules,font-optimization,ssr-ctx}/test/index.test.js test/acceptance/*.test.js - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - - testLegacyReact: - name: React 16 + Webpack 4 (Basic, Production, Acceptance) - runs-on: ubuntu-latest - env: - NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - HEADLESS: true + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off - steps: - - uses: actions/checkout@v2 + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build with: - fetch-depth: 25 - - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') - id: docs-change - - - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - - - run: cat package.json | jq '.resolutions.react = "^16.14.0"' > package.json.tmp && mv package.json.tmp package.json - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - - - run: cat package.json | jq '.resolutions."react-dom" = "^16.14.0"' > package.json.tmp && mv package.json.tmp package.json - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - - - run: yarn install --check-files - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} - - - run: yarn list react react-dom - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + path: ./* + key: ${{ github.sha }} - - run: xvfb-run node run-tests.js test/integration/{link-ref,production,basic,async-modules,font-optimization,ssr-ctx,worker-loader}/test/index.test.js test/acceptance/*.test.js - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + # docker run is used here to fix jest snapshots failing inside of the + # bare GitHub actions environment for the acceptance tests + - run: docker run --ipc=host -e NEXT_PRIVATE_TEST_WEBPACK4_MODE=1 -e NEXT_TEST_JOB=1 -e NEXT_TELEMETRY_DISABLED=1 -v $(pwd):/next.js mcr.microsoft.com/playwright:focal /bin/bash -c 'cd /next.js && node run-tests.js --type development test/development/acceptance/{ReactRefresh,ReactRefreshLogBox-app-doc,ReactRefreshLogBox-scss,ReactRefreshLogBox,ReactRefreshLogBoxMisc,ReactRefreshRegression,ReactRefreshRequire}.test.ts test/development/basic/*.test.ts && node run-tests.js test/integration/{fallback-modules,link-ref,production,async-modules,font-optimization,ssr-ctx}/test/index.test.js' + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testFirefox: name: Test Firefox (production) runs-on: ubuntu-latest needs: build env: - HEADLESS: true - BROWSERNAME: 'firefox' + BROWSER_NAME: 'firefox' NEXT_TELEMETRY_DISABLED: 1 steps: - uses: actions/cache@v2 @@ -233,6 +324,8 @@ jobs: with: path: ./* key: ${{ github.sha }} + - run: npx playwright install-deps && npx playwright install firefox + if: ${{needs.build.outputs.docsChange != 'docs only change'}} - run: node run-tests.js test/integration/production/test/index.test.js if: ${{needs.build.outputs.docsChange != 'docs only change'}} @@ -242,19 +335,29 @@ jobs: needs: build env: BROWSERSTACK: true - BROWSERNAME: 'safari' + BROWSER_NAME: 'safari' NEXT_TELEMETRY_DISABLED: 1 SKIP_LOCAL_SELENIUM_SERVER: true BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} steps: + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + - uses: actions/cache@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* key: ${{ github.sha }} - - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js test/integration/production/test/index.test.js' + + # TODO: use macos runner so that we can use playwright to test against + # PRs instead of only running on canary? + - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || npm i -g browserstack-local@1.4.0' + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js -c 1 test/integration/production/test/index.test.js' if: ${{needs.build.outputs.docsChange != 'docs only change'}} testSafariOld: @@ -264,37 +367,55 @@ jobs: env: BROWSERSTACK: true LEGACY_SAFARI: true - BROWSERNAME: 'safari' + BROWSER_NAME: 'safari' NEXT_TELEMETRY_DISABLED: 1 SKIP_LOCAL_SELENIUM_SERVER: true BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} steps: + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + - uses: actions/cache@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* key: ${{ github.sha }} + + - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || npm i -g browserstack-local@1.4.0' + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js test/integration/production-nav/test/index.test.js' if: ${{needs.build.outputs.docsChange != 'docs only change'}} publishRelease: + if: ${{ needs.build.outputs.isRelease == 'true' }} name: Potentially publish release runs-on: ubuntu-latest - needs: build + needs: [build, build-native] env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }} steps: + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + - uses: actions/cache@v2 id: restore-build with: path: ./* key: ${{ github.sha }} + - uses: actions/download-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/build/swc/dist + - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + - run: ./scripts/publish-native.js $GITHUB_REF + - run: ./scripts/publish-release.sh - - run: ./publish-release.sh - - prStats: + releaseStats: name: Release Stats runs-on: ubuntu-latest needs: [publishRelease] @@ -304,7 +425,123 @@ jobs: with: path: ./* key: ${{ github.sha }} - - run: ./release-stats.sh + - run: ./scripts/release-stats.sh - uses: ./.github/actions/next-stats-action env: PR_STATS_COMMENT_TOKEN: ${{ secrets.PR_STATS_COMMENT_TOKEN }} + + build-native: + strategy: + matrix: + os: [ubuntu-18.04, macos-latest, windows-latest] + description: [default] + include: + - os: ubuntu-18.04 + target: x86_64-unknown-linux-gnu + name: linux-x64-gnu + - os: windows-latest + target: x86_64-pc-windows-msvc + name: win32-x64-msvc + - os: macos-latest + target: x86_64-apple-darwin + name: darwin-x64 + - os: macos-latest + target: aarch64-apple-darwin + name: darwin-arm64 + description: m1 + + name: next-swc - ${{ matrix.os }} - ${{ matrix.target }} - node@14 + runs-on: ${{ matrix.os }} + + steps: + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + if: ${{ matrix.os == 'ubuntu-18.04' }} + - name: tune windows network + run: Disable-NetAdapterChecksumOffload -Name * -TcpIPv4 -UdpIPv4 -TcpIPv6 -UdpIPv6 + if: ${{ matrix.os == 'windows-latest' }} + - name: tune mac network + run: sudo sysctl -w net.link.generic.system.hwcksum_tx=0 && sudo sysctl -w net.link.generic.system.hwcksum_rx=0 + if: ${{ matrix.os == 'macos-latest' }} + + - uses: actions/checkout@v2 + with: + fetch-depth: 25 + - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') + id: docs-change + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + node-version: 14 + check-latest: true + - name: Install + uses: actions-rs/toolchain@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + profile: minimal + toolchain: nightly-2021-08-12 + target: ${{ matrix.target }} + - name: Cache cargo registry + uses: actions/cache@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: ~/.cargo/registry + key: stable-${{ matrix.os }}-node@14-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo index + uses: actions/cache@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: ~/.cargo/git + key: stable-${{ matrix.os }}-node@14-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }} + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: packages/next/native/next-swc.*.node + key: next-swc-nightly-2021-08-12-${{ matrix.target }}-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next/build/swc/**') }} + - name: Cross build aarch64 setup + if: ${{ matrix.target == 'aarch64-apple-darwin' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: | + sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*; + export CC=$(xcrun -f clang); + export CXX=$(xcrun -f clang++); + SYSROOT=$(xcrun --sdk macosx --show-sdk-path); + export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT"; + - name: 'Build' + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: yarn build-native --target ${{ matrix.target }} + env: + MACOSX_DEPLOYMENT_TARGET: '10.13' + working-directory: packages/next + - name: Upload artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: next-swc-binaries + path: packages/next/native/next-swc.${{ matrix.name }}.node + - name: Clear the cargo caches + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache + + test-native: + name: Unit Test Native Code + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 25 + - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') + id: docs-change + - name: Install + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2021-08-12 + profile: minimal + - run: cd packages/next/build/swc && cargo test + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} diff --git a/.github/workflows/test_macos.yml b/.github/workflows/test_macos.yml deleted file mode 100644 index 573425c04aaea..0000000000000 --- a/.github/workflows/test_macos.yml +++ /dev/null @@ -1,27 +0,0 @@ -on: - push: - branches: [canary] - paths-ignore: - - 'bench/**' - - 'docs/**' - - 'errors/**' - - 'examples/**' - -name: Test macOS - -jobs: - testMacOS: - name: macOS (Basic, Production, Acceptance) - runs-on: macos-latest - env: - NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - HEADLESS: true - - steps: - - uses: actions/checkout@v2 - - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - - run: yarn install --frozen-lockfile --check-files || yarn install --frozen-lockfile --check-files - - run: node run-tests.js test/integration/production/test/index.test.js - - run: node run-tests.js test/integration/basic/test/index.test.js - - run: node run-tests.js test/acceptance/* diff --git a/.github/workflows/test_react_experimental.yml b/.github/workflows/test_react_experimental.yml new file mode 100644 index 0000000000000..9073caf8fb414 --- /dev/null +++ b/.github/workflows/test_react_experimental.yml @@ -0,0 +1,52 @@ +on: + schedule: + # * is a special character in YAML so you have to quote this string + - cron: '0 0,12 * * *' + +name: Test react@experimental + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - run: yarn install --frozen-lockfile --check-files + env: + NEXT_TELEMETRY_DISABLED: 1 + + - run: yarn upgrade react@experimental react-dom@experimental -W --dev + + - run: node run-tests.js --timings --write-timings -g 1/1 + + - uses: actions/cache@v2 + id: cache-build + with: + path: ./* + key: ${{ github.sha }}-react-experimental + + testAll: + name: Test All + runs-on: ubuntu-latest + needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_PRIVATE_REACT_ROOT: 1 + NEXT_PRIVATE_SKIP_SIZE_TESTS: true + strategy: + fail-fast: false + matrix: + group: [1, 2, 3, 4, 5, 6] + steps: + - uses: actions/cache@v2 + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-react-experimental + + # TODO: remove after we fix watchpack watching too much + - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + + - run: node run-tests.js --timings -g ${{ matrix.group }}/6 diff --git a/.github/workflows/test_react_next.yml b/.github/workflows/test_react_next.yml index 9eb76fe8ee434..efd8b3810a39e 100644 --- a/.github/workflows/test_react_next.yml +++ b/.github/workflows/test_react_next.yml @@ -6,48 +6,47 @@ on: name: Test react@next jobs: - # build: - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v2 + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 - # - run: yarn install --frozen-lockfile --check-files - # env: - # NEXT_TELEMETRY_DISABLED: 1 + - run: yarn install --frozen-lockfile --check-files + env: + NEXT_TELEMETRY_DISABLED: 1 - # - run: yarn upgrade react@next react-dom@next -W --dev + - run: yarn upgrade react@next react-dom@next -W --dev + + - run: node run-tests.js --timings --write-timings -g 1/1 - # - uses: actions/cache@v2 - # id: cache-build - # with: - # path: ./* - # key: ${{ github.sha }} + - uses: actions/cache@v2 + id: cache-build + with: + path: ./* + key: ${{ github.sha }}-react-next testAll: name: Test All runs-on: ubuntu-latest - # needs: build + needs: build env: NEXT_TELEMETRY_DISABLED: 1 - HEADLESS: true + NEXT_PRIVATE_REACT_ROOT: 1 + NEXT_PRIVATE_SKIP_SIZE_TESTS: true strategy: fail-fast: false matrix: group: [1, 2, 3, 4, 5, 6] steps: - # - uses: actions/cache@v2 - # id: restore-build - # with: - # path: ./* - # key: ${{ github.sha }} - - - uses: actions/checkout@v2 - - - run: yarn install --frozen-lockfile --check-files - - - run: yarn upgrade react@next react-dom@next -W --dev + - uses: actions/cache@v2 + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-react-next # TODO: remove after we fix watchpack watching too much - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - - run: node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3 + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + + - run: node run-tests.js --timings -g ${{ matrix.group }}/6 diff --git a/.gitignore b/.gitignore index f043ec68d93fb..011b7fffcbbde 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # build output dist .next +target # dependencies node_modules diff --git a/.prettierignore b/.prettierignore index 522a3d1a67eac..34f56b257d3fa 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,9 +10,11 @@ packages/react-dev-overlay/lib/** **/__tmp__/** lerna.json .github/actions/next-stats-action/.work +packages/next/build/swc/tests/fixture/**/* packages/next-codemod/transforms/__testfixtures__/**/* packages/next-codemod/transforms/__tests__/**/* packages/next-codemod/**/*.js packages/next-codemod/**/*.d.ts packages/next-env/**/*.d.ts test-timings.json +test/**/out/** diff --git a/.prettierignore_staged b/.prettierignore_staged index e888b26919139..d84fe951e5a58 100644 --- a/.prettierignore_staged +++ b/.prettierignore_staged @@ -1,6 +1,7 @@ **/.next/** **/_next/** **/dist/** +packages/next/build/swc/tests/** packages/next/compiled/**/* packages/next/bundles/webpack/packages/*.runtime.js lerna.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 4fb1ca11305e3..9296ff142e951 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,6 +12,7 @@ "runtimeExecutable": "yarn", "runtimeArgs": ["run", "debug", "dev", "test/integration/basic"], "skipFiles": ["/**"], + "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"], "port": 9229 }, { @@ -26,12 +27,12 @@ "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"] }, { - "name": "Launch app build trace", + "name": "Launch app build trace jaeger", "type": "node", "request": "launch", "cwd": "${workspaceFolder}", "runtimeExecutable": "yarn", - "runtimeArgs": ["run", "trace-debug", "build", "test/integration/basic"], + "runtimeArgs": ["run", "clean-trace-jaeger"], "skipFiles": ["/**"], "port": 9229, "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"] diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 008c8f15e7650..6fbd5f54803cd 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -34,7 +34,7 @@ pr: variables: YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn NEXT_TELEMETRY_DISABLED: '1' - node_version: ^10.10.0 + node_version: ^12.0.0 stages: - stage: Build @@ -69,9 +69,11 @@ stages: - stage: Test dependsOn: Build jobs: - - job: test_ie11_production + - job: test_ie11 pool: vmImage: 'windows-2019' + variables: + BROWSER_NAME: internet explorer steps: - checkout: none - task: NodeTool@0 @@ -85,46 +87,18 @@ stages: key: $(Build.SourceVersion) path: $(System.DefaultWorkingDirectory) displayName: Cache Build - - script: | - yarn testie --forceExit test/integration/production/ - displayName: 'Run tests' - - job: test_unit - pool: - vmImage: 'windows-2019' - steps: - - checkout: none - script: | - wmic datafile where name="C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" get Version /value - displayName: 'List Chrome version' - - task: NodeTool@0 - inputs: - versionSpec: $(node_version) - displayName: 'Install Node.js' - - task: Cache@2 - inputs: - # use deterministic cache key that is specific - # to this test run - key: $(Build.SourceVersion) - path: $(System.DefaultWorkingDirectory) - displayName: Cache Build + npm i -g selenium-standalone@6.18.0 + displayName: 'Install selenium node' + - script: | - node run-tests.js -g 1/1 --timings --azure --type unit + node run-tests.js -c 1 test/integration/production/test/index.test.js test/integration/css-client-nav/test/index.test.js test/integration/rewrites-has-condition/test/index.test.js displayName: 'Run tests' - - job: test_chrome_integration + - job: test_unit pool: vmImage: 'windows-2019' - strategy: - matrix: - node-10-1: - group: 1/4 - node-10-2: - group: 2/4 - node-10-3: - group: 3/4 - node-10-4: - group: 4/4 steps: - checkout: none - script: | @@ -142,5 +116,39 @@ stages: path: $(System.DefaultWorkingDirectory) displayName: Cache Build - script: | - node run-tests.js -g $(group) --timings --azure + node run-tests.js --type unit displayName: 'Run tests' + # TODO: investigate re-enabling when stability matches running in + # tests in ubuntu environment + # - job: test_chrome_integration + # pool: + # vmImage: 'windows-2019' + # strategy: + # matrix: + # nodejs-1: + # group: 1/4 + # nodejs-2: + # group: 2/4 + # nodejs-3: + # group: 3/4 + # nodejs-4: + # group: 4/4 + # steps: + # - checkout: none + # - script: | + # wmic datafile where name="C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" get Version /value + # displayName: 'List Chrome version' + # - task: NodeTool@0 + # inputs: + # versionSpec: $(node_version) + # displayName: 'Install Node.js' + # - task: Cache@2 + # inputs: + # # use deterministic cache key that is specific + # # to this test run + # key: $(Build.SourceVersion) + # path: $(System.DefaultWorkingDirectory) + # displayName: Cache Build + # - script: | + # node run-tests.js -g $(group) --timings --azure + # displayName: 'Run tests' diff --git a/bench/capture-trace.js b/bench/capture-trace.js deleted file mode 100644 index 52ef690bff76b..0000000000000 --- a/bench/capture-trace.js +++ /dev/null @@ -1,66 +0,0 @@ -const http = require('http') -const fs = require('fs') - -const PORT = 9411 -const HOST = '0.0.0.0' - -const traces = [] - -const onReady = () => console.log(`Listening on http://${HOST}:${PORT}`) -const onRequest = async (req, res) => { - if ( - req.method !== 'POST' || - req.url !== '/api/v2/spans' || - (req.headers && req.headers['content-type']) !== 'application/json' - ) { - res.writeHead(200) - return res.end() - } - - try { - const body = JSON.parse(await getBody(req)) - for (const traceEvent of body) { - traces.push(traceEvent) - } - res.writeHead(200) - } catch (err) { - console.warn(err) - res.writeHead(500) - } - - res.end() -} - -const getBody = (req) => - new Promise((resolve, reject) => { - let data = '' - req.on('data', (chunk) => { - data += chunk - }) - req.on('end', () => { - if (!req.complete) { - return reject('Connection terminated before body was received.') - } - resolve(data) - }) - req.on('aborted', () => reject('Connection aborted.')) - req.on('error', () => reject('Connection error.')) - }) - -const main = () => { - const args = process.argv.slice(2) - const outFile = args[0] || `./trace-${Date.now()}.json` - - process.on('SIGINT', () => { - console.log(`\nSaving to ${outFile}...`) - fs.writeFileSync(outFile, JSON.stringify(traces, null, 2)) - process.exit() - }) - - const server = http.createServer(onRequest) - server.listen(PORT, HOST, onReady) -} - -if (require.main === module) { - main() -} diff --git a/bench/instrument.js b/bench/instrument.js deleted file mode 100644 index 38176de31cbeb..0000000000000 --- a/bench/instrument.js +++ /dev/null @@ -1,39 +0,0 @@ -// Disable automatic instrumentation -process.env.OTEL_NO_PATCH_MODULES = '*' - -const { NodeTracerProvider } = require('@opentelemetry/node') -const { SimpleSpanProcessor } = require('@opentelemetry/tracing') -const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin') - -const tracerProvider = new NodeTracerProvider({ - // All automatic instrumentation plugins have to be disabled as it affects worker_thread/child_process bootup performance - plugins: { - mongodb: { enabled: false, path: '@opentelemetry/plugin-mongodb' }, - grpc: { enabled: false, path: '@opentelemetry/plugin-grpc' }, - '@grpc/grpc-js': { enabled: false, path: '@opentelemetry/plugin-grpc-js' }, - http: { enabled: false, path: '@opentelemetry/plugin-http' }, - https: { enabled: false, path: '@opentelemetry/plugin-https' }, - mysql: { enabled: false, path: '@opentelemetry/plugin-mysql' }, - pg: { enabled: false, path: '@opentelemetry/plugin-pg' }, - redis: { enabled: false, path: '@opentelemetry/plugin-redis' }, - ioredis: { enabled: false, path: '@opentelemetry/plugin-ioredis' }, - 'pg-pool': { enabled: false, path: '@opentelemetry/plugin-pg-pool' }, - express: { enabled: false, path: '@opentelemetry/plugin-express' }, - '@hapi/hapi': { - enabled: false, - path: '@opentelemetry/hapi-instrumentation', - }, - koa: { enabled: false, path: '@opentelemetry/koa-instrumentation' }, - dns: { enabled: false, path: '@opentelemetry/plugin-dns' }, - }, -}) - -tracerProvider.addSpanProcessor( - new SimpleSpanProcessor( - new ZipkinExporter({ - serviceName: 'next-js', - }) - ) -) - -tracerProvider.register() diff --git a/bench/nested-deps/.gitignore b/bench/nested-deps/.gitignore new file mode 100644 index 0000000000000..c258d11a350c5 --- /dev/null +++ b/bench/nested-deps/.gitignore @@ -0,0 +1 @@ +components/* diff --git a/bench/nested-deps/fuzzponent.js b/bench/nested-deps/fuzzponent.js new file mode 100755 index 0000000000000..4904669441a8a --- /dev/null +++ b/bench/nested-deps/fuzzponent.js @@ -0,0 +1,182 @@ +#!/usr/bin/env node +const path = require('path') +const fs = require('fs') + +const getSequenceGenerator = require('random-seed') +const generate = require('@babel/generator').default +const t = require('@babel/types') + +const MIN_COMPONENT_NAME_LEN = 18 +const MAX_COMPONENT_NAME_LEN = 24 +const MIN_CHILDREN = 4 +const MAX_CHILDREN = 80 + +const arrayUntil = (len) => [...Array(len)].map((_, i) => i) + +const generateFunctionalComponentModule = (componentName, children = []) => { + const body = [ + generateImport('React', 'react'), + ...children.map((childName) => generateImport(childName, `./${childName}`)), + t.variableDeclaration('const', [ + t.variableDeclarator( + t.identifier(componentName), + t.arrowFunctionExpression( + [], + t.parenthesizedExpression( + generateJSXElement( + 'div', + children.map((childName) => generateJSXElement(childName)) + ) + ) + ) + ), + ]), + t.exportDefaultDeclaration(t.identifier(componentName)), + ] + + return t.program(body, [], 'module') +} + +const generateJSXElement = (componentName, children = null) => + t.JSXElement( + t.JSXOpeningElement(t.JSXIdentifier(componentName), [], !children), + children ? t.JSXClosingElement(t.JSXIdentifier(componentName)) : null, + children || [], + !children + ) + +const generateImport = (componentName, requireString) => + t.importDeclaration( + [t.importDefaultSpecifier(t.identifier(componentName))], + t.stringLiteral(requireString) + ) + +const validFirstChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +const validOtherChars = validFirstChars.toLowerCase() +function generateComponentName(seqGenerator, opts) { + const numOtherChars = seqGenerator.intBetween(opts.minLen, opts.maxLen) + const firstChar = validFirstChars[seqGenerator.range(validFirstChars.length)] + const otherChars = arrayUntil(numOtherChars).map( + () => validOtherChars[seqGenerator.range(validOtherChars.length)] + ) + return `${firstChar}${otherChars.join('')}` +} + +function* generateModules(name, remainingDepth, seqGenerator, opts) { + const filename = `${name}.${opts.extension}` + let ast + + if (name === 'index') { + name = 'RootComponent' + } + + if (remainingDepth === 0) { + ast = generateFunctionalComponentModule(name) + } else { + const numChildren = seqGenerator.intBetween(opts.minChild, opts.maxChild) + const children = arrayUntil(numChildren).map(() => + generateComponentName(seqGenerator, opts) + ) + ast = generateFunctionalComponentModule(name, children) + + for (const child of children) { + yield* generateModules(child, remainingDepth - 1, seqGenerator, opts) + } + } + + yield { + filename, + content: generate(ast).code, + } +} + +function generateFuzzponents(outdir, seed, depth, opts) { + const seqGenerator = getSequenceGenerator(seed) + + const filenames = new Set() + for (const { filename, content } of generateModules( + 'index', + depth, + seqGenerator, + opts + )) { + if (filenames.has(filename)) { + throw new Error( + `Seed "${seed}" generates output with filename collisions.` + ) + } else { + filenames.add(filename) + } + const fpath = path.join(outdir, filename) + fs.writeFileSync(fpath, `// ${filename}\n\n${content}`) + } +} + +if (require.main === module) { + const { outdir, seed, depth, ...opts } = require('yargs') + .option('depth', { + alias: 'd', + demandOption: true, + describe: 'component hierarchy depth', + type: 'number', + }) + .option('seed', { + alias: 's', + demandOption: true, + describe: 'prng seed', + type: 'number', + }) + .option('outdir', { + alias: 'o', + demandOption: false, + default: process.cwd(), + describe: 'the directory where components should be written', + type: 'string', + normalize: true, + }) + .option('minLen', { + demandOption: false, + default: MIN_COMPONENT_NAME_LEN, + describe: 'the smallest acceptible component name length', + type: 'number', + }) + .option('maxLen', { + demandOption: false, + default: MAX_COMPONENT_NAME_LEN, + describe: 'the largest acceptible component name length', + type: 'number', + }) + .option('minLen', { + demandOption: false, + default: MIN_COMPONENT_NAME_LEN, + describe: 'the smallest acceptible component name length', + type: 'number', + }) + .option('maxLen', { + demandOption: false, + default: MAX_COMPONENT_NAME_LEN, + describe: 'the largest acceptible component name length', + type: 'number', + }) + .option('minChild', { + demandOption: false, + default: MIN_CHILDREN, + describe: 'the smallest number of acceptible component children', + type: 'number', + }) + .option('maxChild', { + demandOption: false, + default: MAX_CHILDREN, + describe: 'the largest number of acceptible component children', + type: 'number', + }) + .option('extension', { + default: 'jsx', + describe: 'extension to use for generated components', + type: 'string', + }).argv + + generateFuzzponents(outdir, seed, depth, opts) +} + +module.exports = generateFuzzponents diff --git a/bench/nested-deps/next.config.js b/bench/nested-deps/next.config.js new file mode 100644 index 0000000000000..d06b18cf4e29c --- /dev/null +++ b/bench/nested-deps/next.config.js @@ -0,0 +1,9 @@ +module.exports = { + eslint: { + ignoreDuringBuilds: true, + }, + experimental: { + swcLoader: true, + swcMinify: true, + }, +} diff --git a/bench/nested-deps/package.json b/bench/nested-deps/package.json new file mode 100644 index 0000000000000..6bef7c2d0787c --- /dev/null +++ b/bench/nested-deps/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "prepare": "rm -rf components && mkdir components && node ./fuzzponent.js -d 2 -s 206 -o components", + "dev": "../../node_modules/.bin/next dev", + "build": "../../node_modules/.bin/next build", + "start": "../../node_modules/.bin/next start", + "dev-nocache": "rm -rf .next && yarn dev", + "build-nocache": "rm -rf .next && yarn build" + } +} diff --git a/bench/nested-deps/pages/index.jsx b/bench/nested-deps/pages/index.jsx new file mode 100644 index 0000000000000..f199ad545abfd --- /dev/null +++ b/bench/nested-deps/pages/index.jsx @@ -0,0 +1,5 @@ +import Comp from '../components/index.jsx' + +export default function Home() { + return +} diff --git a/bench/package.json b/bench/package.json deleted file mode 100644 index eb3c593c98f8c..0000000000000 --- a/bench/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "next-bench", - "scripts": { - "build": "next build", - "start": "NODE_ENV=production npm run build && NODE_ENV=production next start", - "bench:stateless": "ab -c1 -n3000 http://0.0.0.0:3000/stateless", - "bench:stateless-big": "ab -c1 -n500 http://0.0.0.0:3000/stateless-big", - "bench:recursive-copy": "node recursive-copy/run" - }, - "dependencies": { - "fs-extra": "7.0.1", - "recursive-copy": "2.0.10" - } -} diff --git a/bench/readdir/glob.js b/bench/readdir/glob.js index 1c409ad384ba5..170f4fb050eb5 100644 --- a/bench/readdir/glob.js +++ b/bench/readdir/glob.js @@ -1,6 +1,7 @@ -const { join } = require('path') -const { promisify } = require('util') -const globMod = require('glob') +import { join } from 'path' +import { promisify } from 'util' +import globMod from 'glob' + const glob = promisify(globMod) const resolveDataDir = join(__dirname, 'fixtures', '**/*') diff --git a/bench/readdir/recursive-readdir.js b/bench/readdir/recursive-readdir.js index 871256707ddb6..2167f30336553 100644 --- a/bench/readdir/recursive-readdir.js +++ b/bench/readdir/recursive-readdir.js @@ -1,5 +1,5 @@ -const { join } = require('path') -const { recursiveReadDir } = require('next/dist/lib/recursive-readdir') +import { join } from 'path' +import { recursiveReadDir } from 'next/dist/lib/recursive-readdir' const resolveDataDir = join(__dirname, 'fixtures') async function test() { diff --git a/bench/recursive-copy/run.js b/bench/recursive-copy/run.js index c4bad36c88df3..ab12258004724 100644 --- a/bench/recursive-copy/run.js +++ b/bench/recursive-copy/run.js @@ -1,18 +1,14 @@ -const { join } = require('path') -const fs = require('fs-extra') - -const recursiveCopyNpm = require('recursive-copy') - -const { - recursiveCopy: recursiveCopyCustom, -} = require('next/dist/lib/recursive-copy') +import { join } from 'path' +import { ensureDir, outputFile, remove } from 'fs-extra' +import recursiveCopyNpm from 'recursive-copy' +import { recursiveCopy as recursiveCopyCustom } from 'next/dist/lib/recursive-copy' const fixturesDir = join(__dirname, 'fixtures') const srcDir = join(fixturesDir, 'src') const destDir = join(fixturesDir, 'dest') const createSrcFolder = async () => { - await fs.ensureDir(srcDir) + await ensureDir(srcDir) const files = new Array(100) .fill(undefined) @@ -20,7 +16,7 @@ const createSrcFolder = async () => { join(srcDir, `folder${i % 5}`, `folder${i + (1 % 5)}`, `file${i}`) ) - await Promise.all(files.map((file) => fs.outputFile(file, 'hello'))) + await Promise.all(files.map((file) => outputFile(file, 'hello'))) } async function run(fn) { @@ -38,7 +34,7 @@ async function run(fn) { for (let i = 0; i < 10; i++) { const t = await test() - await fs.remove(destDir) + await remove(destDir) ts.push(t) } @@ -57,7 +53,7 @@ async function main() { console.log('test recursive-copy custom implementation') await run(recursiveCopyCustom) - await fs.remove(fixturesDir) + await remove(fixturesDir) } main() diff --git a/bench/recursive-delete/recursive-delete.js b/bench/recursive-delete/recursive-delete.js index 8989739c9039f..e23ed3e6f26a6 100644 --- a/bench/recursive-delete/recursive-delete.js +++ b/bench/recursive-delete/recursive-delete.js @@ -1,5 +1,5 @@ -const { join } = require('path') -const { recursiveDelete } = require('next/dist/lib/recursive-delete') +import { join } from 'path' +import { recursiveDelete } from 'next/dist/lib/recursive-delete' const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`) async function test() { diff --git a/bench/recursive-delete/rimraf.js b/bench/recursive-delete/rimraf.js index 2b5d50457a13c..827cdaae77484 100644 --- a/bench/recursive-delete/rimraf.js +++ b/bench/recursive-delete/rimraf.js @@ -1,8 +1,9 @@ -const { join } = require('path') -const { promisify } = require('util') -const rimrafMod = require('rimraf') -const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`, '**/*') +import { join } from 'path' +import { promisify } from 'util' +import rimrafMod from 'rimraf' + const rimraf = promisify(rimrafMod) +const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`, '**/*') async function test() { const time = process.hrtime() diff --git a/bench/rendering/package.json b/bench/rendering/package.json new file mode 100644 index 0000000000000..d311332afdefa --- /dev/null +++ b/bench/rendering/package.json @@ -0,0 +1,14 @@ +{ + "name": "next-bench", + "scripts": { + "build": "next build", + "start": "NODE_ENV=production npm run build && NODE_ENV=production next start", + "bench:stateless": "ab -c1 -n3000 http://0.0.0.0:3000/stateless", + "bench:stateless-big": "ab -c1 -n500 http://0.0.0.0:3000/stateless-big", + "bench:recursive-copy": "node recursive-copy/run" + }, + "dependencies": { + "fs-extra": "10.0.0", + "recursive-copy": "2.0.11" + } +} diff --git a/bench/pages/stateless-big.js b/bench/rendering/pages/stateless-big.js similarity index 100% rename from bench/pages/stateless-big.js rename to bench/rendering/pages/stateless-big.js diff --git a/bench/pages/stateless.js b/bench/rendering/pages/stateless.js similarity index 100% rename from bench/pages/stateless.js rename to bench/rendering/pages/stateless.js diff --git a/bench/readme.md b/bench/rendering/readme.md similarity index 100% rename from bench/readme.md rename to bench/rendering/readme.md diff --git a/check-examples.sh b/check-examples.sh deleted file mode 100755 index 03dcbb6f638eb..0000000000000 --- a/check-examples.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -cd `dirname $0` - -for folder in examples/* ; do - cp -n packages/create-next-app/templates/default/gitignore $folder/.gitignore; - if [ -f "$folder/package.json" ]; then - cat $folder/package.json | jq '.license = "MIT"' | sponge $folder/package.json - fi -done; - -if [[ ! -z $(git status -s) ]];then - echo "Detected changes" - git status - exit 1 -fi diff --git a/contributing.md b/contributing.md index 6bfc13c554a6f..8578bf50d85a6 100644 --- a/contributing.md +++ b/contributing.md @@ -1,43 +1,75 @@ # Contributing to Next.js -Our Commitment to Open Source can be found [here](https://vercel.com/oss). +Read about our [Commitment to Open Source](https://vercel.com/oss). To +contribute to [our examples](examples), please see **[Adding +examples](#adding-examples)** below. -1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device. -2. Create a new branch `git checkout -b MY_BRANCH_NAME` -3. Install yarn: `npm install -g yarn` -4. Install the dependencies: `yarn` -5. Run `yarn dev` to build and watch for code changes -6. In a new terminal, run `yarn types` to compile declaration files from TypeScript -7. The development branch is `canary` (this is the branch pull requests should be made against). On a release, the relevant parts of the changes in the `canary` branch are rebased into `master`. +## Developing -> You may need to run `yarn types` again if your types get outdated. +The development branch is `canary`, and this is the branch that all pull +requests should be made against. After publishing a stable release, the changes +in the `canary` branch are rebased into `master`. The changes on the `canary` +branch are published to the `@canary` dist-tag daily. -To contribute to [our examples](examples), take a look at the [“Adding examples” section](#adding-examples). +To develop locally: -## To run tests +1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your + own GitHub account and then + [clone](https://help.github.com/articles/cloning-a-repository/) it to your + local device. +2. Create a new branch: + ``` + git checkout -b MY_BRANCH_NAME + ``` +3. Install yarn: + ``` + npm install -g yarn + ``` +4. Install the dependencies with: + ``` + yarn + ``` +5. Start developing and watch for code changes: + ``` + yarn dev + ``` +6. In a new terminal, run `yarn types` to compile declaration files from + TypeScript. -Make sure you have `chromedriver` installed for your Chrome version. You can install it with + _Note: You may need to repeat this step if your types get outdated._ -- `brew install --cask chromedriver` on Mac OS X -- `chocolatey install chromedriver` on Windows -- Or manually download the version that matches your installed chrome version (if there's no match, download a version under it, but not above) from the [chromedriver repo](https://chromedriver.storage.googleapis.com/index.html) and add the binary to `/node_modules/.bin` +For instructions on how to build a project with your local version of the CLI, +see **[Developing with your local version of Next.js](#developing-with-your-local-version-of-nextjs)** +below. (Naively linking the binary is not sufficient to develop locally.) -Running all tests: +## Building -```sh -yarn testonly +You can build the project, including all type definitions, with: + +```bash +yarn build +# - or - +yarn prepublish ``` -If you would like to run the tests in headless mode (with the browser windows hidden) you can do +If you need to clean the project for any reason, use `yarn clean`. + +## Testing + +See the [testing readme](./test/readme.md) for information on writing tests. + +You may have to [install Rust](https://www.rust-lang.org/tools/install) and build our native packages to see all tests pass locally. We check in binaries for the most common targets and those required for CI so that most people don't have to, but if you do not see a binary for your target in `packages/next/native`, you can build it by running `yarn --cwd packages/next build-native`. If you are working on the Rust code and you need to build the binaries for ci, you can manually trigger [the workflow](https://github.com/vercel/next.js/actions/workflows/build_native.yml) to build and commit with the "Run workflow" button. + +### Running tests ```sh -yarn testheadless +yarn testonly ``` -If you would like to use a specific Chrome/Chromium binary to run tests you can specify it with +If you would like to run the tests in headless mode (with the browser windows hidden) you can do ```sh -CHROME_BIN='path/to/chrome/bin' yarn testonly +yarn testheadless ``` Running a specific test suite inside of the `test/integration` directory: @@ -46,13 +78,13 @@ Running a specific test suite inside of the `test/integration` directory: yarn testonly --testPathPattern "production" ``` -Running just one test in the `production` test suite: +Running one test in the `production` test suite: ```sh yarn testonly --testPathPattern "production" -t "should allow etag header support" ``` -## Running the integration apps +### Running the integration apps Running examples can be done with: @@ -77,7 +109,11 @@ EXAMPLE=./test/integration/basic ) ``` -## Running your own app with locally compiled version of Next.js +## Developing with your local version of Next.js + +There are two options to develop with your local version of the codebase: + +### Set as local dependency in package.json 1. In your app's `package.json`, replace: @@ -88,7 +124,7 @@ EXAMPLE=./test/integration/basic with: ```json - "next": "file:/packages/next", + "next": "file:/path/to/next.js/packages/next", ``` 2. In your app's root directory, make sure to remove `next` from `node_modules` with: @@ -115,6 +151,42 @@ EXAMPLE=./test/integration/basic yarn install --force ``` +or + +### Develop inside the monorepo + +1. Move your app inside of the Next.js monorepo. + +2. Run with `yarn next-with-deps ./app-path-in-monorepo` + +This will use the version of `next` built inside of the Next.js monorepo and the +main `yarn dev` monorepo command can be running to make changes to the local +Next.js version at the same time (some changes might require re-running `yarn next-with-deps` to take affect). + +## Adding warning/error descriptions + +In Next.js we have a system to add helpful links to warnings and errors. + +This allows for the logged message to be short while giving a broader description and instructions on how to solve the warning/error. + +In general all warnings and errors added should have these links attached. + +Below are the steps to add a new link: + +1. Create a new markdown file under the `errors` directory based on + `errors/template.md`: + + ```shell + cp errors/template.md errors/.md + ``` + +2. Add the newly added file to `errors/manifest.json` +3. Add the following url to your warning/error: + `https://nextjs.org/docs/messages/`. + + For example, to link to `errors/api-routes-static-export.md` you use the url: + `https://nextjs.org/docs/messages/api-routes-static-export` + ## Adding examples When you add an example to the [examples](examples) directory, don’t forget to add a `README.md` file with the following format: @@ -124,12 +196,19 @@ When you add an example to the [examples](examples) directory, don’t forget to - To add additional installation instructions, please add it where appropriate. - To add additional notes, add `## Notes` section at the end. - Remove the `Deploy your own` section if your example can’t be immediately deployed to Vercel. +- Remove the `Preview` section if the example doesn't work on [StackBlitz](http://stackblitz.com/) and file an issue [here](https://github.com/stackblitz/webcontainer-core). ````markdown # Example Name Description +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/DIRECTORY_NAME) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): @@ -148,3 +227,7 @@ yarn create next-app --example DIRECTORY_NAME DIRECTORY_NAME-app Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). ```` + +## Publishing + +Repository maintainers can use `yarn publish-canary` to publish a new version of all packages to npm. diff --git a/docs/advanced-features/amp-support/typescript.md b/docs/advanced-features/amp-support/typescript.md index 273c943d94920..2f284c26d8035 100644 --- a/docs/advanced-features/amp-support/typescript.md +++ b/docs/advanced-features/amp-support/typescript.md @@ -6,4 +6,4 @@ description: Using AMP with TypeScript? Extend your typings to allow AMP compone AMP currently doesn't have built-in types for TypeScript, but it's in their roadmap ([#13791](https://github.com/ampproject/amphtml/issues/13791)). -As a workaround you can manually create a file called `amp.d.ts` inside your project and add the custom types described [here](https://stackoverflow.com/a/50601125). +As a workaround you can manually create a file called `amp.d.ts` inside your project and add these [custom types](https://stackoverflow.com/a/50601125). diff --git a/docs/advanced-features/codemods.md b/docs/advanced-features/codemods.md index d20824316e58d..f897c89f206ea 100644 --- a/docs/advanced-features/codemods.md +++ b/docs/advanced-features/codemods.md @@ -17,6 +17,14 @@ Codemods are transformations that run on your codebase programmatically. This al - `--dry` Do a dry-run, no code will be edited - `--print` Prints the changed output for comparison +## Next.js 11 + +### `cra-to-next` (experimental) + +Migrates a Create React App project to Next.js; creating a pages directory and necessary config to match behavior. Client-side only rendering is leveraged initially to prevent breaking compatibility due to `window` usage during SSR and can be enabled seamlessly to allow gradual adoption of Next.js specific features. + +Please share any feedback related to this transform [in this discussion](https://github.com/vercel/next.js/discussions/25858). + ## Next.js 10 ### `add-missing-react-import` @@ -132,7 +140,7 @@ npx @next/codemod withamp-to-config ### `url-to-withrouter` -Transforms the deprecated automatically injected `url` property on top level pages to using `withRouter` and the `router` property it injects. Read more here: [err.sh/next.js/url-deprecated](https://err.sh/next.js/url-deprecated) +Transforms the deprecated automatically injected `url` property on top level pages to using `withRouter` and the `router` property it injects. Read more here: [https://nextjs.org/docs/messages/url-deprecated](https://nextjs.org/docs/messages/url-deprecated) For example: @@ -161,7 +169,7 @@ export default withRouter( ) ``` -This is just one case. All the cases that are transformed (and tested) can be found in the [`__testfixtures__` directory](https://github.com/vercel/next.js/tree/canary/packages/next-codemod/transforms/__testfixtures__/url-to-withrouter). +This is one case. All the cases that are transformed (and tested) can be found in the [`__testfixtures__` directory](https://github.com/vercel/next.js/tree/canary/packages/next-codemod/transforms/__testfixtures__/url-to-withrouter). #### Usage diff --git a/docs/advanced-features/custom-app.md b/docs/advanced-features/custom-app.md index 571bf79da5a6e..71dd20af32577 100644 --- a/docs/advanced-features/custom-app.md +++ b/docs/advanced-features/custom-app.md @@ -42,8 +42,9 @@ The `Component` prop is the active `page`, so whenever you navigate between rout ### Caveats -- If your app is running and you just added a custom `App`, you'll need to restart the development server. Only required if `pages/_app.js` didn't exist before. +- If your app is running and you added a custom `App`, you'll need to restart the development server. Only required if `pages/_app.js` didn't exist before. - Adding a custom `getInitialProps` in your `App` will disable [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) in pages without [Static Generation](/docs/basic-features/data-fetching.md#getstaticprops-static-generation). +- When you add `getInitialProps` in your custom app, you must `import App from "next/app"`, call `App.getInitialProps(appContext)` inside `getInitialProps` and merge the returned object into the return value. - `App` currently does not support Next.js [Data Fetching methods](/docs/basic-features/data-fetching.md) like [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) or [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering). ### TypeScript diff --git a/docs/advanced-features/custom-error-page.md b/docs/advanced-features/custom-error-page.md index 243a76ce5164b..6dfa999892be1 100644 --- a/docs/advanced-features/custom-error-page.md +++ b/docs/advanced-features/custom-error-page.md @@ -21,11 +21,26 @@ export default function Custom404() { } ``` +> **Note**: You can use [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) inside this page if you need to fetch data at build time. + ## 500 Page -By default Next.js provides a 500 error page that matches the default 404 page’s style. This page is not statically optimized as it allows server-side errors to be reported. This is why 404 and 500 (other errors) are separated. +Server-rendering an error page for every visit adds complexity to responding to errors. To help users get responses to errors as fast as possible, Next.js provides a static 500 page by default without having to add any additional files. + +### Customizing The 500 Page + +To customize the 500 page you can create a `pages/500.js` file. This file is statically generated at build time. + +```jsx +// pages/500.js +export default function Custom500() { + return

500 - Server-side error occurred

+} +``` + +> **Note**: You can use [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) inside this page if you need to fetch data at build time. -### Customizing The Error Page +### More Advanced Error Page Customizing 500 errors are handled both client-side and server-side by the `Error` component. If you wish to override it, define the file `pages/_error.js` and add the following code: diff --git a/docs/advanced-features/custom-server.md b/docs/advanced-features/custom-server.md index f1ef1fb01c684..f6d95c59e7b2d 100644 --- a/docs/advanced-features/custom-server.md +++ b/docs/advanced-features/custom-server.md @@ -15,11 +15,11 @@ description: Start a Next.js app programmatically using a custom server. -Typically you start your next server with `next start`. It's possible, however, to start a server 100% programmatically in order to use custom route patterns. +By default, Next.js includes its own server with `next start`. If you have an existing backend, you can still use it with Next.js (this is not a custom server). A custom Next.js server allows you to start a server 100% programmatically in order to use custom server patterns. Most of the time, you will not need this – but it's available for complete customization. -> A custom server **can not** be deployed on [Vercel](https://vercel.com/solutions/nextjs), the platform Next.js was made for. +> **Note:** A custom server **can not** be deployed on [Vercel](https://vercel.com/solutions/nextjs). -> Before deciding to use a custom server please keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements. A custom server will remove important performance optimizations, like **serverless functions** and **[Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md).** +> Before deciding to use a custom server, please keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements. A custom server will remove important performance optimizations, like **serverless functions** and **[Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md).** Take a look at the following example of a custom server: diff --git a/docs/advanced-features/customizing-babel-config.md b/docs/advanced-features/customizing-babel-config.md index f8fcd8c5d3f5b..aee52289f1589 100644 --- a/docs/advanced-features/customizing-babel-config.md +++ b/docs/advanced-features/customizing-babel-config.md @@ -13,7 +13,7 @@ description: Extend the babel preset added by Next.js with your own configs. Next.js includes the `next/babel` preset to your app, which includes everything needed to compile React applications and server-side code. But if you want to extend the default Babel configs, it's also possible. -To start, you only need to define a `.babelrc` file at the top of your app. If such a file is found, it will be considered as the _source of truth_, and therefore it needs to define what Next.js needs as well, which is the `next/babel` preset. +To start, you only need to define a `.babelrc` file (or `babel.config.js`) at the top of your app. If such a file is found, it will be considered as the _source of truth_, and therefore it needs to define what Next.js needs as well, which is the `next/babel` preset. Here's an example `.babelrc` file: diff --git a/docs/advanced-features/debugging.md b/docs/advanced-features/debugging.md index 3c3b30e735504..be00e2553196b 100644 --- a/docs/advanced-features/debugging.md +++ b/docs/advanced-features/debugging.md @@ -4,74 +4,89 @@ description: Debug your Next.js app. # Debugging -This documentation explains how you can debug your Next.js frontend and backend code with full source maps support using either the [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools) or the [VSCode debugger](https://code.visualstudio.com/docs/editor/debugging). +This documentation explains how you can debug your Next.js frontend and backend code with full source maps support using either the [VS Code debugger](https://code.visualstudio.com/docs/editor/debugging) or [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools). -It requires you to first launch your Next.js application in debug mode in one terminal and then connect an inspector (Chrome DevTools or VS Code) to it. +Any debugger that can attach to Node.js can also be used to debug a Next.js application. You can find more details in the Node.js [Debugging Guide](https://nodejs.org/en/docs/guides/debugging-getting-started/). -There might be more ways to debug a Next.js application since all it requires is to expose the Node.js debugger and start an inspector client. You can find more details in the [Node.js documentation](https://nodejs.org/en/docs/guides/debugging-getting-started/). +## Debugging with VS Code -## Step 1: Start Next.js in debug mode +Create a file named `.vscode/launch.json` at the root of your project with the following content: -Next.js being a Node.js application, all we have to do is to pass down the [`--inspect`](https://nodejs.org/api/cli.html#cli_node_options_options) flag to the underlying Node.js process for it to start in debug mode. +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Next.js: debug server-side", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev" + }, + { + "name": "Next.js: debug client-side", + "type": "pwa-chrome", + "request": "launch", + "url": "http://localhost:3000" + }, + { + "name": "Next.js: debug full stack", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev", + "console": "integratedTerminal", + "serverReadyAction": { + "pattern": "started server on .+, url: (https?://.+)", + "uriFormat": "%s", + "action": "debugWithChrome" + } + } + ] +} +``` + +`npm run dev` can be replaced with `yarn dev` if you're using Yarn. If you're [changing the port number](<(/docs/api-reference/cli#development)>) your application starts on, replace the `3000` in `http://localhost:3000` with the port you're using instead. + +Now go to the Debug panel (Ctrl+Shift+D on Windows/Linux, ++D on macOS), select a launch configuration, then press F5 or select **Debug: Start Debugging** from the Command Palette to start your debugging session. + +## Debugging with Chrome DevTools + +### Client-side code + +Start your development server as usual by running `next dev`, `npm run dev`, or `yarn dev`. Once the server starts, open `http://localhost:3000` (or your alternate URL) in Chrome. Next, open Chrome's Developer Tools (Ctrl+Shift+J on Windows/Linux, ++I on macOS), then go to the **Sources** tab + +Now, any time your client-side code reaches a [`debugger`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger) statement, code execution will pause and that file will appear in the debug area. You can also press Ctrl+P on Windows/Linux or +P on macOS to search for a file and set breakpoints manually. Note that when searching here, your source files will have paths starting with `webpack://_N_E/./`. + +### Server-side code -First, start Next.js with the inspect flag: +To debug server-side Next.js code with Chrome DevTools, you need to pass the [`--inspect`](https://nodejs.org/api/cli.html#cli_inspect_host_port) flag to the underlying Node.js process: ```bash NODE_OPTIONS='--inspect' next dev ``` -If you're using `npm run dev` or `yarn dev` (See: [Getting Started](/docs/getting-started.md)) then you should update the `dev` script on your `package.json`: +If you're using `npm run dev` or `yarn dev` (see [Getting Started](/docs/getting-started)) then you should update the `dev` script on your `package.json`: ```json "dev": "NODE_OPTIONS='--inspect' next dev" ``` -The result of launching Next.js with the inspect flag looks like this: +Launching the Next.js dev server with the `--inspect` flag will look something like this: -```bash +```shellsession Debugger listening on ws://127.0.0.1:9229/0cf90313-350d-4466-a748-cd60f4e47c95 For help, see: https://nodejs.org/en/docs/inspector -ready - started server on http://localhost:3000 -``` - -> Be aware that using `NODE_OPTIONS='--inspect' npm run dev` or `NODE_OPTIONS='--inspect' yarn dev` won't work. This would try to start multiple debuggers on the same port: one for the npm/yarn process and one for Next.js. You would then get an error like `Starting inspector on 127.0.0.1:9229 failed: address already in use` in your console. - -## Step 2: Connect to the debugger - -### Using Chrome DevTools - -Once you open a new tab in Google Chrome and go to `chrome://inspect`, you should see your Next.js application inside the "Remote Target" section. Now click "inspect" to open a screen that will be your debugging environment from now on. - -### Using the Debugger in Visual Studio Code - -We will be using the [attach mode](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_setting-up-an-attach-configuration) of VS Code to attach the VS Code inspector to our running debugger started in step 1. - -Create a file named `.vscode/launch.json` at the root of your project with this content: - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "attach", - "name": "Launch Program", - "skipFiles": ["/**"], - "port": 9229 - } - ] -} +ready - started server on 0.0.0.0:3000, url: http://localhost:3000 ``` -Now hit F5 or select **Debug: Start Debugging** from the Command Palette and you can start your debugging session. +> Be aware that running `NODE_OPTIONS='--inspect' npm run dev` or `NODE_OPTIONS='--inspect' yarn dev` won't work. This would try to start multiple debuggers on the same port: one for the npm/yarn process and one for Next.js. You would then get an error like `Starting inspector on 127.0.0.1:9229 failed: address already in use` in your console. -## Step 3: Put breakpoints and see what happens +Once the server starts, open a new tab in Chrome and visit `chrome://inspect`, where you should see your Next.js application inside the **Remote Target** section. Click **inspect** under your application to open a separate DevTools window, then go to the **Sources** tab. -Now you can use the [`debugger`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger) statement to pause your backend or frontend code anytime you want to observe and debug your code more precisely. +Debugging server-side code here works much like debugging client-side code with Chrome DevTools, except that when you search for files here with Ctrl+P or +P, your source files will have paths starting with `webpack://{application-name}/./` (where `{application-name}` will be replaced with the name of your application according to your `package.json` file). -If you trigger the underlying code by refreshing the current page, clicking on a page link or fetching an API route, your code will be paused and the debugger window will pop up. +## More information -To learn more on how to use a JavaScript debugger, take a look at the following documentation: +To learn more about how to use a JavaScript debugger, take a look at the following documentation: -- [VS Code Node.js debugging: Breakpoints](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_breakpoints) -- [Get Started with Debugging JavaScript in Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools/javascript) +- [Node.js debugging in VS Code: Breakpoints](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_breakpoints) +- [Chrome DevTools: Debug JavaScript](https://developers.google.com/web/tools/chrome-devtools/javascript) diff --git a/docs/advanced-features/dynamic-import.md b/docs/advanced-features/dynamic-import.md index 3cb20a86ad0e5..fd92848067e99 100644 --- a/docs/advanced-features/dynamic-import.md +++ b/docs/advanced-features/dynamic-import.md @@ -45,7 +45,7 @@ export default function Page() { You can think of dynamic imports as another way to split your code into manageable chunks. -React components can also be imported using dynamic imports, but in this case we use it in conjunction with `next/dynamic` to make sure it works just like any other React Component. Check out the sections below for more details on how it works. +React components can also be imported using dynamic imports, but in this case we use it in conjunction with `next/dynamic` to make sure it works like any other React Component. Check out the sections below for more details on how it works. ## Basic usage @@ -71,7 +71,7 @@ export default Home `DynamicComponent` will be the default component returned by `../components/hello`. It works like a regular React Component, and you can pass props to it as you normally would. -> **Note**: `import()` needs to be explicitly written without template strings. Furthermore the `import()` has to be inside the `dynamic()` call for Next.js to be able to match webpack bundles / module ids to the specific `dynamic()` call and preload them before rendering. `dynamic()` can't be used inside of React rendering as it needs to be marked in the top level of the module for preloading to work, similar to `React.lazy`. +> **Note**: In `import('path/to/component')`, the path must be explicitly written. It can't be a template string nor a variable. Furthermore the `import()` has to be inside the `dynamic()` call for Next.js to be able to match webpack bundles / module ids to the specific `dynamic()` call and preload them before rendering. `dynamic()` can't be used inside of React rendering as it needs to be marked in the top level of the module for preloading to work, similar to `React.lazy`. ## With named exports @@ -156,3 +156,25 @@ function Home() { export default Home ``` + +## With suspense + +Option `suspense` allows you to lazy-load a component, similar to `React.lazy` and `` with React 18. Note that it only works on client-side or server-side with `fallback`. Full SSR support in concurrent mode is still a work-in-progress. + +```jsx +import dynamic from 'next/dynamic' + +const DynamicLazyComponent = dynamic(() => import('../components/hello4'), { + suspense: true, +}) + +function Home() { + return ( +
+ + + +
+ ) +} +``` diff --git a/docs/advanced-features/i18n-routing.md b/docs/advanced-features/i18n-routing.md index 0564c2421b2d4..219a9956e5aa1 100644 --- a/docs/advanced-features/i18n-routing.md +++ b/docs/advanced-features/i18n-routing.md @@ -13,7 +13,7 @@ description: Next.js has built-in support for internationalized routing and lang Next.js has built-in support for internationalized ([i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization#Naming)) routing since `v10.0.0`. You can provide a list of locales, the default locale, and domain-specific locales and Next.js will automatically handle the routing. -The i18n routing support is currently meant to complement existing i18n library solutions like `react-intl`, `react-i18next`, `lingui`, `rosetta`, and others by streamlining the routes and locale parsing. +The i18n routing support is currently meant to complement existing i18n library solutions like [`react-intl`](https://formatjs.io/docs/getting-started/installation), [`react-i18next`](https://react.i18next.com/), [`lingui`](https://lingui.js.org/), [`rosetta`](https://github.com/lukeed/rosetta), [`next-intl`](https://github.com/amannn/next-intl) and others by streamlining the routes and locale parsing. ## Getting started @@ -52,6 +52,9 @@ module.exports = { { domain: 'example.fr', defaultLocale: 'fr', + // an optional http field can also be used to test + // locale domains locally with http instead of https + http: true, }, ], }, @@ -217,9 +220,9 @@ export default function IndexPage(props) { ## Leveraging the NEXT_LOCALE cookie -Next.js supports overriding the accept-language header with a `NEXT_LOCALE=the-locale` cookie. This cookie can be set using a language switcher and then when a user comes back to the site it will leverage the locale specified in the cookie. +Next.js supports overriding the accept-language header with a `NEXT_LOCALE=the-locale` cookie. This cookie can be set using a language switcher and then when a user comes back to the site it will leverage the locale specified in the cookie when redirecting from `/` to the correct locale location. -For example, if a user prefers the locale `fr` but a `NEXT_LOCALE=en` cookie is set the `en` locale will be used instead until the cookie is removed or expired. +For example, if a user prefers the locale `fr` in their accept-language header but a `NEXT_LOCALE=en` cookie is set the `en` locale when visiting `/` the user will be redirected to the `en` locale location until the cookie is removed or expired. ## Search Engine Optimization @@ -231,6 +234,30 @@ Next.js doesn't know about variants of a page so it's up to you to add the `href > Note that Internationalized Routing does not integrate with [`next export`](/docs/advanced-features/static-html-export.md) as `next export` does not leverage the Next.js routing layer. Hybrid Next.js applications that do not use `next export` are fully supported. +### Dynamic Routes and `getStaticProps` Pages + +For pages using `getStaticProps` with [Dynamic Routes](/docs/routing/dynamic-routes.md), all locale variants of the page desired to be prerendered need to be returned from [`getStaticPaths`](/docs/basic-features/data-fetching.md#getstaticpaths-static-generation). Along with the `params` object returned for `paths`, you can also return a `locale` field specifying which locale you want to render. For example: + +```js +// pages/blog/[slug].js +export const getStaticPaths = ({ locales }) => { + return { + paths: [ + // if no `locale` is provided only the defaultLocale will be generated + { params: { slug: 'post-1' }, locale: 'en-US' }, + { params: { slug: 'post-1' }, locale: 'fr' }, + ], + fallback: true, + } +} +``` + +For [Automatically Statically Optimized](/docs/advanced-features/automatic-static-optimization.md) and non-dynamic `getStaticProps` pages, **a version of the page will be generated for each locale**. This is important to consider because it can increase build times depending on how many locales are configured inside `getStaticProps`. + +For example, if you have 50 locales configured with 10 non-dynamic pages using `getStaticProps`, this means `getStaticProps` will be called 500 times. 50 versions of the 10 pages will be generated during each build. + +To decrease the build time of dynamic pages with `getStaticProps`, use a [`fallback` mode](https://nextjs.org/docs/basic-features/data-fetching#fallback-true). This allows you to return only the most popular paths and locales from `getStaticPaths` for prerendering during the build. Then, Next.js will build the remaining pages at runtime as they are requested. + ### Automatically Statically Optimized Pages For pages that are [automatically statically optimized](/docs/advanced-features/automatic-static-optimization.md), a version of the page will be generated for each locale. @@ -262,19 +289,9 @@ export async function getStaticProps({ locale }) { } ``` -### Dynamic getStaticProps Pages +## Limits for the i18n config -For dynamic `getStaticProps` pages, any locale variants of the page that is desired to be prerendered needs to be returned from [`getStaticPaths`](/docs/basic-features/data-fetching.md#getstaticpaths-static-generation). Along with the `params` object that can be returned for the `paths`, you can also return a `locale` field specifying which locale you want to render. For example: +- `locales`: 100 total locales +- `domains`: 100 total locale domain items -```js -// pages/blog/[slug].js -export const getStaticPaths = ({ locales }) => { - return { - paths: [ - { params: { slug: 'post-1' }, locale: 'en-US' }, - { params: { slug: 'post-1' }, locale: 'fr' }, - ], - fallback: true, - } -} -``` +> **Note:** These limits have been added initially to prevent potential [performance issues at build time](#dynamic-routes-and-getStaticProps-pages). We are continuing to evaluate if these limits are sufficient. diff --git a/docs/advanced-features/measuring-performance.md b/docs/advanced-features/measuring-performance.md index 329cddd452cf7..789d94bb8759c 100644 --- a/docs/advanced-features/measuring-performance.md +++ b/docs/advanced-features/measuring-performance.md @@ -175,7 +175,7 @@ export function reportWebVitals(metric) { > } > ``` > -> Read more about sending results to Google Analytics [here](https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics). +> Read more about [sending results to Google Analytics](https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics). ## TypeScript diff --git a/docs/advanced-features/module-path-aliases.md b/docs/advanced-features/module-path-aliases.md index 12cd2215871cf..8015f82667cd0 100644 --- a/docs/advanced-features/module-path-aliases.md +++ b/docs/advanced-features/module-path-aliases.md @@ -15,6 +15,8 @@ Next.js automatically supports the `tsconfig.json` and `jsconfig.json` `"paths"` > Note: `jsconfig.json` can be used when you don't use TypeScript +> Note: you need to restart dev server to reflect modifications done in `tsconfig.json` / `jsconfig.json` + These options allow you to configure module aliases, for example a common pattern is aliasing certain directories to use absolute paths. One useful feature of these options is that they integrate automatically into certain editors, for example vscode. diff --git a/docs/advanced-features/multi-zones.md b/docs/advanced-features/multi-zones.md index 1fce98d049e58..7d288941b0cb9 100644 --- a/docs/advanced-features/multi-zones.md +++ b/docs/advanced-features/multi-zones.md @@ -18,7 +18,7 @@ With multi zones support, you can merge both these apps into a single one allowi ## How to define a zone -There are no special zones related APIs. You only need to do following: +There are no zone related APIs. You only need to do the following: - Make sure to keep only the pages you need in your app, meaning that an app can't have pages from another app, if app `A` has `/blog` then app `B` shouldn't have it too. - Make sure to configure a [basePath](/docs/api-reference/next.config.js/basepath.md) to avoid conflicts with pages and static files. diff --git a/docs/advanced-features/preview-mode.md b/docs/advanced-features/preview-mode.md index 465d4354e48fc..98d1ae6698cb8 100644 --- a/docs/advanced-features/preview-mode.md +++ b/docs/advanced-features/preview-mode.md @@ -9,18 +9,19 @@ description: Next.js has the preview mode for statically generated pages. You ca
Examples
@@ -221,6 +222,8 @@ export default function myApiRoute(req, res) { Both the bypass cookie value and the private key for encrypting the `previewData` change when `next build` is completed. This ensures that the bypass cookie can’t be guessed. +> **Note:** To test Preview Mode locally over HTTP your browser will need to allow third-party cookies and local storage access. + ## Learn more The following pages might also be useful. diff --git a/docs/advanced-features/security-headers.md b/docs/advanced-features/security-headers.md new file mode 100644 index 0000000000000..c51718bdcb08e --- /dev/null +++ b/docs/advanced-features/security-headers.md @@ -0,0 +1,139 @@ +--- +description: Improve the security of your Next.js application by adding HTTP response headers. +--- + +# Security Headers + +To improve the security of your application, you can use [`headers`](/docs/api-reference/next.config.js/headers.md) in `next.config.js` to apply HTTP response headers to all routes in your application. + +```jsx +// next.config.js + +// You can choose which headers to add to the list +// after learning more below. +const securityHeaders = [] + +module.exports = { + async headers() { + return [ + { + // Apply these headers to all routes in your application. + source: '/(.*)', + headers: securityHeaders, + }, + ] + }, +} +``` + +## Options + +### [X-DNS-Prefetch-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control) + +This header controls DNS prefetching, allowing browsers to proactively perform domain name resolution on external links, images, CSS, JavaScript, and more. This prefetching is performed in the background, so the [DNS](https://developer.mozilla.org/en-US/docs/Glossary/DNS) is more likely to be resolved by the time the referenced items are needed. This reduces latency when the user clicks a link. + +```jsx +{ + key: 'X-DNS-Prefetch-Control', + value: 'on' +} +``` + +### [Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) + +This header informs browsers it should only be accessed using HTTPS, instead of using HTTP. Using the configuration below, all present and future subdomains will use HTTPS for a `max-age` of 2 years. This blocks access to pages or subdomains that can only be served over HTTP. + +If you're deploying to [Vercel](https://vercel.com/docs/edge-network/headers#strict-transport-security), this header is not necessary as it's automatically added to all deployments. + +```jsx +{ + key: 'Strict-Transport-Security', + value: 'max-age=63072000; includeSubDomains; preload' +} +``` + +### [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) + +This header stops pages from loading when they detect reflected cross-site scripting (XSS) attacks. Although this protection is not necessary when sites implement a strong [`Content-Security-Policy`](#content-security-policy) disabling the use of inline JavaScript (`'unsafe-inline'`), it can still provide protection for older web browsers that don't support CSP. + +```jsx +{ + key: 'X-XSS-Protection', + value: '1; mode=block' +} +``` + +### [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) + +This header indicates whether the site should be allowed to be displayed within an `iframe`. This can prevent against clickjacking attacks. This header has been superseded by CSP's `frame-ancestors` option, which has better support in modern browsers. + +```jsx +{ + key: 'X-Frame-Options', + value: 'SAMEORIGIN' +} +``` + +### [Permissions-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy) + +This header allows you to control which features and APIs can be used in the browser. It was previously named `Feature-Policy`. You can view the full list of permission options [here](https://www.w3.org/TR/permissions-policy-1/). + +```jsx +{ + key: 'Permissions-Policy', + value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()' +} +``` + +### [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) + +This header prevents the browser from attempting to guess the type of content if the `Content-Type` header is not explicitly set. This can prevent XSS exploits for websites that allow users to upload and share files. For example, a user trying to download an image, but having it treated as a different `Content-Type` like an executable, which could be malicious. This header also applies to downloading browser extensions. The only valid value for this header is `nosniff`. + +```jsx +{ + key: 'X-Content-Type-Options', + value: 'nosniff' +} +``` + +### [Referrer-Policy](https://scotthelme.co.uk/a-new-security-header-referrer-policy/) + +This header controls how much information the browser includes when navigating from the current website (origin) to another. You can read about the different options [here](https://scotthelme.co.uk/a-new-security-header-referrer-policy/). + +```jsx +{ + key: 'Referrer-Policy', + value: 'origin-when-cross-origin' +} +``` + +### [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) + +This header helps prevent cross-site scripting (XSS), clickjacking and other code injection attacks. Content Security Policy (CSP) can specify allowed origins for content including scripts, stylesheets, images, fonts, objects, media (audio, video), iframes, and more. + +You can read about the many different CSP options [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). + +```jsx +{ + key: 'Content-Security-Policy', + value: // Your CSP Policy +} +``` + +### References + +- [MDN](https://developer.mozilla.org) +- [Varun Naik](https://blog.vnaik.com/posts/web-attacks.html) +- [Scott Helme](https://scotthelme.co.uk) +- [Mozilla Observatory](https://observatory.mozilla.org/) + +## Related + +For more information, we recommend the following sections: + + diff --git a/docs/advanced-features/source-maps.md b/docs/advanced-features/source-maps.md index ce949cbd4822a..aa07ea4cf5982 100644 --- a/docs/advanced-features/source-maps.md +++ b/docs/advanced-features/source-maps.md @@ -4,7 +4,7 @@ description: Enables browser source map generation during the production build. # Source Maps -Source Maps are enabled by default during development. During production builds they are disabled as generation source maps can significantly increase build times and memory usage while being generated. +Source Maps are enabled by default during development. During production builds, they are disabled as generating source maps can significantly increase build times and memory usage while being generated. Next.js provides a configuration flag you can use to enable browser source map generation during the production build: @@ -15,9 +15,9 @@ module.exports = { } ``` -When the `productionBrowserSourceMaps` option is enabled the source maps will be output in the same directory as the JavaScript files, Next.js will automatically serve these files when requested. +When the `productionBrowserSourceMaps` option is enabled, the source maps will be output in the same directory as the JavaScript files. Next.js will automatically serve these files when requested. ## Caveats -- Can increase `next build` time +- Adding source maps can increase `next build` time - Increases memory usage during `next build` diff --git a/docs/advanced-features/static-html-export.md b/docs/advanced-features/static-html-export.md index 4451680a3544f..c217b55d952ac 100644 --- a/docs/advanced-features/static-html-export.md +++ b/docs/advanced-features/static-html-export.md @@ -17,7 +17,7 @@ The exported app supports almost every feature of Next.js, including dynamic rou `next export` works by prerendering all pages to HTML. For [dynamic routes](/docs/routing/dynamic-routes.md), your page can export a [`getStaticPaths`](/docs/basic-features/data-fetching.md#getstaticpaths-static-generation) function to let the exporter know which HTML pages to generate for that route. -> `next export` is intended for scenarios where **none** of your pages have server-side or incremental data requirements (though statically-rendered pages can still [fetch data on the client side](/docs/basic-features/data-fetching.md#fetching-data-on-the-client-side) just fine). +> `next export` is intended for scenarios where **none** of your pages have server-side or incremental data requirements (though statically-rendered pages can still [fetch data on the client side](/docs/basic-features/data-fetching.md#fetching-data-on-the-client-side)). > > If you're looking to make a hybrid site where only _some_ pages are prerendered to static HTML, Next.js already does that automatically for you! Read up on [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) for details. > diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md index bd9959252349e..b79edb5f0bbce 100644 --- a/docs/api-reference/cli.md +++ b/docs/api-reference/cli.md @@ -21,7 +21,7 @@ Usage $ next Available commands - build, start, export, dev, telemetry + build, start, export, dev, lint, telemetry Options --version, -v Version number @@ -46,7 +46,7 @@ NODE_OPTIONS='--inspect' next - **Size** – The number of assets downloaded when navigating to the page client-side. The size for each route only includes its dependencies. - **First Load JS** – The number of assets downloaded when visiting the page from the server. The amount of JS shared by all is shown as a separate metric. -The first load is colored green, yellow, or red. Aim for green for performant applications. +The first load is indicated by green, yellow, or red. Aim for green for performant applications. You can enable production profiling for React with the `--profile` flag in `next build`. This requires [Next.js 9.5](https://nextjs.org/blog/next-9-5): @@ -74,6 +74,20 @@ The application will start at `http://localhost:3000` by default. The default po npx next dev -p 4000 ``` +Or using the `PORT` environment variable: + +```bash +PORT=4000 npx next dev +``` + +> Note: `PORT` can not be set in `.env` as booting up the HTTP server happens before any other code is initialized. + +You can also set the hostname to be different from the default of `0.0.0.0`, this can be useful for making the application available for other devices on the network. The default hostname can be changed with `-H`, like so: + +```bash +npx next dev -H 192.168.1.2 +``` + ## Production `next start` starts the application in production mode. The application should be compiled with [`next build`](#build) first. @@ -84,6 +98,27 @@ The application will start at `http://localhost:3000` by default. The default po npx next start -p 4000 ``` +Or using the `PORT` environment variable: + +```bash +PORT=4000 npx next start +``` + +> Note: `PORT` can not be set in `.env` as booting up the HTTP server happens before any other code is initialized. + +## Lint + +`next lint` runs ESLint for all files in the `pages`, `components`, and `lib` directories. It also +provides a guided setup to install any required dependencies if ESLint is not already configured in +your application. + +If you have other directories that you would like to lint, you can specify them using the `--dir` +flag: + +```bash +next lint --dir utils +``` + ## Telemetry Next.js collects **completely anonymous** telemetry data about general usage. diff --git a/docs/api-reference/create-next-app.md b/docs/api-reference/create-next-app.md index 1b96f94179d20..1192865d09c00 100644 --- a/docs/api-reference/create-next-app.md +++ b/docs/api-reference/create-next-app.md @@ -4,7 +4,7 @@ description: Create Next.js apps in one command with create-next-app. # Create Next App -The easiest way to get started with Next.js is by using `create-next-app`. This simple CLI tool enables you to quickly start building a new Next.js application, with everything set up for you. You can create a new app using the default Next.js template, or by using one of the [official Next.js examples](https://github.com/vercel/next.js/tree/canary/examples). To get started, use the following command: +The easiest way to get started with Next.js is by using `create-next-app`. This CLI tool enables you to quickly start building a new Next.js application, with everything set up for you. You can create a new app using the default Next.js template, or by using one of the [official Next.js examples](https://github.com/vercel/next.js/tree/canary/examples). To get started, use the following command: ```bash npx create-next-app @@ -12,13 +12,22 @@ npx create-next-app yarn create next-app ``` +You can create a [TypeScript project](https://github.com/vercel/next.js/blob/canary/docs/basic-features/typescript.md) with the `--ts, --typescript` flag: + +```bash +npx create-next-app --ts +# or +yarn create next-app --typescript +``` + ### Options `create-next-app` comes with the following options: +- **--ts, --typescript** - Initialize as a TypeScript project. - **-e, --example [name]|[github-url]** - An example to bootstrap the app with. You can use an example name from the [Next.js repo](https://github.com/vercel/next.js/tree/master/examples) or a GitHub URL. The URL can use any branch and/or subdirectory. - **--example-path [path-to-example]** - In a rare case, your GitHub URL might contain a branch name with a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar). In this case, you must specify the path to the example separately: `--example-path foo/bar` -- **--use-npm** - Explicitly tell the CLI to bootstrap the app using npm. Yarn will be used by default if it's installed +- **--use-npm** - Explicitly tell the CLI to bootstrap the app using npm. To bootstrap using yarn we recommend running `yarn create next-app` ### Why use Create Next App? diff --git a/docs/api-reference/next.config.js/basepath.md b/docs/api-reference/next.config.js/basepath.md index 143763f0f16fe..0df919b35a057 100644 --- a/docs/api-reference/next.config.js/basepath.md +++ b/docs/api-reference/next.config.js/basepath.md @@ -4,7 +4,14 @@ description: Learn more about setting a base path in Next.js # Base Path -> This feature was introduced in [Next.js 9.5](https://nextjs.org/blog/next-9-5) and up. If you’re using older versions of Next.js, please upgrade before trying it out. +
+ Version History + +| Version | Changes | +| -------- | ---------------- | +| `v9.5.0` | Base Path added. | + +
To deploy a Next.js application under a sub-path of a domain you can use the `basePath` config option. @@ -22,7 +29,7 @@ Note: this value must be set at build time and can not be changed without re-bui When linking to other pages using `next/link` and `next/router` the `basePath` will be automatically applied. -For example using `/about` will automatically become `/docs/about` when `basePath` is set to `/docs`. +For example, using `/about` will automatically become `/docs/about` when `basePath` is set to `/docs`. ```js export default function HomePage() { @@ -43,3 +50,30 @@ Output html: ``` This makes sure that you don't have to change all links in your application when changing the `basePath` value. + +## Images + +When using the [`next/image`](/docs/api-reference/next/image.md) component, you will need to add the `basePath` in front of `src`. + +For example, using `/docs/me.png` will properly serve your image when `basePath` is set to `/docs`. + +```jsx +import Image from 'next/image' + +function Home() { + return ( + <> +

My Homepage

+ Picture of the author +

Welcome to my homepage!

+ + ) +} + +export default Home +``` diff --git a/docs/api-reference/next.config.js/cdn-support-with-asset-prefix.md b/docs/api-reference/next.config.js/cdn-support-with-asset-prefix.md index 60ebbed5abd2e..20056521d6e78 100644 --- a/docs/api-reference/next.config.js/cdn-support-with-asset-prefix.md +++ b/docs/api-reference/next.config.js/cdn-support-with-asset-prefix.md @@ -24,9 +24,21 @@ module.exports = { } ``` -Next.js will automatically use your asset prefix for the JavaScript and CSS files it loads from the `/_next/` path (`.next/static/` folder). +Next.js will automatically use your asset prefix for the JavaScript and CSS files it loads from the `/_next/` path (`.next/static/` folder). For example, with the above configuration, the following request for a JS chunk: -Asset prefix support does not influence the following paths: +``` +/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js +``` + +Would instead become: + +``` +https://cdn.mydomain.com/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js +``` + +The exact configuration for uploading your files to a given CDN will depend on your CDN of choice. The only folder you need to host on your CDN is the contents of `.next/static/`, which should be uploaded as `_next/static/` as the above URL request indicates. **Do not upload the rest of your `.next/` folder**, as you should not expose your server code and other configuration to the public. + +While `assetPrefix` covers requests to `_next/static`, it does not influence the following paths: - Files in the [public](/docs/basic-features/static-file-serving.md) folder; if you want to serve those assets over a CDN, you'll have to introduce the prefix yourself - `/_next/data/` requests for `getServerSideProps` pages. These requests will always be made against the main domain since they're not static. diff --git a/docs/api-reference/next.config.js/custom-page-extensions.md b/docs/api-reference/next.config.js/custom-page-extensions.md index f457b9083d177..dc11fdc6fac1c 100644 --- a/docs/api-reference/next.config.js/custom-page-extensions.md +++ b/docs/api-reference/next.config.js/custom-page-extensions.md @@ -10,12 +10,41 @@ Open `next.config.js` and add the `pageExtensions` config: ```js module.exports = { - pageExtensions: ['mdx', 'jsx', 'js', 'ts', 'tsx'], + pageExtensions: ['mdx', 'md', 'jsx', 'js', 'tsx', 'ts'], } ``` +> **Note**: The default value of `pageExtensions` is [`['tsx', 'ts', 'jsx', 'js']`](https://github.com/vercel/next.js/blob/f1dbc9260d48c7995f6c52f8fbcc65f08e627992/packages/next/server/config-shared.ts#L161). + > **Note**: configuring `pageExtensions` also affects `_document.js`, `_app.js` as well as files under `pages/api/`. For example, setting `pageExtensions: ['page.tsx', 'page.ts']` means the following files: `_document.tsx`, `_app.tsx`, `pages/users.tsx` and `pages/api/users.ts` will have to be renamed to `_document.page.tsx`, `_app.page.tsx`, `pages/users.page.tsx` and `pages/api/users.page.ts` respectively. +## Including non-page files in the `pages` directory + +To colocate test files, generated files, or other files used by components in the `pages` directory, you can prefix the extensions with something like `page`. + +Open `next.config.js` and add the `pageExtensions` config: + +```js +module.exports = { + pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'], +} +``` + +Then rename your pages to have a file extension that includes `.page` (ex. rename `MyPage.tsx` to `MyPage.page.tsx`). + +> **Note**: Make sure you also rename `_document.js`, `_app.js` as well as files under `pages/api/`. + +Without this config, Next.js assumes every tsx/ts/jsx/js file in the `pages` directory is a page or API route, and may expose unintended routes vulnerable to denial of service attacks, or throw an error like the following when building the production bundle: + +``` +Build error occurred +Error: Build optimization failed: found pages without a React Component as default export in +pages/MyPage.generated +pages/MyPage.test + +See https://nextjs.org/docs/messages/page-without-valid-component for more info. +``` + ## Related
diff --git a/docs/api-reference/next.config.js/custom-webpack-config.md b/docs/api-reference/next.config.js/custom-webpack-config.md index d339d1ae14548..6b4535c7e80f5 100644 --- a/docs/api-reference/next.config.js/custom-webpack-config.md +++ b/docs/api-reference/next.config.js/custom-webpack-config.md @@ -15,7 +15,6 @@ Before continuing to add custom webpack configuration to your application make s Some commonly asked for features are available as plugins: -- [@zeit/next-less](https://github.com/vercel/next-plugins/tree/master/packages/next-less) - [@next/mdx](https://github.com/vercel/next.js/tree/canary/packages/next-mdx) - [@next/bundle-analyzer](https://github.com/vercel/next.js/tree/canary/packages/next-bundle-analyzer) @@ -24,10 +23,6 @@ In order to extend our usage of `webpack`, you can define a function that extend ```js module.exports = { webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => { - // Note: we provide webpack above so you should not `require` it - // Perform customizations to webpack config - config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//)) - // Important: return the modified config return config }, diff --git a/docs/api-reference/next.config.js/disabling-http-keep-alive.md b/docs/api-reference/next.config.js/disabling-http-keep-alive.md new file mode 100644 index 0000000000000..bfa79ad3d8b6d --- /dev/null +++ b/docs/api-reference/next.config.js/disabling-http-keep-alive.md @@ -0,0 +1,36 @@ +--- +description: Next.js will automatically use HTTP Keep-Alive by default. Learn more about how to disable HTTP Keep-Alive here. +--- + +# Disabling HTTP Keep-Alive + +Next.js automatically polyfills [node-fetch](/docs/basic-features/supported-browsers-features#polyfills) and enables [HTTP Keep-Alive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive) by default. You may want to disable HTTP Keep-Alive for certain `fetch()` calls or globally. + +For a single `fetch()` call, you can add the agent option: + +```js +import { Agent } from 'https' + +const url = 'https://example.com' +const agent = new Agent({ keepAlive: false }) +fetch(url, { agent }) +``` + +To override all `fetch()` calls globally, you can use `next.config.js`: + +```js +module.exports = { + httpAgentOptions: { + keepAlive: false, + }, +} +``` + +## Related + + diff --git a/docs/api-reference/next.config.js/exportPathMap.md b/docs/api-reference/next.config.js/exportPathMap.md index 9f93567c721e7..a5e4420ffa112 100644 --- a/docs/api-reference/next.config.js/exportPathMap.md +++ b/docs/api-reference/next.config.js/exportPathMap.md @@ -4,7 +4,7 @@ description: Customize the pages that will be exported as HTML files when using # exportPathMap -> This feature is exclusive of `next export`. Please refer to [Static HTML export](/docs/advanced-features/static-html-export.md) if you want to learn more about it. +> This feature is exclusive to `next export`. Please refer to [Static HTML export](/docs/advanced-features/static-html-export.md) if you want to learn more about it.
Examples @@ -40,6 +40,8 @@ module.exports = { } ``` +Note: the `query` field in `exportPathMap` cannot be used with [automatically statically optimized pages](/docs/advanced-features/automatic-static-optimization) or [`getStaticProps` pages](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) as they are rendered to HTML files at build-time and additional query information cannot be provided during `next export`. + The pages will then be exported as HTML files, for example, `/about` will become `/about.html`. `exportPathMap` is an `async` function that receives 2 arguments: the first one is `defaultPathMap`, which is the default map used by Next.js. The second argument is an object with: diff --git a/docs/api-reference/next.config.js/headers.md b/docs/api-reference/next.config.js/headers.md index 73623b08a2893..d64c01c676b79 100644 --- a/docs/api-reference/next.config.js/headers.md +++ b/docs/api-reference/next.config.js/headers.md @@ -4,8 +4,6 @@ description: Add custom HTTP headers to your Next.js app. # Headers -> This feature was introduced in [Next.js 9.5](https://nextjs.org/blog/next-9-5) and up. If you’re using older versions of Next.js, please upgrade before trying it out. -
Examples
    @@ -13,6 +11,16 @@ description: Add custom HTTP headers to your Next.js app.
+
+ Version History + +| Version | Changes | +| --------- | -------------- | +| `v10.2.0` | `has` added. | +| `v9.5.0` | Headers added. | + +
+ Headers allow you to set custom HTTP headers for an incoming request path. To set custom HTTP headers you can use the `headers` key in `next.config.js`: @@ -43,6 +51,11 @@ module.exports = { - `source` is the incoming request path pattern. - `headers` is an array of header objects with the `key` and `value` properties. +- `basePath`: `false` or `undefined` - if false the basePath won't be included when matching, can be used for external rewrites only. +- `locale`: `false` or `undefined` - whether the locale should not be included when matching. +- `has` is an array of [has objects](#header-cookie-and-query-matching) with the `type`, `key` and `value` properties. + +Headers are checked before the filesystem which includes pages and `/public` files. ## Header Overriding Behavior @@ -149,6 +162,124 @@ module.exports = { } ``` +The following characters `(`, `)`, `{`, `}`, `:`, `*`, `+`, `?` are used for regex path matching, so when used in the `source` as non-special values they must be escaped by adding `\\` before them: + +```js +module.exports = { + async headers() { + return [ + { + // this will match `/english(default)/something` being requested + source: '/english\\(default\\)/:slug', + headers: [ + { + key: 'x-header', + value: 'value', + }, + ], + }, + ] + }, +} +``` + +## Header, Cookie, and Query Matching + +To only apply a header when either header, cookie, or query values also match the `has` field can be used. Both the `source` and all `has` items must match for the header to be applied. + +`has` items have the following fields: + +- `type`: `String` - must be either `header`, `cookie`, `host`, or `query`. +- `key`: `String` - the key from the selected type to match against. +- `value`: `String` or `undefined` - the value to check for, if undefined any value will match. A regex like string can be used to capture a specific part of the value, e.g. if the value `first-(?.*)` is used for `first-second` then `second` will be usable in the destination with `:paramName`. + +```js +module.exports = { + async headers() { + return [ + // if the header `x-add-header` is present, + // the `x-another-header` header will be applied + { + source: '/:path*', + has: [ + { + type: 'header', + key: 'x-add-header', + }, + ], + headers: [ + { + key: 'x-another-header', + value: 'hello', + }, + ], + }, + // if the source, query, and cookie are matched, + // the `x-authorized` header will be applied + { + source: '/specific/:path*', + has: [ + { + type: 'query', + key: 'page', + // the page value will not be available in the + // header key/values since value is provided and + // doesn't use a named capture group e.g. (?home) + value: 'home', + }, + { + type: 'cookie', + key: 'authorized', + value: 'true', + }, + ], + headers: [ + { + key: 'x-authorized', + value: ':authorized', + }, + ], + }, + // if the header `x-authorized` is present and + // contains a matching value, the `x-another-header` will be applied + { + source: '/:path*', + has: [ + { + type: 'header', + key: 'x-authorized', + value: '(?yes|true)', + }, + ], + headers: [ + { + key: 'x-another-header', + value: ':authorized', + }, + ], + }, + // if the host is `example.com`, + // this header will be applied + { + source: '/:path*', + has: [ + { + type: 'host', + value: 'example.com', + }, + ], + headers: [ + { + key: 'x-another-header', + value: ':authorized', + }, + ], + }, + ] + }, +} +``` + ### Headers with basePath support When leveraging [`basePath` support](/docs/api-reference/next.config.js/basepath.md) with headers each `source` is automatically prefixed with the `basePath` unless you add `basePath: false` to the header: @@ -227,6 +358,17 @@ module.exports = { }, ], }, + { + // this gets converted to /(en|fr|de)/(.*) so will not match the top-level + // `/` or `/fr` routes like /:path* would + source: '/(.*)', + headers: [ + { + key: 'x-hello', + value: 'worlld', + }, + ], + }, ] }, } @@ -235,3 +377,14 @@ module.exports = { ### Cache-Control Cache-Control headers set in next.config.js will be overwritten in production to ensure that static assets can be cached effectively. If you need to revalidate the cache of a page that has been [statically generated](https://nextjs.org/docs/basic-features/pages#static-generation-recommended), you can do so by setting `revalidate` in the page's [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) function. + +## Related + +For more information, we recommend the following sections: + + diff --git a/docs/api-reference/next.config.js/ignoring-eslint.md b/docs/api-reference/next.config.js/ignoring-eslint.md new file mode 100644 index 0000000000000..650c678bfd072 --- /dev/null +++ b/docs/api-reference/next.config.js/ignoring-eslint.md @@ -0,0 +1,37 @@ +--- +description: Next.js reports ESLint errors and warnings during builds by default. Learn how to opt-out of this behavior here. +--- + +# Ignoring ESLint + +When ESLint is detected in your project, Next.js fails your **production build** (`next build`) when errors are present. + +If you'd like Next.js to produce production code even when your application has ESLint errors, you can disable the built-in linting step completely. This is not recommended unless you already have ESLint configured to run in a separate part of your workflow (for example, in CI or a pre-commit hook). + +Open `next.config.js` and enable the `ignoreDuringBuilds` option in the `eslint` config: + +```js +module.exports = { + eslint: { + // Warning: This allows production builds to successfully complete even if + // your project has ESLint errors. + ignoreDuringBuilds: true, + }, +} +``` + +## Related + + + + diff --git a/docs/api-reference/next.config.js/introduction.md b/docs/api-reference/next.config.js/introduction.md index a4e531b4ae756..02bf1c5904bbf 100644 --- a/docs/api-reference/next.config.js/introduction.md +++ b/docs/api-reference/next.config.js/introduction.md @@ -11,22 +11,31 @@ For custom advanced behavior of Next.js, you can create a `next.config.js` in th Take a look at the following `next.config.js` example: ```js -module.exports = { +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { /* config options here */ } + +module.exports = nextConfig ``` You can also use a function: ```js module.exports = (phase, { defaultConfig }) => { - return { + /** + * @type {import('next').NextConfig} + */ + const nextConfig = { /* config options here */ } + return nextConfig } ``` -`phase` is the current context in which the configuration is loaded. You can see the available phases [here](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/lib/constants.ts#L1-L4). Phases can be imported from `next/constants`: +`phase` is the current context in which the configuration is loaded. You can see the [available phases](https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/constants.ts#L1-L4). Phases can be imported from `next/constants`: ```js const { PHASE_DEVELOPMENT_SERVER } = require('next/constants') @@ -44,7 +53,7 @@ module.exports = (phase, { defaultConfig }) => { } ``` -The commented lines are the place where you can put the configs allowed by `next.config.js`, which are defined [here](https://github.com/vercel/next.js/blob/canary/packages/next/next-server/server/config.ts#L12-L63). +The commented lines are the place where you can put the configs allowed by `next.config.js`, which are [defined in this file](https://github.com/vercel/next.js/blob/canary/packages/next/server/config-shared.ts#L68). However, none of the configs are required, and it's not necessary to understand what each config does. Instead, search for the features you need to enable or modify in this section and they will show you what to do. diff --git a/docs/api-reference/next.config.js/react-strict-mode.md b/docs/api-reference/next.config.js/react-strict-mode.md index d442b81ac1ad0..b844bf84506fd 100644 --- a/docs/api-reference/next.config.js/react-strict-mode.md +++ b/docs/api-reference/next.config.js/react-strict-mode.md @@ -6,7 +6,9 @@ description: The complete Next.js runtime is now Strict Mode-compliant, learn ho > **Suggested**: We strongly suggest you enable Strict Mode in your Next.js application to better prepare your application for the future of React. -The Next.js runtime is now Strict Mode-compliant. To opt-in to Strict Mode, configure the following option in your `next.config.js`: +React's [Strict Mode](https://reactjs.org/docs/strict-mode.html) is a development mode only feature for highlighting potential problems in an application. It helps to identify unsafe lifecycles, legacy API usage, and a number of other features. + +The Next.js runtime is Strict Mode-compliant. To opt-in to Strict Mode, configure the following option in your `next.config.js`: ```js // next.config.js @@ -15,9 +17,7 @@ module.exports = { } ``` -If you or your team are not ready to use Strict Mode in your entire application, that's OK! You can incrementally migrate on a page-by-page basis [using ``](https://reactjs.org/docs/strict-mode.html). - -React's Strict Mode is a development mode only feature for highlighting potential problems in an application. It helps to identify unsafe lifecycles, legacy API usage, and a number of other features. +If you or your team are not ready to use Strict Mode in your entire application, that's OK! You can incrementally migrate on a page-by-page basis using ``. ## Related diff --git a/docs/api-reference/next.config.js/redirects.md b/docs/api-reference/next.config.js/redirects.md index 4ee91c3fc48f9..a173840875af9 100644 --- a/docs/api-reference/next.config.js/redirects.md +++ b/docs/api-reference/next.config.js/redirects.md @@ -4,8 +4,6 @@ description: Add redirects to your Next.js app. # Redirects -> This feature was introduced in [Next.js 9.5](https://nextjs.org/blog/next-9-5) and up. If you’re using older versions of Next.js, please upgrade before trying it out. -
Examples
    @@ -13,6 +11,16 @@ description: Add redirects to your Next.js app.
+
+ Version History + +| Version | Changes | +| --------- | ---------------- | +| `v10.2.0` | `has` added. | +| `v9.5.0` | Redirects added. | + +
+ Redirects allow you to redirect an incoming request path to a different destination path. Redirects are only available on the Node.js environment and do not affect client-side routing. @@ -38,6 +46,23 @@ module.exports = { - `source` is the incoming request path pattern. - `destination` is the path you want to route to. - `permanent` if the redirect is permanent or not. +- `basePath`: `false` or `undefined` - if false the basePath won't be included when matching, can be used for external rewrites only. +- `locale`: `false` or `undefined` - whether the locale should not be included when matching. +- `has` is an array of [has objects](#header-cookie-and-query-matching) with the `type`, `key` and `value` properties. + +Redirects are checked before the filesystem which includes pages and `/public` files. + +When a redirect is applied, any query values provided in the request will be passed through to the redirect destination. For example, see the following redirect configuration: + +```js +{ + source: '/old-blog/:path*', + destination: '/blog/:path*', + permanent: false +} +``` + +When `/old-blog/post-1?hello=world` is requested, the client will be redirected to `/blog/post-1?hello=world`. ## Path Matching @@ -77,7 +102,7 @@ module.exports = { ### Regex Path Matching -To match a regex path you can wrap the regex in parenthesis after a parameter, for example `/post/:slug(\\d{1,})` will match `/post/123` but not `/post/abc`: +To match a regex path you can wrap the regex in parentheses after a parameter, for example `/post/:slug(\\d{1,})` will match `/post/123` but not `/post/abc`: ```js module.exports = { @@ -93,6 +118,104 @@ module.exports = { } ``` +The following characters `(`, `)`, `{`, `}`, `:`, `*`, `+`, `?` are used for regex path matching, so when used in the `source` as non-special values they must be escaped by adding `\\` before them: + +```js +module.exports = { + async redirects() { + return [ + { + // this will match `/english(default)/something` being requested + source: '/english\\(default\\)/:slug', + destination: '/en-us/:slug', + permanent: false, + }, + ] + }, +} +``` + +## Header, Cookie, and Query Matching + +To only match a redirect when header, cookie, or query values also match the `has` field can be used. Both the `source` and all `has` items must match for the redirect to be applied. + +`has` items have the following fields: + +- `type`: `String` - must be either `header`, `cookie`, `host`, or `query`. +- `key`: `String` - the key from the selected type to match against. +- `value`: `String` or `undefined` - the value to check for, if undefined any value will match. A regex like string can be used to capture a specific part of the value, e.g. if the value `first-(?.*)` is used for `first-second` then `second` will be usable in the destination with `:paramName`. + +```js +module.exports = { + async redirects() { + return [ + // if the header `x-redirect-me` is present, + // this redirect will be applied + { + source: '/:path((?!another-page$).*)', + has: [ + { + type: 'header', + key: 'x-redirect-me', + }, + ], + permanent: false, + destination: '/another-page', + }, + // if the source, query, and cookie are matched, + // this redirect will be applied + { + source: '/specific/:path*', + has: [ + { + type: 'query', + key: 'page', + // the page value will not be available in the + // destination since value is provided and doesn't + // use a named capture group e.g. (?home) + value: 'home', + }, + { + type: 'cookie', + key: 'authorized', + value: 'true', + }, + ], + permanent: false, + destination: '/another/:path*', + }, + // if the header `x-authorized` is present and + // contains a matching value, this redirect will be applied + { + source: '/', + has: [ + { + type: 'header', + key: 'x-authorized', + value: '(?yes|true)', + }, + ], + permanent: false, + destination: '/home?authorized=:authorized', + }, + // if the host is `example.com`, + // this redirect will be applied + { + source: '/:path((?!another-page$).*)', + has: [ + { + type: 'host', + value: 'example.com', + }, + ], + permanent: false, + destination: '/another-page', + }, + ] + }, +} +``` + ### Redirects with basePath support When leveraging [`basePath` support](/docs/api-reference/next.config.js/basepath.md) with redirects each `source` and `destination` is automatically prefixed with the `basePath` unless you add `basePath: false` to the redirect: @@ -152,9 +275,21 @@ module.exports = { locale: false, permanent: false, }, + { + // this gets converted to /(en|fr|de)/(.*) so will not match the top-level + // `/` or `/fr` routes like /:path* would + source: '/(.*)', + destination: '/another', + permanent: false, + }, ] }, } ``` In some rare cases, you might need to assign a custom status code for older HTTP Clients to properly redirect. In these cases, you can use the `statusCode` property instead of the `permanent` property, but not both. Note: to ensure IE11 compatibility a `Refresh` header is automatically added for the 308 status code. + +## Other Redirects + +- Inside [API Routes](/docs/api-routes/response-helpers.md), you can use `res.redirect()`. +- Inside [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) and [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering), you can redirect specific pages at request-time. diff --git a/docs/api-reference/next.config.js/rewrites.md b/docs/api-reference/next.config.js/rewrites.md index a6f4efcfcf364..46c58eb6035dd 100644 --- a/docs/api-reference/next.config.js/rewrites.md +++ b/docs/api-reference/next.config.js/rewrites.md @@ -4,8 +4,6 @@ description: Add rewrites to your Next.js app. # Rewrites -> This feature was introduced in [Next.js 9.5](https://nextjs.org/blog/next-9-5) and up. If you’re using older versions of Next.js, please upgrade before trying it out. -
Examples
    @@ -13,11 +11,19 @@ description: Add rewrites to your Next.js app.
-Rewrites allow you to map an incoming request path to a different destination path. +
+ Version History -Rewrites are only available on the Node.js environment and do not affect client-side routing. +| Version | Changes | +| --------- | --------------- | +| `v10.2.0` | `has` added. | +| `v9.5.0` | Rewrites added. | -Rewrites are not able to override public files or routes in the pages directory as these have higher priority than rewrites. For example, if you have `pages/index.js` you are not able to rewrite `/` to another location unless you rename the `pages/index.js` file. +
+ +Rewrites allow you to map an incoming request path to a different destination path. + +Rewrites act as a URL proxy and mask the destination path, making it appear the user hasn't changed their location on the site. In contrast, [redirects](/docs/api-reference/next.config.js/redirects.md) will reroute to a new page and show the URL changes. To use rewrites you can use the `rewrites` key in `next.config.js`: @@ -34,10 +40,63 @@ module.exports = { } ``` +Rewrites are applied to client-side routing, a `` will have the rewrite applied in the above example. + `rewrites` is an async function that expects an array to be returned holding objects with `source` and `destination` properties: -- `source` is the incoming request path pattern. -- `destination` is the path you want to route to. +- `source`: `String` - is the incoming request path pattern. +- `destination`: `String` is the path you want to route to. +- `basePath`: `false` or `undefined` - if false the basePath won't be included when matching, can be used for external rewrites only. +- `locale`: `false` or `undefined` - whether the locale should not be included when matching. +- `has` is an array of [has objects](#header-cookie-and-query-matching) with the `type`, `key` and `value` properties. + +Rewrites are applied after checking the filesystem (pages and `/public` files) and before dynamic routes by default. This behavior can be changed by instead returning an object instead of an array from the `rewrites` function since `v10.1` of Next.js: + +```js +module.exports = { + async rewrites() { + return { + beforeFiles: [ + // These rewrites are checked after headers/redirects + // and before all files including _next/public files which + // allows overriding page files + { + source: '/some-page', + destination: '/somewhere-else', + has: [{ type: 'query', key: 'overrideMe' }], + }, + ], + afterFiles: [ + // These rewrites are checked after pages/public files + // are checked but before dynamic routes + { + source: '/non-existent', + destination: '/somewhere-else', + }, + ], + fallback: [ + // These rewrites are checked after both pages/public files + // and dynamic routes are checked + { + source: '/:path*', + destination: `https://my-old-site.com/:path*`, + }, + ], + } + }, +} +``` + +Note: rewrites in `beforeFiles` do not check the filesystem/dynamic routes immediately after matching a source, they continue until all `beforeFiles` have been checked. + +The order Next.js routes are checked is: + +1. [headers](/docs/api-reference/next.config.js/headers) are checked/applied +2. [redirects](/docs/api-reference/next.config.js/redirects) are checked/applied +3. `beforeFiles` rewrites are checked/applied +4. static files from the [public directory](/docs/basic-features/static-file-serving), `_next/static` files, and non-dynamic pages are checked/served +5. `afterFiles` rewrites are checked/applied, if one of these rewrites is matched we check dynamic routes/static files after each match +6. `fallback` rewrites are checked/applied, these are applied before rendering the 404 page and after dynamic routes/all static assets have been checked. ## Rewrite parameters @@ -140,6 +199,99 @@ module.exports = { } ``` +The following characters `(`, `)`, `{`, `}`, `:`, `*`, `+`, `?` are used for regex path matching, so when used in the `source` as non-special values they must be escaped by adding `\\` before them: + +```js +module.exports = { + async rewrites() { + return [ + { + // this will match `/english(default)/something` being requested + source: '/english\\(default\\)/:slug', + destination: '/en-us/:slug', + }, + ] + }, +} +``` + +## Header, Cookie, and Query Matching + +To only match a rewrite when header, cookie, or query values also match the `has` field can be used. Both the `source` and all `has` items must match for the rewrite to be applied. + +`has` items have the following fields: + +- `type`: `String` - must be either `header`, `cookie`, `host`, or `query`. +- `key`: `String` - the key from the selected type to match against. +- `value`: `String` or `undefined` - the value to check for, if undefined any value will match. A regex like string can be used to capture a specific part of the value, e.g. if the value `first-(?.*)` is used for `first-second` then `second` will be usable in the destination with `:paramName`. + +```js +module.exports = { + async rewrites() { + return [ + // if the header `x-rewrite-me` is present, + // this rewrite will be applied + { + source: '/:path*', + has: [ + { + type: 'header', + key: 'x-rewrite-me', + }, + ], + destination: '/another-page', + }, + // if the source, query, and cookie are matched, + // this rewrite will be applied + { + source: '/specific/:path*', + has: [ + { + type: 'query', + key: 'page', + // the page value will not be available in the + // destination since value is provided and doesn't + // use a named capture group e.g. (?home) + value: 'home', + }, + { + type: 'cookie', + key: 'authorized', + value: 'true', + }, + ], + destination: '/:path*/home', + }, + // if the header `x-authorized` is present and + // contains a matching value, this rewrite will be applied + { + source: '/:path*', + has: [ + { + type: 'header', + key: 'x-authorized', + value: '(?yes|true)', + }, + ], + destination: '/home?authorized=:authorized', + }, + // if the host is `example.com`, + // this rewrite will be applied + { + source: '/:path*', + has: [ + { + type: 'host', + value: 'example.com', + }, + ], + destination: '/another-page', + }, + ] + }, +} +``` + ## Rewriting to an external URL
@@ -166,29 +318,27 @@ module.exports = { ### Incremental adoption of Next.js -You can also make Next.js check the application routes before falling back to proxying to the previous website. +You can also have Next.js fall back to proxying to an existing website after checking all Next.js routes. This way you don't have to change the rewrites configuration when migrating more pages to Next.js ```js module.exports = { async rewrites() { - return [ - // we need to define a no-op rewrite to trigger checking - // all pages/static files before we attempt proxying - { - source: '/:path*', - destination: '/:path*', - }, - { - source: '/:path*', - destination: `https://custom-routes-proxying-endpoint.vercel.app/:path*`, - }, - ] + return { + fallback: [ + { + source: '/:path*', + destination: `https://custom-routes-proxying-endpoint.vercel.app/:path*`, + }, + ], + } }, } ``` +See additional information on incremental adoption [in the docs here](/docs/migrating/incremental-adoption.md). + ### Rewrites with basePath support When leveraging [`basePath` support](/docs/api-reference/next.config.js/basepath.md) with rewrites each `source` and `destination` is automatically prefixed with the `basePath` unless you add `basePath: false` to the rewrite: @@ -244,6 +394,12 @@ module.exports = { destination: '/en/another', locale: false, }, + { + // this gets converted to /(en|fr|de)/(.*) so will not match the top-level + // `/` or `/fr` routes like /:path* would + source: '/(.*)', + destination: '/another', + }, ] }, } diff --git a/docs/api-reference/next.config.js/trailing-slash.md b/docs/api-reference/next.config.js/trailing-slash.md index 4fad79dfcd24a..ee2757f872ccc 100644 --- a/docs/api-reference/next.config.js/trailing-slash.md +++ b/docs/api-reference/next.config.js/trailing-slash.md @@ -4,7 +4,14 @@ description: Configure Next.js pages to resolve with or without a trailing slash # Trailing Slash -> This feature was introduced in [Next.js 9.5](https://nextjs.org/blog/next-9-5) and up. If you’re using older versions of Next.js, please upgrade before trying it out. +
+ Version History + +| Version | Changes | +| -------- | --------------------- | +| `v9.5.0` | Trailing Slash added. | + +
By default Next.js will redirect urls with trailing slashes to their counterpart without a trailing slash. For example `/about/` will redirect to `/about`. You can configure this behavior to act the opposite way, where urls without trailing slashes are redirected to their counterparts with trailing slashes. diff --git a/docs/api-reference/next/amp.md b/docs/api-reference/next/amp.md index 2e58efb82dcee..6cc212791168d 100644 --- a/docs/api-reference/next/amp.md +++ b/docs/api-reference/next/amp.md @@ -11,7 +11,7 @@ description: Enable AMP in a page, and control the way Next.js adds AMP to the p
-> AMP support is one of our advanced features, you can read more about it [here](/docs/advanced-features/amp-support/introduction.md). +> AMP support is one of our advanced features, you can [read more about AMP here](/docs/advanced-features/amp-support/introduction.md). To enable AMP, add the following config to your page: @@ -44,7 +44,7 @@ The page above is an AMP-only page, which means: - The page has no Next.js or React client-side runtime - The page is automatically optimized with [AMP Optimizer](https://github.com/ampproject/amp-toolbox/tree/master/packages/optimizer), an optimizer that applies the same transformations as AMP caches (improves performance by up to 42%) -- The page has an user-accessible (optimized) version of the page and a search-engine indexable (unoptimized) version of the page +- The page has a user-accessible (optimized) version of the page and a search-engine indexable (unoptimized) version of the page ## Hybrid AMP Page diff --git a/docs/api-reference/next/head.md b/docs/api-reference/next/head.md index b642c18cd5761..96a6d14a5ea31 100644 --- a/docs/api-reference/next/head.md +++ b/docs/api-reference/next/head.md @@ -55,9 +55,11 @@ function IndexPage() { export default IndexPage ``` -In this case only the second `` is rendered. `meta` tags with duplicate `name` attributes are automatically handled. +In this case only the second `` is rendered. `meta` tags with duplicate `key` attributes are automatically handled. > The contents of `head` get cleared upon unmounting the component, so make sure each page completely defines what it needs in `head`, without making assumptions about what other pages added. `title`, `meta` or any other elements (e.g. `script`) need to be contained as **direct** children of the `Head` element, or wrapped into maximum one level of `` or arrays—otherwise the tags won't be correctly picked up on client-side navigations. + +> We recommend using [next/script](/docs/basic-features/script.md) in your component instead of manually creating a ` + +// or + + + + + ) +} +``` + +## Useful links + +- [Docs for Next.js Script component](https://nextjs.org/docs/basic-features/script) diff --git a/errors/invalid-api-status-body.md b/errors/invalid-api-status-body.md new file mode 100644 index 0000000000000..b0d5fd60fd99d --- /dev/null +++ b/errors/invalid-api-status-body.md @@ -0,0 +1,32 @@ +Invalid API Route Status/Body Response + +#### Why This Error Occurred + +In one of your API routes a 204 or 304 status code was used as well as sending a response body. + +This is invalid as a 204 or 304 status code dictates no response body should be present. + +#### Possible Ways to Fix It + +Send an empty body when using a 204 or 304 status code or use a different status code while sending a response body. + +Before + +```js +export default function handler(req, res) { + res.status(204).send('invalid body') +} +``` + +After + +```js +export default function handler(req, res) { + res.status(204).send() +} +``` + +### Useful Links + +- [204 status code documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204) +- [304 status code documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304) diff --git a/errors/invalid-dynamic-suspense.md b/errors/invalid-dynamic-suspense.md new file mode 100644 index 0000000000000..11e4d6134d732 --- /dev/null +++ b/errors/invalid-dynamic-suspense.md @@ -0,0 +1,13 @@ +# Invalid Usage of `suspense` Option of `next/dynamic` + +#### Why This Error Occurred + +`` is not allowed under legacy render mode when using React older than v18. + +#### Possible Ways to Fix It + +Remove `suspense: true` option in `next/dynamic` usages. + +### Useful Links + +- [Dynamic Import Suspense Usage](https://nextjs.org/docs/advanced-features/dynamic-import#with-suspense) diff --git a/errors/invalid-getstaticprops-value.md b/errors/invalid-getstaticprops-value.md index ccbfd7232d077..16fcf6a9df158 100644 --- a/errors/invalid-getstaticprops-value.md +++ b/errors/invalid-getstaticprops-value.md @@ -12,7 +12,7 @@ Make sure to return the following shape from `getStaticProps`: export async function getStaticProps(ctx: { params?: ParsedUrlQuery preview?: boolean - previewData?: any + previewData?: PreviewData }) { return { props: { [key: string]: any } diff --git a/errors/invalid-i18n-config.md b/errors/invalid-i18n-config.md index 1a1be32af8839..b840d14a61f98 100644 --- a/errors/invalid-i18n-config.md +++ b/errors/invalid-i18n-config.md @@ -2,11 +2,11 @@ #### Why This Error -In your `next.config.js` file you provided an invalid config for the `i18n` field. +In your `next.config.js` file you provided an invalid config for the `i18n` field. This could mean the limit for 100 locale items was exceeded. #### Possible Ways to Fix It -Make sure your `i18n` field follows the allowed config shape and values: +Make sure your `i18n` field follows the allowed config shape, limits, and values: ```js module.exports = { diff --git a/errors/invalid-images-config.md b/errors/invalid-images-config.md index c060c62728da3..febbc5ee4adad 100644 --- a/errors/invalid-images-config.md +++ b/errors/invalid-images-config.md @@ -18,8 +18,12 @@ module.exports = { // limit of 50 domains values domains: [], path: '/_next/image', - // loader can be 'default', 'imgix', 'cloudinary', or 'akamai' + // loader can be 'default', 'imgix', 'cloudinary', 'akamai', or 'custom' loader: 'default', + // disable static imports for image files + disableStaticImages: false, + // minimumCacheTTL is in seconds, must be integer 0 or more + minimumCacheTTL: 60, }, } ``` diff --git a/errors/link-multiple-children.md b/errors/link-multiple-children.md new file mode 100644 index 0000000000000..54476589c6900 --- /dev/null +++ b/errors/link-multiple-children.md @@ -0,0 +1,36 @@ +# Multiple children were passed to + +#### Why This Error Occurred + +In your application code multiple children were passed to `next/link` but only one child is supported: + +For example: + +```js +import Link from 'next/link' + +export default function Home() { + return ( + + To About + Second To About + + ) +} +``` + +#### Possible Ways to Fix It + +Make sure only one child is used when using ``: + +```js +import Link from 'next/link' + +export default function Home() { + return ( + + To About + + ) +} +``` diff --git a/errors/link-passhref.md b/errors/link-passhref.md new file mode 100644 index 0000000000000..33fec658066e0 --- /dev/null +++ b/errors/link-passhref.md @@ -0,0 +1,32 @@ +# Link passHref + +### Why This Error Occurred + +`passHref` was not used for a `Link` component that wraps a custom component. This is needed in order to pass the `href` to the child `` tag. + +### Possible Ways to Fix It + +If you're using a custom component that wraps an `` tag, make sure to add `passHref`: + +```jsx +import Link from 'next/link' +import styled from 'styled-components' + +const StyledLink = styled.a` + color: red; +` + +function NavLink({ href, name }) { + return ( + + {name} + + ) +} + +export default NavLink +``` + +### Useful Links + +- [next/link - Custom Component](https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-custom-component-that-wraps-an-a-tag) diff --git a/errors/manifest.json b/errors/manifest.json new file mode 100644 index 0000000000000..e1fcda26a76fb --- /dev/null +++ b/errors/manifest.json @@ -0,0 +1,477 @@ +{ + "routes": [ + { + "title": "Messages", + "heading": true, + "routes": [ + { + "title": "custom-document-image-import", + "path": "/errors/custom-document-image-import.md" + }, + { + "title": "404-get-initial-props", + "path": "/errors/404-get-initial-props.md" + }, + { "title": "amp-bind-jsx-alt", "path": "/errors/amp-bind-jsx-alt.md" }, + { + "title": "amp-export-validation", + "path": "/errors/amp-export-validation.md" + }, + { + "title": "api-routes-body-size-limit", + "path": "/errors/api-routes-body-size-limit.md" + }, + { + "title": "api-routes-static-export", + "path": "/errors/api-routes-static-export.md" + }, + { + "title": "app-container-deprecated", + "path": "/errors/app-container-deprecated.md" + }, + { + "title": "build-dir-not-writeable", + "path": "/errors/build-dir-not-writeable.md" + }, + { + "title": "built-in-css-disabled", + "path": "/errors/built-in-css-disabled.md" + }, + { + "title": "can-not-output-to-public", + "path": "/errors/can-not-output-to-public.md" + }, + { + "title": "can-not-output-to-static", + "path": "/errors/can-not-output-to-static.md" + }, + { + "title": "cant-override-next-props", + "path": "/errors/cant-override-next-props.md" + }, + { + "title": "circular-structure", + "path": "/errors/circular-structure.md" + }, + { + "title": "config-resolve-alias", + "path": "/errors/config-resolve-alias.md" + }, + { + "title": "conflicting-amp-tag", + "path": "/errors/conflicting-amp-tag.md" + }, + { + "title": "conflicting-public-file-page", + "path": "/errors/conflicting-public-file-page.md" + }, + { + "title": "conflicting-ssg-paths", + "path": "/errors/conflicting-ssg-paths.md" + }, + { "title": "css-global", "path": "/errors/css-global.md" }, + { "title": "css-modules-npm", "path": "/errors/css-modules-npm.md" }, + { "title": "css-npm", "path": "/errors/css-npm.md" }, + { + "title": "custom-error-no-custom-404", + "path": "/errors/custom-error-no-custom-404.md" + }, + { + "title": "doc-crossorigin-deprecated", + "path": "/errors/doc-crossorigin-deprecated.md" + }, + { "title": "duplicate-sass", "path": "/errors/duplicate-sass.md" }, + { + "title": "empty-configuration", + "path": "/errors/empty-configuration.md" + }, + { + "title": "empty-object-getInitialProps", + "path": "/errors/empty-object-getInitialProps.md" + }, + { + "title": "env-key-not-allowed", + "path": "/errors/env-key-not-allowed.md" + }, + { + "title": "env-loading-disabled", + "path": "/errors/env-loading-disabled.md" + }, + { + "title": "export-all-in-page", + "path": "/errors/export-all-in-page.md" + }, + { "title": "export-image-api", "path": "/errors/export-image-api.md" }, + { + "title": "export-no-custom-routes", + "path": "/errors/export-no-custom-routes.md" + }, + { + "title": "export-path-mismatch", + "path": "/errors/export-path-mismatch.md" + }, + { + "title": "generatebuildid-not-a-string", + "path": "/errors/generatebuildid-not-a-string.md" + }, + { + "title": "google-font-display", + "path": "/errors/google-font-display.md" + }, + { + "title": "google-font-preconnect", + "path": "/errors/google-font-preconnect.md" + }, + { + "title": "get-initial-props-as-an-instance-method", + "path": "/errors/get-initial-props-as-an-instance-method.md" + }, + { + "title": "gsp-redirect-during-prerender", + "path": "/errors/gsp-redirect-during-prerender.md" + }, + { + "title": "gssp-component-member", + "path": "/errors/gssp-component-member.md" + }, + { "title": "gssp-export", "path": "/errors/gssp-export.md" }, + { + "title": "gssp-mixed-not-found-redirect", + "path": "/errors/gssp-mixed-not-found-redirect.md" + }, + { + "title": "gssp-no-mutating-res", + "path": "/errors/gssp-no-mutating-res.md" + }, + { "title": "head-build-id", "path": "/errors/head-build-id.md" }, + { + "title": "href-interpolation-failed", + "path": "/errors/href-interpolation-failed.md" + }, + { "title": "improper-devtool", "path": "/errors/improper-devtool.md" }, + { + "title": "incompatible-href-as", + "path": "/errors/incompatible-href-as.md" + }, + { + "title": "inline-script-id", + "path": "/errors/inline-script-id.md" + }, + { "title": "install-sass", "path": "/errors/install-sass.md" }, + { "title": "install-sharp", "path": "/errors/install-sharp.md" }, + { + "title": "invalid-assetprefix", + "path": "/errors/invalid-assetprefix.md" + }, + { + "title": "invalid-dynamic-suspense", + "path": "/errors/invalid-dynamic-suspense.md" + }, + { + "title": "invalid-external-rewrite", + "path": "/errors/invalid-external-rewrite.md" + }, + { + "title": "invalid-getstaticpaths-value", + "path": "/errors/invalid-getstaticpaths-value.md" + }, + { + "title": "invalid-getstaticprops-value", + "path": "/errors/invalid-getstaticprops-value.md" + }, + { + "title": "invalid-href-passed", + "path": "/errors/invalid-href-passed.md" + }, + { + "title": "invalid-i18n-config", + "path": "/errors/invalid-i18n-config.md" + }, + { + "title": "invalid-images-config", + "path": "/errors/invalid-images-config.md" + }, + { + "title": "invalid-multi-match", + "path": "/errors/invalid-multi-match.md" + }, + { + "title": "invalid-page-config", + "path": "/errors/invalid-page-config.md" + }, + { + "title": "invalid-react-version", + "path": "/errors/invalid-react-version.md" + }, + { + "title": "invalid-redirect-gssp", + "path": "/errors/invalid-redirect-gssp.md" + }, + { + "title": "invalid-relative-url-external-as", + "path": "/errors/invalid-relative-url-external-as.md" + }, + { + "title": "invalid-resolve-alias", + "path": "/errors/invalid-resolve-alias.md" + }, + { + "title": "invalid-route-source", + "path": "/errors/invalid-route-source.md" + }, + { + "title": "invalid-server-options", + "path": "/errors/invalid-server-options.md" + }, + { + "title": "invalid-webpack-5-version", + "path": "/errors/invalid-webpack-5-version.md" + }, + { + "title": "link-passhref", + "path": "/errors/link-passhref.md" + }, + { "title": "manifest.json", "path": "/errors/manifest.json" }, + { + "title": "minification-disabled", + "path": "/errors/minification-disabled.md" + }, + { + "title": "missing-document-component", + "path": "/errors/missing-document-component.md" + }, + { + "title": "missing-env-value", + "path": "/errors/missing-env-value.md" + }, + { "title": "multi-tabs", "path": "/errors/multi-tabs.md" }, + { + "title": "nested-reserved-page", + "path": "/errors/nested-reserved-page.md" + }, + { + "title": "next-dynamic-modules", + "path": "/errors/next-dynamic-modules.md" + }, + { + "title": "next-export-no-build-id", + "path": "/errors/next-export-no-build-id.md" + }, + { + "title": "next-export-serverless", + "path": "/errors/next-export-serverless.md" + }, + { + "title": "next-head-count-missing", + "path": "/errors/next-head-count-missing.md" + }, + { + "title": "next-image-missing-loader", + "path": "/errors/next-image-missing-loader.md" + }, + { + "title": "next-image-missing-loader-width", + "path": "/errors/next-image-missing-loader-width.md" + }, + { + "title": "next-image-unconfigured-host", + "path": "/errors/next-image-unconfigured-host.md" + }, + { + "title": "next-script-for-ga", + "path": "/errors/next-script-for-ga.md" + }, + { + "title": "next-start-serverless", + "path": "/errors/next-start-serverless.md" + }, + { "title": "no-cache", "path": "/errors/no-cache.md" }, + { "title": "no-css-tags", "path": "/errors/no-css-tags.md" }, + { + "title": "no-document-import-in-page", + "path": "/errors/no-document-import-in-page.md" + }, + { + "title": "no-document-title", + "path": "/errors/no-document-title.md" + }, + { + "title": "no-document-viewport-meta", + "path": "/errors/no-document-viewport-meta.md" + }, + { + "title": "no-duplicate-head", + "path": "/errors/no-duplicate-head.md" + }, + { + "title": "no-head-import-in-document", + "path": "/errors/no-head-import-in-document.md" + }, + { + "title": "no-html-link-for-pages", + "path": "/errors/no-html-link-for-pages.md" + }, + { + "title": "no-on-app-updated-hook", + "path": "/errors/no-on-app-updated-hook.md" + }, + { + "title": "no-page-custom-font", + "path": "/errors/no-page-custom-font.md" + }, + { + "title": "no-router-instance", + "path": "/errors/no-router-instance.md" + }, + { + "title": "no-sync-scripts", + "path": "/errors/no-sync-scripts.md" + }, + { + "title": "no-title-in-document-head", + "path": "/errors/no-title-in-document-head.md" + }, + { + "title": "no-unwanted-polyfillio", + "path": "/errors/no-unwanted-polyfillio.md" + }, + { + "title": "non-standard-node-env", + "path": "/errors/non-standard-node-env.md" + }, + { + "title": "opt-out-auto-static-optimization", + "path": "/errors/opt-out-auto-static-optimization.md" + }, + { + "title": "opt-out-automatic-prerendering", + "path": "/errors/opt-out-automatic-prerendering.md" + }, + { + "title": "page-without-valid-component", + "path": "/errors/page-without-valid-component.md" + }, + { + "title": "popstate-state-empty", + "path": "/errors/popstate-state-empty.md" + }, + { "title": "postcss-function", "path": "/errors/postcss-function.md" }, + { + "title": "postcss-ignored-plugin", + "path": "/errors/postcss-ignored-plugin.md" + }, + { "title": "postcss-shape", "path": "/errors/postcss-shape.md" }, + { + "title": "prefetch-true-deprecated", + "path": "/errors/prefetch-true-deprecated.md" + }, + { "title": "prerender-error", "path": "/errors/prerender-error.md" }, + { + "title": "production-start-no-build-id", + "path": "/errors/production-start-no-build-id.md" + }, + { + "title": "promise-in-next-config", + "path": "/errors/promise-in-next-config.md" + }, + { + "title": "public-next-folder-conflict", + "path": "/errors/public-next-folder-conflict.md" + }, + { "title": "react-version", "path": "/errors/react-version.md" }, + { + "title": "render-no-starting-slash", + "path": "/errors/render-no-starting-slash.md" + }, + { + "title": "reserved-page-prop", + "path": "/errors/reserved-page-prop.md" + }, + { + "title": "rewrite-auto-export-fallback", + "path": "/errors/rewrite-auto-export-fallback.md" + }, + { + "title": "routes-must-be-array", + "path": "/errors/routes-must-be-array.md" + }, + { + "title": "ssg-fallback-true-export", + "path": "/errors/ssg-fallback-true-export.md" + }, + { + "title": "static-dir-deprecated", + "path": "/errors/static-dir-deprecated.md" + }, + { "title": "threw-undefined", "path": "/errors/threw-undefined.md" }, + { + "title": "undefined-webpack-config", + "path": "/errors/undefined-webpack-config.md" + }, + { "title": "url-deprecated", "path": "/errors/url-deprecated.md" }, + { "title": "webpack5", "path": "/errors/webpack5.md" }, + { + "title": "client-side-exception-occurred", + "path": "/errors/client-side-exception-occurred.md" + }, + { + "title": "future-webpack5-moved-to-webpack5", + "path": "/errors/future-webpack5-moved-to-webpack5.md" + }, + { + "title": "link-multiple-children", + "path": "/errors/link-multiple-children.md" + }, + { "title": "no-img-element", "path": "/errors/no-img-element.md" }, + { + "title": "non-dynamic-getstaticpaths-usage", + "path": "/errors/non-dynamic-getstaticpaths-usage.md" + }, + { + "title": "placeholder-blur-data-url", + "path": "/errors/placeholder-blur-data-url.md" + }, + { + "title": "import-esm-externals", + "path": "/errors/import-esm-externals.md" + }, + { + "title": "static-page-generation-timeout", + "path": "/errors/static-page-generation-timeout.md" + }, + { + "title": "page-data-collection-timeout", + "path": "/errors/page-data-collection-timeout.md" + }, + { + "title": "sharp-missing-in-production", + "path": "/errors/sharp-missing-in-production.md" + }, + { + "title": "script-in-document-page", + "path": "/errors/no-script-in-document-page.md" + }, + { + "title": "script-in-head-component", + "path": "/errors/no-script-in-head-component.md" + }, + { + "title": "max-custom-routes-reached", + "path": "/errors/max-custom-routes-reached.md" + }, + { + "title": "module-not-found", + "path": "/errors/module-not-found.md" + }, + { + "title": "next-config-error", + "path": "/errors/next-config-error.md" + }, + { + "title": "invalid-api-status-body", + "path": "/errors/invalid-api-status-body.md" + } + ] + } + ] +} diff --git a/errors/max-custom-routes-reached.md b/errors/max-custom-routes-reached.md new file mode 100644 index 0000000000000..94398ff987774 --- /dev/null +++ b/errors/max-custom-routes-reached.md @@ -0,0 +1,17 @@ +# Max Custom Routes Reached + +#### Why This Error Occurred + +The number of combined routes from `headers`, `redirects`, and `rewrites` exceeds 1000. This can impact performance because each request will iterate over all routes to check for a match in the worst case. + +#### Possible Ways to Fix It + +- Leverage dynamic routes inside of the `pages` folder to reduce the number of rewrites needed +- Combine headers routes into dynamic matches e.g. `/first-header-route` `/second-header-route` -> `/(first-header-route$|second-header-route$)` + +### Useful Links + +- [Dynamic Routes documentation](https://nextjs.org/docs/routing/dynamic-routes) +- [Rewrites documentation](https://nextjs.org/docs/api-reference/next.config.js/rewrites) +- [Redirects documentation](https://nextjs.org/docs/api-reference/next.config.js/redirects) +- [Headers documentation](https://nextjs.org/docs/api-reference/next.config.js/headers) diff --git a/errors/module-not-found.md b/errors/module-not-found.md new file mode 100644 index 0000000000000..5cd6401e0e135 --- /dev/null +++ b/errors/module-not-found.md @@ -0,0 +1,151 @@ +# Module Not Found + +#### Why This Error Occurred + +A module not found error can occur for many different reasons: + +- The module you're trying to import is not installed in your dependencies +- The module you're trying to import is in a different directory +- The module you're trying to import has a different casing +- The module you're trying to import uses Node.js specific modules, for example `dns`, outside of `getStaticProps` / `getStaticPaths` / `getServerSideProps` + +#### Possible Ways to Fix It + +##### The module you're trying to import is not installed in your dependencies + +When importing a module from [npm](https://npmjs.com) this module has to be installed locally. + +For example when importing the `swr` package: + +```js +import useSWR from 'swr' +``` + +The `swr` module has to be installed using a package manager. + +- When using `npm`: `npm install swr` +- When using `yarn`: `yarn add swr` + +##### The module you're trying to import is in a different directory + +Make sure that the path you're importing refers to the right directory and file. + +##### The module you're trying to import has a different casing + +Make sure the casing of the file is correct. + +Example: + +```js +// components/MyComponent.js +export default function MyComponent() { + return

Hello

+} +``` + +```js +// pages/index.js +// Note how `components/MyComponent` exists but `Mycomponent` without the capital `c` is imported +import MyComponent from '../components/Mycomponent' +``` + +Incorrect casing will lead to build failures on case-sensitive environments like most Linux-based continuous integration and can cause issues with Fast Refresh. + +##### The module you're trying to import uses Node.js specific modules + +`getStaticProps`, `getStaticPaths`, and `getServerSideProps` allow for using modules that can only run in the Node.js environment. This allows you to do direct database queries or reading data from Redis to name a few examples. + +The tree shaking only runs on top level pages, so it can't be relied on in separate React components. + +You can verify the tree shaking on [next-code-elimination.vercel.app](https://next-code-elimination.vercel.app/). + +Example of correctly tree shaken code: + +```js +// lib/redis.js +import Redis from 'ioredis' + +const redis = new Redis(process.env.REDIS_URL) + +export default redis +``` + +```js +// pages/index.js +import redis from '../lib/redis' + +export async function getStaticProps() { + const message = await redis.get('message') + return { + message, + } +} + +export default function Home({ message }) { + return

{message}

+} +``` + +Example of code that would break: + +```js +// lib/redis.js +import Redis from 'ioredis' + +const redis = new Redis(process.env.REDIS_URL) + +export default redis +``` + +```js +// pages/index.js +// Redis is a Node.js specific library that can't run in the browser +// Trying to use it in code that runs on both Node.js and the browser will result in a module not found error for modules that ioredis relies on +// If you run into such an error it's recommended to move the code to `getStaticProps` or `getServerSideProps` as those methods guarantee that the code is only run in Node.js. +import redis from '../lib/redis' +import { useEffect, useState } from 'react' + +export default function Home() { + const [message, setMessage] = useState() + useEffect(() => { + redis.get('message').then((result) => { + setMessage(result) + }) + }, []) + return

{message}

+} +``` + +Example of code that would break: + +```js +// lib/redis.js +import Redis from 'ioredis' + +// Modules that hold Node.js-only code can't also export React components +// Tree shaking of getStaticProps/getStaticPaths/getServerSideProps is ran only on page files +const redis = new Redis(process.env.REDIS_URL) + +export function MyComponent() { + return

Hello

+} + +export default redis +``` + +```js +// pages/index.js +// In practice you'll want to refactor the `MyComponent` to be a separate file so that tree shaking ensures that specific import is not included for the browser compilation +import redis, { MyComponent } from '../lib/redis' + +export async function getStaticProps() { + const message = await redis.get('message') + return { + message, + } +} + +export default function Home() { + return +} +``` diff --git a/errors/next-config-error.md b/errors/next-config-error.md new file mode 100644 index 0000000000000..d5e5b8b146387 --- /dev/null +++ b/errors/next-config-error.md @@ -0,0 +1,14 @@ +# next.config.js Loading Error + +#### Why This Error Occurred + +When attempting to load your `next.config.js` file an error occurred. This could be due to a syntax error or attempting to `require` a module that wasn't available. + +#### Possible Ways to Fix It + +See the error message in your terminal where you started `next` to see more context. The `next.config.js` file is not transpiled by Next.js currently so ensure only features supported by your current node.js version are being used. + +### Useful Links + +- [next.config.js documentation](https://nextjs.org/docs/api-reference/next.config.js/introduction) +- [node.js version feature chart](https://node.green/) diff --git a/errors/next-image-missing-loader-width.md b/errors/next-image-missing-loader-width.md new file mode 100644 index 0000000000000..0df5c6efa7124 --- /dev/null +++ b/errors/next-image-missing-loader-width.md @@ -0,0 +1,17 @@ +# Missing `width` in the URL Returned by the Loader Prop on `next/image` + +#### Why This Error Occurred + +The [`loader`](https://nextjs.org/docs/api-reference/next/image#loader) prop on the `next/image` component allows you to override the built-in URL resolution with a custom implementation in order to support any 3rd party cloud provider that can perform Image Optimization. + +This error occurred because the provided `loader()` function did not use `width` in the returned URL string. This means that the image will likely not be resized and therefore degrade performance. + +#### Possible Ways to Fix It + +- Ensure your Image Optimization provider can resize images. Then use the `width` parameter from the [`loader()`](https://nextjs.org/docs/api-reference/next/image#loader) function to construct the correct URL string. +- Add the [`unoptimized`](https://nextjs.org/docs/api-reference/next/image#unoptimized) prop. + +### Useful Links + +- [Image Optimization Documentation](https://nextjs.org/docs/basic-features/image-optimization) +- [`next/image` Documentation](https://nextjs.org/docs/api-reference/next/image) diff --git a/errors/next-image-missing-loader.md b/errors/next-image-missing-loader.md new file mode 100644 index 0000000000000..9a03dcb26a3d9 --- /dev/null +++ b/errors/next-image-missing-loader.md @@ -0,0 +1,15 @@ +# Missing `loader` Prop on `next/image` + +#### Why This Error Occurred + +When using the `next/image` component with [`loader="custom"`](https://nextjs.org/docs/basic-features/image-optimization#loader) in `next.config.js`, you must provide the [`loader`](https://nextjs.org/docs/api-reference/next/image#loader) prop to the component with your custom implementation. + +#### Possible Ways to Fix It + +- Add the [`loader`](https://nextjs.org/docs/api-reference/next/image#loader) prop to all usages of the `next/image` component. +- Change the [`loader`](https://nextjs.org/docs/basic-features/image-optimization#loader) configuration in `next.config.js`. + +### Useful Links + +- [Image Optimization Documentation](https://nextjs.org/docs/basic-features/image-optimization) +- [`next/image` Documentation](https://nextjs.org/docs/api-reference/next/image) diff --git a/errors/next-image-unconfigured-host.md b/errors/next-image-unconfigured-host.md index 5fdcc8bbb773e..e69525ad03d2b 100644 --- a/errors/next-image-unconfigured-host.md +++ b/errors/next-image-unconfigured-host.md @@ -2,11 +2,11 @@ #### Why This Error Occurred -On one of your pages that leverages the `next/image` component, you passed a `src` value that uses a `hostname` in the URL that isn't defined in the `images` config in `next.config.js`. +On one of your pages that leverages the `next/image` component, you passed a `src` value that uses a hostname in the URL that isn't defined in the `images.domains` config in `next.config.js`. #### Possible Ways to Fix It -Add the `hostname` to your `images` config in `next.config.js`: +Add the hostname of your URL to the `images.domains` config in `next.config.js`: ```js // next.config.js diff --git a/errors/next-script-for-ga.md b/errors/next-script-for-ga.md new file mode 100644 index 0000000000000..1d968090e1f15 --- /dev/null +++ b/errors/next-script-for-ga.md @@ -0,0 +1,98 @@ +# Next Script for Google Analytics + +### Why This Error Occurred + +An inline script was used for Google analytics which might impact your webpage's performance. + +### Possible Ways to Fix It + +#### Using gtag.js + +If you are using the [gtag.js](https://developers.google.com/analytics/devguides/collection/gtagjs) script to add analytics, use the `next/script` component with the right loading strategy to defer loading of the script until necessary. + +```jsx +import Script from 'next/script' + +const Home = () => { + return ( +
+ + +
+ ) +} + +export default Home +``` + +#### Using analytics.js + +If you are using the [analytics.js](https://developers.google.com/analytics/devguides/collection/analyticsjs) script to add analytics: + +```jsx +import Script from 'next/script' + +const Home = () => { + return ( +
+ +
+ ) +} + +export default Home +``` + +If you are using the [alternative async variant](https://developers.google.com/analytics/devguides/collection/analyticsjs#alternative_async_tag): + +```jsx +import Script from 'next/script' + +const Home = () => { + return ( +
+ + +
Home Page
+
+ ) +} + +export default Home +``` + +### Useful Links + +- [Efficiently load third-party JavaScript](https://web.dev/efficiently-load-third-party-javascript/) diff --git a/errors/no-title-in-document-head.md b/errors/no-title-in-document-head.md new file mode 100644 index 0000000000000..771e99674d5c4 --- /dev/null +++ b/errors/no-title-in-document-head.md @@ -0,0 +1,30 @@ +# No Title in Document Head + +### Why This Error Occurred + +A `` element was defined within the `Head` component imported from `next/document`, which should only be used for any `<head>` code that is common for all pages. Title tags should be defined at the page-level using `next/head`. + +### Possible Ways to Fix It + +Within a page or component, import and use `next/head` to define a page title: + +```jsx +import Head from 'next/head' + +export class Home { + render() { + return ( + <div> + <Head> + <title>My page title + +
+ ) + } +} +``` + +### Useful links + +- [next/head](https://nextjs.org/docs/api-reference/next/head) +- [Custom Document](https://nextjs.org/docs/advanced-features/custom-document) diff --git a/errors/no-unwanted-polyfillio.md b/errors/no-unwanted-polyfillio.md new file mode 100644 index 0000000000000..6f05aa9a7251a --- /dev/null +++ b/errors/no-unwanted-polyfillio.md @@ -0,0 +1,13 @@ +# Duplicate Polyfills from Polyfill.io + +#### Why This Error Occurred + +You are using Polyfill.io and including duplicate polyfills already shipped with Next.js. This increases page weight unnecessarily which can affect loading performance. + +#### Possible Ways to Fix It + +Remove all duplicate polyfills that are included with Polyfill.io. If you need to add polyfills but are not sure if Next.js already includes it, take a look at the list of [supported browsers and features](https://nextjs.org/docs/basic-features/supported-browsers-features) first. + +### Useful Links + +- [Supported Browsers and Features](https://nextjs.org/docs/basic-features/supported-browsers-features) diff --git a/errors/non-dynamic-getstaticpaths-usage.md b/errors/non-dynamic-getstaticpaths-usage.md new file mode 100644 index 0000000000000..2f94c774b0cab --- /dev/null +++ b/errors/non-dynamic-getstaticpaths-usage.md @@ -0,0 +1,14 @@ +# getStaticPaths Used on Non-Dynamic Page + +#### Why This Error Occurred + +On a non-dynamic SSG page `getStaticPaths` was incorrectly exported as this can only be used on dynamic pages to return the paths to prerender. + +#### Possible Ways to Fix It + +Remove the `getStaticPaths` export on the non-dynamic page or rename the page to be a dynamic page. + +### Useful Links + +- [Dynamic Routes Documentation](https://nextjs.org/docs/routing/dynamic-routes) +- [`getStaticPaths` Documentation](https://nextjs.org/docs/routing/dynamic-routes) diff --git a/errors/page-data-collection-timeout.md b/errors/page-data-collection-timeout.md new file mode 100644 index 0000000000000..12332bfb430be --- /dev/null +++ b/errors/page-data-collection-timeout.md @@ -0,0 +1,18 @@ +# Collecting page data timed out after multiple attempts + +#### Why This Error Occurred + +Next.js tries to restart the worker pool of the page data collection when no progress happens for a while, to avoid hanging builds. + +When restarted it will retry all uncompleted jobs, but if a job was unsuccessfully attempted multiple times, this will lead to an error. + +#### Possible Ways to Fix It + +- Make sure that there is no infinite loop during execution. +- Make sure all Promises in `getStaticPaths` `resolve` or `reject` correctly. +- Avoid very long timeouts for network requests. +- Increase the timeout by changing the `experimental.pageDataCollectionTimeout` configuration option (default `60` in seconds). + +### Useful Links + +- [`getStaticPaths`](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation) diff --git a/errors/placeholder-blur-data-url.md b/errors/placeholder-blur-data-url.md new file mode 100644 index 0000000000000..2fc53099c9830 --- /dev/null +++ b/errors/placeholder-blur-data-url.md @@ -0,0 +1,15 @@ +# `placeholder=blur` without `blurDataURL` + +#### Why This Error Occurred + +You are attempting use the `next/image` component with `placeholder=blur` property but no `blurDataURL` property. + +The `blurDataURL` might be missing because you're using a string for `src` instead of a static import. + +Or `blurDataURL` might be missing because the static import is an unsupported image format. Only jpg, png, and webp are supported at this time. + +#### Possible Ways to Fix It + +- Add a [`blurDataURL`](https://nextjs.org/docs/api-reference/next/image#blurdataurl) property, the contents should be a small Data URL to represent the image +- Change the [`src`](https://nextjs.org/docs/api-reference/next/image#src) property to a static import with one of the supported file types: jpg, png, or webp +- Remove the [`placeholder`](https://nextjs.org/docs/api-reference/next/image#placeholder) property, effectively no blur effect diff --git a/errors/postcss-ignored-plugin.md b/errors/postcss-ignored-plugin.md index 658ad57f28575..e8c73f1c69965 100644 --- a/errors/postcss-ignored-plugin.md +++ b/errors/postcss-ignored-plugin.md @@ -17,4 +17,4 @@ Remove the plugin specified in the error message from your custom PostCSS config #### How do I configure CSS Modules? CSS Modules are supported in [Next.js' built-in CSS support](https://nextjs.org/docs/advanced-features/customizing-postcss-config). -You can [read more](https://nextjs.org/docs/advanced-features/customizing-postcss-config) about how to use them [here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). +You can [read more about how to use CSS Modules here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). diff --git a/errors/postcss-shape.md b/errors/postcss-shape.md index 889fe55bcbc68..f121c889d04b1 100644 --- a/errors/postcss-shape.md +++ b/errors/postcss-shape.md @@ -39,7 +39,7 @@ module.exports = { } ``` -You can [read more](https://nextjs.org/docs/advanced-features/customizing-postcss-config) about configuring PostCSS in Next.js [here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). +You can [read more about configuring PostCSS in Next.js here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). #### Common Errors diff --git a/errors/prefetch-true-deprecated.md b/errors/prefetch-true-deprecated.md index dbc5a2ed346f4..77733fea94726 100644 --- a/errors/prefetch-true-deprecated.md +++ b/errors/prefetch-true-deprecated.md @@ -18,4 +18,4 @@ These requests have low-priority and yield to fetch() or XHR requests. Next.js w The prefetch attribute is no longer needed, when set to true, example: `prefetch={true}`, remove the property. -Prefetching can be disabled with `prefetch={false}`. +Prefetching can be turned off with `prefetch={false}`. diff --git a/errors/prerender-error.md b/errors/prerender-error.md index 26f5a03e14201..87e301c459db6 100644 --- a/errors/prerender-error.md +++ b/errors/prerender-error.md @@ -9,3 +9,5 @@ While prerendering a page an error occurred. This can occur for many reasons fro - Make sure to move any non-pages out of the `pages` folder - Check for any code that assumes a prop is available even when it might not be. e.g., have default data for all dynamic pages' props. - Check for any out of date modules that you might be relying on +- Make sure your component handles `fallback` if it is enabled in `getStaticPaths`. [Fallback docs](https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required) +- Make sure you are not trying to export (`next export`) pages that have server-side rendering enabled [(getServerSideProps)](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) diff --git a/errors/react-version.md b/errors/react-version.md index cfa4412e88461..713254102491b 100644 --- a/errors/react-version.md +++ b/errors/react-version.md @@ -5,7 +5,7 @@ Your project is using an old version of `react` or `react-dom` that does not meet the suggested minimum version requirement. -Next.js suggests using, at a minimum, `react@17.0.1` and `react-dom@17.0.1`. +Next.js suggests using, at a minimum, `react@17.0.2` and `react-dom@17.0.2`. Older versions of `react` and `react-dom` do work with Next.js, however, they do not enable all of Next.js' features. @@ -39,8 +39,8 @@ yarn add react@latest react-dom@latest ```json { "dependencies": { - "react": "^17.0.1", - "react-dom": "^17.0.1" + "react": "^17.0.2", + "react-dom": "^17.0.2" } } ``` diff --git a/errors/sharp-missing-in-production.md b/errors/sharp-missing-in-production.md new file mode 100644 index 0000000000000..02face5fc054f --- /dev/null +++ b/errors/sharp-missing-in-production.md @@ -0,0 +1,18 @@ +# Sharp Missing In Production + +#### Why This Error Occurred + +The `next/image` component's default loader uses [`squoosh`](https://www.npmjs.com/package/@squoosh/lib) because it is quick to install and suitable for a development environment. For a production environment using `next start`, it is strongly recommended you install [`sharp`](https://www.npmjs.com/package/sharp) by running `yarn add sharp` in your project directory. + +You are seeing this error because Image Optimization in production mode (`next start`) was detected. + +#### Possible Ways to Fix It + +- Install `sharp` by running `yarn add sharp` in your project directory and then reboot the server by running `next start` again +- If `sharp` is already installed but can't be resolved, set the `NEXT_SHARP_PATH` environment variable such as `NEXT_SHARP_PATH=/tmp/node_modules/sharp next start` + +> Note: This is not necessary for Vercel deployments, since `sharp` is installed automatically for you. + +### Useful Links + +- [Image Optimization Documentation](https://nextjs.org/docs/basic-features/image-optimization) diff --git a/errors/static-dir-deprecated.md b/errors/static-dir-deprecated.md index f15dc12d814c0..76e13986aed9a 100644 --- a/errors/static-dir-deprecated.md +++ b/errors/static-dir-deprecated.md @@ -8,11 +8,11 @@ The reason we want to support a `public` directory instead is to not require the #### Possible Ways to Fix It -You can move your `static` directory inside of the `public` directory and all URLs will remain the same as they were before. +You can move your `static` directory inside of the `public` directory and all URLs will stay the same as they were before. **Before** -```sh +``` static/ my-image.jpg pages/ @@ -23,7 +23,7 @@ components/ **After** -```sh +``` public/ static/ my-image.jpg diff --git a/errors/static-page-generation-timeout.md b/errors/static-page-generation-timeout.md new file mode 100644 index 0000000000000..6cef054dc5ddd --- /dev/null +++ b/errors/static-page-generation-timeout.md @@ -0,0 +1,19 @@ +# Static page generation timed out after multiple attempts + +#### Why This Error Occurred + +Next.js tries to restart the worker pool of the static page generation when no progress happens for a while, to avoid hanging builds. + +When restarted it will retry all uncompleted jobs, but if a job was unsuccessfully attempted multiple times, this will lead to an error. + +#### Possible Ways to Fix It + +- Make sure that there is no infinite loop during execution. +- Make sure all Promises in `getStaticPaths`/`getStaticProps` `resolve` or `reject` correctly. +- Avoid very long timeouts for network requests. +- Increase the timeout by changing the `experimental.staticPageGenerationTimeout` configuration option (default `60` in seconds). + +### Useful Links + +- [`getStaticPaths`](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation) +- [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) diff --git a/errors/template.md b/errors/template.md new file mode 100644 index 0000000000000..711da0c52abf2 --- /dev/null +++ b/errors/template.md @@ -0,0 +1,13 @@ +# + +#### Why This Error Occurred + + + +#### Possible Ways to Fix It + + + +### Useful Links + + diff --git a/errors/url-deprecated.md b/errors/url-deprecated.md index 50db5b6b5ad7f..df352d8e07068 100644 --- a/errors/url-deprecated.md +++ b/errors/url-deprecated.md @@ -10,7 +10,7 @@ The reason this is going away is that we want to make things very predictable an #### Possible Ways to Fix It -https://github.com/zeit/next-codemod#url-to-withrouter +https://nextjs.org/docs/advanced-features/codemods#url-to-withrouter Since Next 5 we provide a way to explicitly inject the Next.js router object into pages and all their descending components. The `router` property that is injected will hold the same values as `url`, like `pathname`, `asPath`, and `query`. @@ -33,4 +33,4 @@ export default withRouter(Page) We provide a codemod (a code to code transformation) to automatically change the `url` property usages to `withRouter`. -You can find this codemod and instructions on how to run it here: https://github.com/zeit/next-codemod#url-to-withrouter +You can find this codemod and instructions on how to run it here: https://nextjs.org/docs/advanced-features/codemods#url-to-withrouter diff --git a/errors/webpack5.md b/errors/webpack5.md new file mode 100644 index 0000000000000..0c8aa780aabc9 --- /dev/null +++ b/errors/webpack5.md @@ -0,0 +1,48 @@ +# Webpack 5 Adoption + +#### Why This Message Occurred + +Next.js has adopted webpack 5 as the default for compilation, webpack 4 has been deprecated. We've spent a lot of effort into ensuring the transition from webpack 4 to 5 will be as smooth as possible. + +Your application currently has webpack 5 disabled using the `webpack5: false` flag which will be removed in Next.js 12: + +```js +module.exports = { + // Webpack 5 is enabled by default + // You can still use webpack 4 while upgrading to the latest version of Next.js by adding the "webpack5: false" flag + webpack5: false, +} +``` + +Using webpack 5 in your application has many benefits, notably: + +- Improved Disk Caching: `next build` is significantly faster on subsequent builds +- Improved Fast Refresh: Fast Refresh work is prioritized +- Improved Long Term Caching of Assets: Deterministic code output that is less likely to change between builds +- Improved Tree Shaking +- Support for assets using `new URL("file.png", import.meta.url)` +- Support for web workers using `new Worker(new URL("worker.js", import.meta.url))` +- Support for `exports`/`imports` field in `package.json` + +In the past releases we have gradually rolled out webpack 5 to Next.js applications: + +- In Next.js 10.2 we automatically opted-in applications without custom webpack configuration in `next.config.js` +- In Next.js 10.2 we automatically opted-in applications that do not have a `next.config.js` +- In Next.js 11 webpack 5 was enabled by default for all applications. You can still opt-out and use webpack 4 to help with backwards compatibility using `webpack5: false` in `next.config.js` +- In Next.js 11.2 webpack 4 was deprecated + +In Next.js 12 webpack 4 support will be removed. + +#### Custom webpack configuration + +In case you do have custom webpack configuration, either through custom plugins or your own modifications you'll have to take a few steps to ensure your applications works with webpack 5. + +- When using `next-transpile-modules` make sure you use the latest version which includes [this patch](https://github.com/martpie/next-transpile-modules/pull/179) +- When using `@zeit/next-css` / `@zeit/next-sass` make sure you use the [built-in CSS/Sass support](https://nextjs.org/docs/basic-features/built-in-css-support) instead +- When using `@zeit/next-preact` use [this example](https://github.com/vercel/next-plugins/tree/master/packages/next-preact) instead +- When using `@zeit/next-source-maps` use the [built-in production Source Map support](https://nextjs.org/docs/advanced-features/source-maps) +- When using webpack plugins make sure they're upgraded to the latest version, in most cases the latest version will include webpack 5 support. In some cases these upgraded webpack plugins will only support webpack 5. + +### Useful Links + +In case you're running into issues you can connect with the community in [this help discussion](https://github.com/vercel/next.js/discussions/23498). diff --git a/examples/active-class-name/README.md b/examples/active-class-name/README.md index 03d46cea4d39e..902d79e24b7fe 100644 --- a/examples/active-class-name/README.md +++ b/examples/active-class-name/README.md @@ -2,6 +2,12 @@ ReactRouter has a convenience property on the `Link` element to allow an author to set the _active_ className on a link. This example replicates that functionality using Next's own `Link`. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/active-class-name) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/active-class-name/package.json b/examples/active-class-name/package.json index a08741654ad62..6513b825ee0a6 100644 --- a/examples/active-class-name/package.json +++ b/examples/active-class-name/package.json @@ -1,16 +1,14 @@ { - "name": "active-class-name", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", "start": "next start" }, - "author": "Remy Sharp ", "dependencies": { "next": "latest", - "react": "^16.7.0", - "react-dom": "^16.7.0" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2", + "prop-types": "^15.7.2" + } } diff --git a/examples/amp-first/README.md b/examples/amp-first/README.md index 1213e4471715e..4db9d29709f07 100644 --- a/examples/amp-first/README.md +++ b/examples/amp-first/README.md @@ -1,6 +1,6 @@ # AMP First Boilerplate ⚡ -This example sets up the boilerplate for an AMP First Site. You can see a live version [here](https://my-next-app.sebastianbenz.now.sh). The boilerplate includes the following features: +This example sets up the boilerplate for an AMP First Site. You can see a [live version here](https://my-next-app.sebastianbenz.vercel.app). The boilerplate includes the following features: - AMP Extension helpers (`amp-state`, `amp-script`, ...) - AMP Serviceworker integration diff --git a/examples/amp-first/package.json b/examples/amp-first/package.json index 3c62fecf57f37..349de02f4d84b 100644 --- a/examples/amp-first/package.json +++ b/examples/amp-first/package.json @@ -1,6 +1,5 @@ { - "name": "amp-first", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -8,8 +7,7 @@ }, "dependencies": { "next": "latest", - "react": "^16.10.2", - "react-dom": "^16.10.2" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2" + } } diff --git a/examples/amp-story/package.json b/examples/amp-story/package.json index d577702ebefaf..349de02f4d84b 100644 --- a/examples/amp-story/package.json +++ b/examples/amp-story/package.json @@ -1,6 +1,5 @@ { - "name": "amp-story", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -8,8 +7,7 @@ }, "dependencies": { "next": "latest", - "react": "^16.7.0", - "react-dom": "^16.7.0" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2" + } } diff --git a/examples/amp/package.json b/examples/amp/package.json index d033597980c09..349de02f4d84b 100644 --- a/examples/amp/package.json +++ b/examples/amp/package.json @@ -1,6 +1,5 @@ { - "name": "amp", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -8,8 +7,7 @@ }, "dependencies": { "next": "latest", - "react": "^16.7.0", - "react-dom": "^16.7.0" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2" + } } diff --git a/examples/analyze-bundles/README.md b/examples/analyze-bundles/README.md index abea34df8353d..8aec1e56c79c4 100644 --- a/examples/analyze-bundles/README.md +++ b/examples/analyze-bundles/README.md @@ -2,6 +2,12 @@ This example shows how to analyze the output bundles using [@next/bundle-analyzer](https://github.com/vercel/next.js/tree/master/packages/next-bundle-analyzer) +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/analyze-bundles) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): @@ -27,3 +33,11 @@ npm run analyze # or yarn analyze ``` + +Once the build is completed, you can inspect the bundle by running: + +```bash +npm run serve +# or +yarn serve +``` diff --git a/examples/analyze-bundles/package.json b/examples/analyze-bundles/package.json index a5caa9e790812..a40dca11de1d7 100644 --- a/examples/analyze-bundles/package.json +++ b/examples/analyze-bundles/package.json @@ -1,19 +1,21 @@ { - "name": "analyze-bundles", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", "start": "next start", - "analyze": "cross-env ANALYZE=true yarn build" + "analyze": "cross-env ANALYZE=true yarn build", + "serve": "serve .next/analyze" }, "dependencies": { "@next/bundle-analyzer": "^9.1.4", "cross-env": "^6.0.3", "faker": "^4.1.0", "next": "latest", - "react": "^16.8.0", - "react-dom": "^16.8.0" + "react": "^17.0.2", + "react-dom": "^17.0.2" }, - "license": "MIT" + "devDependencies": { + "serve": "^11.3.2" + } } diff --git a/examples/api-routes-apollo-server-and-client-auth/README.md b/examples/api-routes-apollo-server-and-client-auth/README.md index 68ca3be46917e..6385acf21368a 100644 --- a/examples/api-routes-apollo-server-and-client-auth/README.md +++ b/examples/api-routes-apollo-server-and-client-auth/README.md @@ -4,6 +4,12 @@ In this simple example, we integrate Apollo seamlessly with [Next.js data fetching methods](https://nextjs.org/docs/basic-features/data-fetching) to fetch queries in the server and hydrate them in the browser. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/api-routes-apollo-server-and-client-auth) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/api-routes-apollo-server-and-client-auth/package.json b/examples/api-routes-apollo-server-and-client-auth/package.json index 7c0ddd6572ff4..448f8254297e2 100644 --- a/examples/api-routes-apollo-server-and-client-auth/package.json +++ b/examples/api-routes-apollo-server-and-client-auth/package.json @@ -1,6 +1,5 @@ { - "name": "api-routes-apollo-server-and-client-auth", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -15,9 +14,8 @@ "graphql": "^14.0.2", "next": "latest", "prop-types": "^15.6.2", - "react": "^16.7.0", - "react-dom": "^16.7.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", "uuid": "8.1.0" - }, - "license": "MIT" + } } diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/signin.js b/examples/api-routes-apollo-server-and-client-auth/pages/signin.js index 0e652164cffef..59349fb0e57dc 100644 --- a/examples/api-routes-apollo-server-and-client-auth/pages/signin.js +++ b/examples/api-routes-apollo-server-and-client-auth/pages/signin.js @@ -65,7 +65,7 @@ function SignIn() { label="Password" /> or{' '} - + Sign up diff --git a/examples/api-routes-apollo-server-and-client-auth/pages/signup.js b/examples/api-routes-apollo-server-and-client-auth/pages/signup.js index ce16f159e4c94..bcbf81f2410c3 100644 --- a/examples/api-routes-apollo-server-and-client-auth/pages/signup.js +++ b/examples/api-routes-apollo-server-and-client-auth/pages/signup.js @@ -60,7 +60,7 @@ function SignUp() { label="Password" /> or{' '} - + Sign in diff --git a/examples/api-routes-apollo-server-and-client/README.md b/examples/api-routes-apollo-server-and-client/README.md index 2d51269818307..96c454e0527c6 100644 --- a/examples/api-routes-apollo-server-and-client/README.md +++ b/examples/api-routes-apollo-server-and-client/README.md @@ -4,6 +4,18 @@ In this simple example, we integrate Apollo seamlessly with [Next.js data fetching methods](https://nextjs.org/docs/basic-features/data-fetching) to fetch queries in the server and hydrate them in the browser. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/api-routes-apollo-server-and-client) + +## Deploy your own + +Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/api-routes-apollo-server-and-client&project-name=api-routes-apollo-server-and-client&repository-name=api-routes-apollo-server-and-client) + ## How to use Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: diff --git a/examples/api-routes-apollo-server-and-client/package.json b/examples/api-routes-apollo-server-and-client/package.json index 43263e3486f16..c1de5dc4ba83f 100644 --- a/examples/api-routes-apollo-server-and-client/package.json +++ b/examples/api-routes-apollo-server-and-client/package.json @@ -1,6 +1,5 @@ { - "name": "api-routes-apollo-server-and-client", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -12,8 +11,7 @@ "graphql": "^14.0.2", "next": "latest", "prop-types": "^15.6.2", - "react": "^16.7.0", - "react-dom": "^16.7.0" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2" + } } diff --git a/examples/api-routes-apollo-server/README.md b/examples/api-routes-apollo-server/README.md index 320060be87784..9b4209908adb2 100644 --- a/examples/api-routes-apollo-server/README.md +++ b/examples/api-routes-apollo-server/README.md @@ -2,6 +2,12 @@ Next.js ships with two forms of pre-rendering: [Static Generation](https://nextjs.org/docs/basic-features/pages#static-generation-recommended) and [Server-side Rendering](https://nextjs.org/docs/basic-features/pages#server-side-rendering). This example shows how to perform Static Generation using a local [Apollo GraphQL](https://www.apollographql.com/docs/apollo-server/) schema within [getStaticProps](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) and [getStaticPaths](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation). The end result is a Next.js application that uses one Apollo GraphQL schema to generate static pages at build time and also serve a GraphQL [API Route](https://nextjs.org/docs/api-routes/introduction) at runtime. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/api-routes-apollo-server) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/api-routes-apollo-server/package.json b/examples/api-routes-apollo-server/package.json index c42045d94020b..88af4336ccc68 100644 --- a/examples/api-routes-apollo-server/package.json +++ b/examples/api-routes-apollo-server/package.json @@ -1,6 +1,5 @@ { - "name": "api-routes-apollo-server", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -11,8 +10,7 @@ "apollo-server-micro": "2.13.1", "graphql": "15.0.0", "next": "latest", - "react": "16.13.1", - "react-dom": "16.13.1" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2" + } } diff --git a/examples/api-routes-cors/README.md b/examples/api-routes-cors/README.md index 5fdee8ab744b8..5ba1b348401fc 100644 --- a/examples/api-routes-cors/README.md +++ b/examples/api-routes-cors/README.md @@ -4,6 +4,12 @@ Next.js ships with [API routes](https://nextjs.org/docs/api-routes/introduction) This example shows how to create an `API` endpoint with [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) headers, using the [cors](https://github.com/expressjs/cors) package. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/api-routes-cors) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/api-routes-cors/package.json b/examples/api-routes-cors/package.json index 0338eb2648475..bc1300882acc4 100644 --- a/examples/api-routes-cors/package.json +++ b/examples/api-routes-cors/package.json @@ -1,6 +1,5 @@ { - "name": "api-routes-cors", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -9,8 +8,7 @@ "dependencies": { "cors": "2.8.5", "next": "latest", - "react": "^16.13.1", - "react-dom": "^16.13.1" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2" + } } diff --git a/examples/api-routes-graphql/README.md b/examples/api-routes-graphql/README.md index 1a31922c94a90..1c51cf4d7e38b 100644 --- a/examples/api-routes-graphql/README.md +++ b/examples/api-routes-graphql/README.md @@ -2,6 +2,12 @@ Next.js ships with [API routes](https://github.com/vercel/next.js#api-routes), which provide an easy solution to build your own `API`. This example shows their usage alongside [apollo-server-micro](https://github.com/apollographql/apollo-server/tree/main/packages/apollo-server-micro) to provide simple GraphQL server consumed by Next.js app. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/api-routes-graphql) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/api-routes-graphql/package.json b/examples/api-routes-graphql/package.json index eb2b811a582b6..e4329bea00b67 100644 --- a/examples/api-routes-graphql/package.json +++ b/examples/api-routes-graphql/package.json @@ -1,18 +1,17 @@ { - "name": "api-routes-graphql", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", "start": "next start" }, "dependencies": { - "apollo-server-micro": "2.11.0", - "graphql": "14.6.0", + "apollo-server-micro": "^3.0.1", + "graphql": "^15.5.1", + "micro": "^9.3.4", "next": "latest", - "react": "16.13.1", - "react-dom": "16.13.1", - "swr": "0.1.18" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2", + "swr": "^0.5.6" + } } diff --git a/examples/api-routes-graphql/pages/api/graphql.js b/examples/api-routes-graphql/pages/api/graphql.js index 0dcb066bbcbdc..0a29f3ce88987 100644 --- a/examples/api-routes-graphql/pages/api/graphql.js +++ b/examples/api-routes-graphql/pages/api/graphql.js @@ -19,10 +19,31 @@ const resolvers = { const apolloServer = new ApolloServer({ typeDefs, resolvers }) +const startServer = apolloServer.start() + +export default async function handler(req, res) { + res.setHeader('Access-Control-Allow-Credentials', 'true') + res.setHeader( + 'Access-Control-Allow-Origin', + 'https://studio.apollographql.com' + ) + res.setHeader( + 'Access-Control-Allow-Headers', + 'Origin, X-Requested-With, Content-Type, Accept' + ) + if (req.method === 'OPTIONS') { + res.end() + return false + } + + await startServer + await apolloServer.createHandler({ + path: '/api/graphql', + })(req, res) +} + export const config = { api: { bodyParser: false, }, } - -export default apolloServer.createHandler({ path: '/api/graphql' }) diff --git a/examples/api-routes-middleware/README.md b/examples/api-routes-middleware/README.md index 7ed15b6ff90a5..44a12e118ddcb 100644 --- a/examples/api-routes-middleware/README.md +++ b/examples/api-routes-middleware/README.md @@ -2,6 +2,12 @@ Next.js ships with [API routes](https://github.com/vercel/next.js#api-routes), which provide an easy solution to build your own `API`. This example shows how to implement simple `middleware` to wrap around your `API` endpoints. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/api-routes-middleware) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/api-routes-middleware/package.json b/examples/api-routes-middleware/package.json index a7e21e6f70abb..ac005757491a9 100644 --- a/examples/api-routes-middleware/package.json +++ b/examples/api-routes-middleware/package.json @@ -1,6 +1,5 @@ { - "name": "api-routes-middleware", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -9,9 +8,8 @@ "dependencies": { "cookie": "0.4.0", "next": "latest", - "react": "^16.8.6", - "react-dom": "^16.8.6", + "react": "^17.0.2", + "react-dom": "^17.0.2", "swr": "0.1.18" - }, - "license": "MIT" + } } diff --git a/examples/api-routes-rate-limit/README.md b/examples/api-routes-rate-limit/README.md index e3397b2a9fc4e..9328d25846365 100644 --- a/examples/api-routes-rate-limit/README.md +++ b/examples/api-routes-rate-limit/README.md @@ -16,6 +16,12 @@ X-RateLimit-Limit: 10 X-RateLimit-Remaining: 0 ``` +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/api-routes-rate-limit) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): @@ -27,9 +33,9 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash -npx create-next-app --example api-routes api-routes-rate-limit +npx create-next-app --example api-routes-rate-limit api-routes-rate-limit-app # or -yarn create next-app --example api-routes api-routes-rate-limit +yarn create next-app --example api-routes-rate-limit api-routes-rate-limit-app ``` Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/api-routes-rate-limit/package.json b/examples/api-routes-rate-limit/package.json index b43827f60cb63..568cc38e0b848 100644 --- a/examples/api-routes-rate-limit/package.json +++ b/examples/api-routes-rate-limit/package.json @@ -1,6 +1,4 @@ { - "name": "nextjs-rate-limit", - "version": "0.0.0", "private": true, "scripts": { "dev": "next dev", @@ -10,8 +8,8 @@ "dependencies": { "lru-cache": "^6.0.0", "next": "10.0.3", - "react": "17.0.1", - "react-dom": "17.0.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", "uuid": "^8.3.1" } } diff --git a/examples/api-routes-rest/README.md b/examples/api-routes-rest/README.md index 5183ca2166520..c8f176bd5e2c1 100644 --- a/examples/api-routes-rest/README.md +++ b/examples/api-routes-rest/README.md @@ -2,6 +2,12 @@ Next.js ships with [API routes](https://github.com/vercel/next.js#api-routes), which provide an easy solution to build your own `API`. This example shows how it can be used to create your [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) `API`. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/api-routes-rest) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): @@ -18,6 +24,6 @@ npx create-next-app --example api-routes-rest api-routes-rest-app yarn create next-app --example api-routes-rest api-routes-rest-app ``` -### Deploy to Now +### Deploy to Vercel Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/api-routes-rest/package.json b/examples/api-routes-rest/package.json index 883f3d7720f03..4b23e0ce0f192 100644 --- a/examples/api-routes-rest/package.json +++ b/examples/api-routes-rest/package.json @@ -1,6 +1,5 @@ { - "name": "api-routes-rest", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -8,9 +7,8 @@ }, "dependencies": { "next": "latest", - "react": "^16.8.6", - "react-dom": "^16.8.6", + "react": "^17.0.2", + "react-dom": "^17.0.2", "swr": "^0.1.18" - }, - "license": "MIT" + } } diff --git a/examples/api-routes/README.md b/examples/api-routes/README.md index 334bf15feedb0..87e9fa6321f9d 100644 --- a/examples/api-routes/README.md +++ b/examples/api-routes/README.md @@ -2,6 +2,12 @@ Next.js ships with [API routes](https://nextjs.org/docs/api-routes/introduction) which provides an easy solution to build your own `API`. This example shows how to create multiple `API` endpoints with serverless functions, which can execute independently. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/api-routes) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/api-routes/package.json b/examples/api-routes/package.json index f6156d9d3fa1a..633936bee19fb 100644 --- a/examples/api-routes/package.json +++ b/examples/api-routes/package.json @@ -1,6 +1,5 @@ { - "name": "api-routes", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -8,9 +7,8 @@ }, "dependencies": { "next": "latest", - "react": "^16.8.6", - "react-dom": "^16.8.6", + "react": "^17.0.2", + "react-dom": "^17.0.2", "swr": "0.1.18" - }, - "license": "MIT" + } } diff --git a/examples/auth0/.env.local.example b/examples/auth0/.env.local.example index 4946e625c14e5..40f6027fa2d3d 100644 --- a/examples/auth0/.env.local.example +++ b/examples/auth0/.env.local.example @@ -2,8 +2,9 @@ NEXT_PUBLIC_AUTH0_CLIENT_ID= NEXT_PUBLIC_AUTH0_SCOPE="openid profile" NEXT_PUBLIC_AUTH0_DOMAIN= -NEXT_PUBLIC_REDIRECT_URI="http://localhost:3000/api/callback" -NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI="http://localhost:3000" +NEXT_PUBLIC_BASE_URL="http://localhost:3000" +NEXT_PUBLIC_REDIRECT_URI="/api/callback" +NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI="/" # Secret environment variables only available to Node.js AUTH0_CLIENT_SECRET= diff --git a/examples/auth0/README.md b/examples/auth0/README.md index 90cd8b521b4c9..e2b2d97bb4381 100644 --- a/examples/auth0/README.md +++ b/examples/auth0/README.md @@ -44,12 +44,13 @@ cp .env.local.example .env.local Then, open `.env.local` and add the missing environment variables: -- `NEXT_PUBLIC_AUTH0_DOMAIN` - Can be found in the Auth0 dashboard under `settings`. +- `NEXT_PUBLIC_AUTH0_DOMAIN` - Can be found in the Auth0 dashboard under `settings`. (Should be prefixed with `https://`) - `NEXT_PUBLIC_AUTH0_CLIENT_ID` - Can be found in the Auth0 dashboard under `settings`. - `AUTH0_CLIENT_SECRET` - Can be found in the Auth0 dashboard under `settings`. -- `NEXT_PUBLIC_REDIRECT_URI` - The url where Auth0 redirects back to, make sure a consistent url is used here. -- `NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI` - Where to redirect after logging out -- `SESSION_COOKIE_SECRET` - A unique secret used to encrypt the cookies, has to be at least 32 characters. You can use [this generator](https://generate-secret.now.sh/32) to generate a value. +- `NEXT_PUBLIC_BASE_URL` - The base url of the application. +- `NEXT_PUBLIC_REDIRECT_URI` - The relative url path where Auth0 redirects back to. +- `NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI` - Where to redirect after logging out. +- `SESSION_COOKIE_SECRET` - A unique secret used to encrypt the cookies, has to be at least 32 characters. You can use [this generator](https://generate-secret.vercel.app/32) to generate a value. - `SESSION_COOKIE_LIFETIME` - How long a session lasts in seconds. The default is 2 hours. ## Deploy on Vercel diff --git a/examples/auth0/lib/auth0.js b/examples/auth0/lib/auth0.js index 84fe93be3df93..ffcf3450c5890 100644 --- a/examples/auth0/lib/auth0.js +++ b/examples/auth0/lib/auth0.js @@ -1,18 +1,24 @@ import { initAuth0 } from '@auth0/nextjs-auth0' export default initAuth0({ - clientId: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID, + secret: process.env.SESSION_COOKIE_SECRET, + issuerBaseURL: process.env.NEXT_PUBLIC_AUTH0_DOMAIN, + baseURL: process.env.NEXT_PUBLIC_BASE_URL, + clientID: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID, clientSecret: process.env.AUTH0_CLIENT_SECRET, - scope: process.env.NEXT_PUBLIC_AUTH0_SCOPE || 'openid profile', - domain: process.env.NEXT_PUBLIC_AUTH0_DOMAIN, - redirectUri: - process.env.NEXT_PUBLIC_REDIRECT_URI || - 'http://localhost:3000/api/callback', - postLogoutRedirectUri: - process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI || - 'http://localhost:3000/', + routes: { + callback: + process.env.NEXT_PUBLIC_REDIRECT_URI || + 'http://localhost:3000/api/callback', + postLogoutRedirect: + process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI || + 'http://localhost:3000', + }, + authorizationParams: { + response_type: 'code', + scope: process.env.NEXT_PUBLIC_AUTH0_SCOPE, + }, session: { - cookieSecret: process.env.SESSION_COOKIE_SECRET, - cookieLifetime: Number(process.env.SESSION_COOKIE_LIFETIME) || 7200, + absoluteDuration: process.env.SESSION_COOKIE_LIFETIME, }, }) diff --git a/examples/auth0/package.json b/examples/auth0/package.json index 16f771f656d6d..96caf0e6d5c95 100644 --- a/examples/auth0/package.json +++ b/examples/auth0/package.json @@ -1,16 +1,15 @@ { - "name": "auth0", + "private": true, "scripts": { "dev": "next", "build": "next build", "start": "next start" }, - "author": "", - "license": "MIT", "dependencies": { - "@auth0/nextjs-auth0": "^0.8.0", + "@auth0/nextjs-auth0": "^1.4.2", "next": "latest", - "react": "^16.12.0", - "react-dom": "^16.12.0" + "react": "^17.0.2", + "react-dom": "^17.0.2", + "tslib": "^2.2.0" } } diff --git a/examples/basic-css/README.md b/examples/basic-css/README.md index a7e56f8b5f0dc..0f6d8517a7b75 100644 --- a/examples/basic-css/README.md +++ b/examples/basic-css/README.md @@ -2,6 +2,12 @@ Next.js has built-in support for [CSS Modules](https://nextjs.org/docs/basic-features/built-in-css-support#adding-component-level-css) allowing you to write scoped CSS by automatically creating a unique class name. CSS Module files can be imported anywhere in your application and you don't have to worry about collisions. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/basic-css) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/basic-css/package.json b/examples/basic-css/package.json index 99b864c87fd0d..349de02f4d84b 100644 --- a/examples/basic-css/package.json +++ b/examples/basic-css/package.json @@ -1,6 +1,5 @@ { - "name": "basic-css", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -8,8 +7,7 @@ }, "dependencies": { "next": "latest", - "react": "^16.7.0", - "react-dom": "^16.7.0" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2" + } } diff --git a/examples/basic-export/README.md b/examples/basic-export/README.md index 06f34a95483e2..e53128be24960 100644 --- a/examples/basic-export/README.md +++ b/examples/basic-export/README.md @@ -2,6 +2,12 @@ This example shows the most basic usage of `next export`. Without `exportPathMap`. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/basic-export) + ## Deploy your own Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): diff --git a/examples/basic-export/package.json b/examples/basic-export/package.json index f3b2ce33a2120..d7485059ef3fc 100644 --- a/examples/basic-export/package.json +++ b/examples/basic-export/package.json @@ -1,6 +1,5 @@ { - "name": "basic-export", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build && next export", @@ -8,8 +7,7 @@ }, "dependencies": { "next": "latest", - "react": "^16.7.0", - "react-dom": "^16.7.0" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2" + } } diff --git a/examples/blog-starter-typescript/README.md b/examples/blog-starter-typescript/README.md index c9156b6a3be42..b74a6e5ad721a 100644 --- a/examples/blog-starter-typescript/README.md +++ b/examples/blog-starter-typescript/README.md @@ -8,7 +8,11 @@ The blog posts are stored in `/_posts` as Markdown files with front matter suppo To create the blog posts we use [`remark`](https://github.com/remarkjs/remark) and [`remark-html`](https://github.com/remarkjs/remark-html) to convert the Markdown files into an HTML string, and then send it down as a prop to the page. The metadata of every post is handled by [`gray-matter`](https://github.com/jonschlinkert/gray-matter) and also sent in props to the page. -## How to use +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/blog-starter-typescript) ## Deploy your own @@ -16,6 +20,8 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/blog-starter-typescript&project-name=blog-starter-typescript&repository-name=blog-starter-typescript) +## How to use + Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash diff --git a/examples/blog-starter-typescript/lib/api.ts b/examples/blog-starter-typescript/lib/api.ts index cf3be86f0b0f8..be2edfd854e78 100644 --- a/examples/blog-starter-typescript/lib/api.ts +++ b/examples/blog-starter-typescript/lib/api.ts @@ -29,7 +29,7 @@ export function getPostBySlug(slug: string, fields: string[] = []) { items[field] = content } - if (data[field]) { + if (typeof data[field] !== 'undefined') { items[field] = data[field] } }) diff --git a/examples/blog-starter-typescript/lib/constants.ts b/examples/blog-starter-typescript/lib/constants.ts index 5ebee62574cae..d0dbbd103524d 100644 --- a/examples/blog-starter-typescript/lib/constants.ts +++ b/examples/blog-starter-typescript/lib/constants.ts @@ -1,4 +1,4 @@ export const EXAMPLE_PATH = 'blog-starter-typescript' export const CMS_NAME = 'Markdown' export const HOME_OG_IMAGE_URL = - 'https://og-image.now.sh/Next.js%20Blog%20Starter%20Example.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg' + 'https://og-image.vercel.app/Next.js%20Blog%20Starter%20Example.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg' diff --git a/examples/blog-starter-typescript/next-env.d.ts b/examples/blog-starter-typescript/next-env.d.ts index 7b7aa2c7727d8..9bc3dd46b9d9b 100644 --- a/examples/blog-starter-typescript/next-env.d.ts +++ b/examples/blog-starter-typescript/next-env.d.ts @@ -1,2 +1,6 @@ /// /// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/blog-starter-typescript/package.json b/examples/blog-starter-typescript/package.json index 1a0e3a0536cbe..4ddf0e92bf33c 100644 --- a/examples/blog-starter-typescript/package.json +++ b/examples/blog-starter-typescript/package.json @@ -1,6 +1,5 @@ { - "name": "blog-starter-typescript", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -8,25 +7,23 @@ "typecheck": "tsc" }, "dependencies": { - "classnames": "2.2.6", - "date-fns": "2.10.0", - "gray-matter": "4.0.2", + "classnames": "2.3.1", + "date-fns": "2.21.3", + "gray-matter": "4.0.3", "next": "latest", - "react": "^16.13.0", - "react-dom": "^16.13.0", - "remark": "11.0.2", - "remark-html": "10.0.0", - "typescript": "^4.0.3" + "react": "^17.0.2", + "react-dom": "^17.0.2", + "remark": "13.0.0", + "remark-html": "13.0.1", + "typescript": "^4.2.4" }, "devDependencies": { - "@types/classnames": "^2.2.10", - "@types/jest": "^25.2.2", - "@types/node": "^14.0.1", - "@types/react": "^16.9.35", - "@types/react-dom": "^16.9.8", - "autoprefixer": "^10.2.1", - "postcss": "^8.2.4", - "tailwindcss": "^2.0.2" - }, - "license": "MIT" + "@types/jest": "^26.0.23", + "@types/node": "^15.6.0", + "@types/react": "^17.0.6", + "@types/react-dom": "^17.0.5", + "autoprefixer": "^10.2.5", + "postcss": "^8.3.0", + "tailwindcss": "^2.1.2" + } } diff --git a/examples/blog-starter-typescript/pages/posts/[slug].tsx b/examples/blog-starter-typescript/pages/posts/[slug].tsx index 9b11f54f90956..1f77e63e773ce 100644 --- a/examples/blog-starter-typescript/pages/posts/[slug].tsx +++ b/examples/blog-starter-typescript/pages/posts/[slug].tsx @@ -87,10 +87,10 @@ export async function getStaticPaths() { const posts = getAllPosts(['slug']) return { - paths: posts.map((posts) => { + paths: posts.map((post) => { return { params: { - slug: posts.slug, + slug: post.slug, }, } }), diff --git a/examples/blog-starter-typescript/postcss.config.js b/examples/blog-starter-typescript/postcss.config.js index 9fb517645aeea..33ad091d26d8a 100644 --- a/examples/blog-starter-typescript/postcss.config.js +++ b/examples/blog-starter-typescript/postcss.config.js @@ -1,3 +1,6 @@ module.exports = { - plugins: ['tailwindcss', 'autoprefixer'], + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, } diff --git a/examples/blog-starter/README.md b/examples/blog-starter/README.md index bcfee0c665a77..0b9ab5a1d932b 100644 --- a/examples/blog-starter/README.md +++ b/examples/blog-starter/README.md @@ -6,6 +6,12 @@ The blog posts are stored in `/_posts` as Markdown files with front matter suppo To create the blog posts we use [`remark`](https://github.com/remarkjs/remark) and [`remark-html`](https://github.com/remarkjs/remark-html) to convert the Markdown files into an HTML string, and then send it down as a prop to the page. The metadata of every post is handled by [`gray-matter`](https://github.com/jonschlinkert/gray-matter) and also sent in props to the page. +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/blog-starter) + ## Demo [https://next-blog-starter.vercel.app/](https://next-blog-starter.vercel.app/) @@ -36,10 +42,16 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: -```bash +``` npx create-next-app --example blog-starter blog-starter-app -# or + +``` + +or + +``` yarn create next-app --example blog-starter blog-starter-app + ``` Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). diff --git a/examples/blog-starter/lib/api.js b/examples/blog-starter/lib/api.js index 08da07eb78ab8..b9612a8ac5366 100644 --- a/examples/blog-starter/lib/api.js +++ b/examples/blog-starter/lib/api.js @@ -25,7 +25,7 @@ export function getPostBySlug(slug, fields = []) { items[field] = content } - if (data[field]) { + if (typeof data[field] !== 'undefined') { items[field] = data[field] } }) diff --git a/examples/blog-starter/lib/constants.js b/examples/blog-starter/lib/constants.js index 5cd3cee31c243..9beaff8f7f29c 100644 --- a/examples/blog-starter/lib/constants.js +++ b/examples/blog-starter/lib/constants.js @@ -1,4 +1,4 @@ export const EXAMPLE_PATH = 'blog-starter' export const CMS_NAME = 'Markdown' export const HOME_OG_IMAGE_URL = - 'https://og-image.now.sh/Next.js%20Blog%20Starter%20Example.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg' + 'https://og-image.vercel.app/Next.js%20Blog%20Starter%20Example.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg' diff --git a/examples/blog-starter/package.json b/examples/blog-starter/package.json index dd144e8438827..82d61419b449a 100644 --- a/examples/blog-starter/package.json +++ b/examples/blog-starter/package.json @@ -1,6 +1,5 @@ { - "name": "blog-starter", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -11,8 +10,8 @@ "date-fns": "2.16.1", "gray-matter": "4.0.2", "next": "latest", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", "remark": "13.0.0", "remark-html": "13.0.1" }, @@ -20,6 +19,5 @@ "autoprefixer": "^10.2.1", "postcss": "^8.2.4", "tailwindcss": "^2.0.2" - }, - "license": "MIT" + } } diff --git a/examples/blog-with-comment/.env.local.example b/examples/blog-with-comment/.env.local.example new file mode 100644 index 0000000000000..e2035e120e7aa --- /dev/null +++ b/examples/blog-with-comment/.env.local.example @@ -0,0 +1,4 @@ +REDIS_URL= +NEXT_PUBLIC_AUTH0_CLIENT_ID= +NEXT_PUBLIC_AUTH0_DOMAIN= +NEXT_PUBLIC_AUTH0_ADMIN_EMAIL= diff --git a/examples/custom-server/.gitignore b/examples/blog-with-comment/.gitignore similarity index 100% rename from examples/custom-server/.gitignore rename to examples/blog-with-comment/.gitignore diff --git a/examples/blog-with-comment/.prettierrc b/examples/blog-with-comment/.prettierrc new file mode 100644 index 0000000000000..fd496a820ea94 --- /dev/null +++ b/examples/blog-with-comment/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "semi": false +} diff --git a/examples/blog-with-comment/README.md b/examples/blog-with-comment/README.md new file mode 100644 index 0000000000000..3854144b5958a --- /dev/null +++ b/examples/blog-with-comment/README.md @@ -0,0 +1,65 @@ +# Blog with Comment + +This project adds commenting functionality to [Next.js blog application](https://github.com/vercel/next.js/tree/canary/examples/blog) using Upstash and Auth0. + +The comment box requires Auth0 authentication for users to add new comments. A user can delete their own comment. Also admin user can delete any comment. + +Comments are stored in Serverless Redis ([Upstash](http://upstash.com/)). + +### Demo + +[https://blog-with-comment.vercel.app/](https://blog-with-comment.vercel.app/) + +## `1` Project set up + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) +with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the +example: + +```bash +npx create-next-app --example blog-with-comment blog-with-comment-app +``` + +## `2` Set up environment variables + +Copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git): + +```bash +cp .env.local.example .env.local +``` + +## `3` Configuring Upstash + +Go to the [Upstash Console](https://console.upstash.com/) and create a new database + +#### Upstash environment + +- `REDIS_URL`: Find the URL in the database details page in Upstash Console clicking on **Redis Connect** button. + +## `4` Configuring Auth0 + +1. Go to the [Auth0 dashboard](https://manage.auth0.com/) and create a new application of type **Single Page Web + Applications**. +2. Go to the settings page of the application +3. Configure the following settings: + - **Allowed Callback URLs**: Should be set to `http://localhost:3000/` when testing locally or typically + to `https://myapp.com/` when deploying your application. + - **Allowed Logout URLs**: Should be set to `http://localhost:3000/` when testing locally or typically + to `https://myapp.com/` when deploying your application. + - **Allowed Web Origins**: Should be set to `http://localhost:3000` when testing locally or typically + to `https://myapp.com/` when deploying your application. +4. Save the settings. + +#### Auth0 environment + +- `NEXT_PUBLIC_AUTH0_DOMAIN`: Can be found in the Auth0 dashboard under `settings`. +- `NEXT_PUBLIC_AUTH0_CLIENT_ID`: Can be found in the Auth0 dashboard under `settings`. +- `NEXT_PUBLIC_AUTH0_ADMIN_EMAIL`: This is the email of the admin user which you use while signing in Auth0. Admin is able to delete any comment. + +## Deploy Your Local Project + +To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket +and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=upstash-roadmap). + +**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to +match your `.env.local` file. diff --git a/examples/blog-with-comment/_posts/long-expected-party.md b/examples/blog-with-comment/_posts/long-expected-party.md new file mode 100644 index 0000000000000..8dc7eb1b95bf9 --- /dev/null +++ b/examples/blog-with-comment/_posts/long-expected-party.md @@ -0,0 +1,18 @@ +--- +title: "A Long-expected Party" +excerpt: "Gandalf arrives in the Shire for Bilbo's Farewell Birthday Party. +Bilbo leaves the Shire permanently." +date: "2021-03-02" +--- + +Placeat consequuntur ullam aut sapiente illo velit. Eius facere ut molestias +totam laborum pariatur quam. Praesentium quo veritatis expedita animi. + +Quite anything glass benefit. Such form clearly top tend can require my. Federal +degree sort performance region maintain. + +Ut dignissimos sapiente culpa rerum pariatur consequatur. Corporis suscipit ad +corrupti aut. Expedita culpa aut deleniti officiis. + +Porro eum id sit quia expedita. Alias expedita asperiores. Corporis ex eum atque +cum ea. diff --git a/examples/blog-with-comment/_posts/passing-of-grey-company.md b/examples/blog-with-comment/_posts/passing-of-grey-company.md new file mode 100644 index 0000000000000..cb27ea6f465cd --- /dev/null +++ b/examples/blog-with-comment/_posts/passing-of-grey-company.md @@ -0,0 +1,18 @@ +--- +title: "The Passing of the Grey Company" +excerpt: "Aragorn, Legolas, and Gimli are accompanied by the Grey Company as +they pass through the Paths of the Dead between Rohan and Gondor." +date: "2021-01-22" +--- + +Placeat consequuntur ullam aut sapiente illo velit. Eius facere ut molestias +totam laborum pariatur quam. Praesentium quo veritatis expedita animi. + +Quite anything glass benefit. Such form clearly top tend can require my. Federal +degree sort performance region maintain. + +Ut dignissimos sapiente culpa rerum pariatur consequatur. Corporis suscipit ad +corrupti aut. Expedita culpa aut deleniti officiis. + +Porro eum id sit quia expedita. Alias expedita asperiores. Corporis ex eum atque +cum ea. diff --git a/examples/blog-with-comment/_posts/prancing-pony.md b/examples/blog-with-comment/_posts/prancing-pony.md new file mode 100644 index 0000000000000..7fa5123243e52 --- /dev/null +++ b/examples/blog-with-comment/_posts/prancing-pony.md @@ -0,0 +1,18 @@ +--- +title: "At the Sign of the Prancing Pony" +excerpt: "The Hobbits reach the The Prancing Pony inn at Bree, where Frodo uses +a false name, Underhill." +date: "2021-03-03" +--- + +Placeat consequuntur ullam aut sapiente illo velit. Eius facere ut molestias +totam laborum pariatur quam. Praesentium quo veritatis expedita animi. + +Quite anything glass benefit. Such form clearly top tend can require my. Federal +degree sort performance region maintain. + +Ut dignissimos sapiente culpa rerum pariatur consequatur. Corporis suscipit ad +corrupti aut. Expedita culpa aut deleniti officiis. + +Porro eum id sit quia expedita. Alias expedita asperiores. Corporis ex eum atque +cum ea. diff --git a/examples/blog-with-comment/_posts/riders-of-rohan.md b/examples/blog-with-comment/_posts/riders-of-rohan.md new file mode 100644 index 0000000000000..f67dd3b40205b --- /dev/null +++ b/examples/blog-with-comment/_posts/riders-of-rohan.md @@ -0,0 +1,18 @@ +--- +title: "The Riders of Rohan" +excerpt: "Aragorn, Legolas, and Gimli follow the trail of the Orcs and find +several clues as to what happened with Merry and Pippin." +date: "2021-02-01" +--- + +Placeat consequuntur ullam aut sapiente illo velit. Eius facere ut molestias +totam laborum pariatur quam. Praesentium quo veritatis expedita animi. + +Quite anything glass benefit. Such form clearly top tend can require my. Federal +degree sort performance region maintain. + +Ut dignissimos sapiente culpa rerum pariatur consequatur. Corporis suscipit ad +corrupti aut. Expedita culpa aut deleniti officiis. + +Porro eum id sit quia expedita. Alias expedita asperiores. Corporis ex eum atque +cum ea. diff --git a/examples/blog-with-comment/components/comment/form.js b/examples/blog-with-comment/components/comment/form.js new file mode 100644 index 0000000000000..fee2240a88b3b --- /dev/null +++ b/examples/blog-with-comment/components/comment/form.js @@ -0,0 +1,45 @@ +import { useAuth0 } from '@auth0/auth0-react' + +function CommentForm({ text, setText, onSubmit }) { + const { isAuthenticated, logout, loginWithPopup } = useAuth0() + + return ( +
+ - -
- {'content' in (error || {}) && {error.content}} -
- -
- {typeof error === 'string' && {error}} -
- ) -} - -export default AddNoteForm diff --git a/examples/with-redux-toolkit/components/clock.js b/examples/with-redux-toolkit/components/clock.js deleted file mode 100644 index 269563a0a2408..0000000000000 --- a/examples/with-redux-toolkit/components/clock.js +++ /dev/null @@ -1,33 +0,0 @@ -import { useSelector } from 'react-redux' - -import { selectClock } from '../lib/slices/clockSlice' - -const formatTime = (time) => { - // cut off except hh:mm:ss - return new Date(time).toJSON().slice(11, 19) -} - -const Clock = () => { - const { lastUpdate, light } = useSelector(selectClock) - - return ( -
- {formatTime(lastUpdate)} - -
- ) -} - -export default Clock diff --git a/examples/with-redux-toolkit/components/counter.js b/examples/with-redux-toolkit/components/counter.js deleted file mode 100644 index 771065eeed1e5..0000000000000 --- a/examples/with-redux-toolkit/components/counter.js +++ /dev/null @@ -1,155 +0,0 @@ -import { useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' - -import { - decrement, - increment, - incrementAsync, - incrementByAmount, - reset, - selectCount, -} from '../lib/slices/counterSlice' - -const Counter = () => { - const dispatch = useDispatch() - const count = useSelector(selectCount) - const [incrementAmount, setIncrementAmount] = useState('2') - - function dispatchIncrement() { - dispatch(increment()) - } - function dispatchDecrement() { - dispatch(decrement()) - } - function dispatchReset() { - dispatch(reset()) - } - function changeIncrementAmount(event) { - setIncrementAmount(event.target.value) - } - function dispatchIncrementByAmount() { - dispatch(incrementByAmount(Number(incrementAmount) || 0)) - } - function dispatchIncrementAsync() { - dispatch(incrementAsync(Number(incrementAmount) || 0)) - } - - return ( - <> -
- - {count} - -
-
- - - -
-
- -
- - - ) -} - -export default Counter diff --git a/examples/with-redux-toolkit/components/edit-note.js b/examples/with-redux-toolkit/components/edit-note.js deleted file mode 100644 index d841b8ef6baf6..0000000000000 --- a/examples/with-redux-toolkit/components/edit-note.js +++ /dev/null @@ -1,54 +0,0 @@ -import { useLayoutEffect, useRef } from 'react' -import { useDispatch } from 'react-redux' - -import { editNote } from '../lib/slices/notesSlice' -import useForm from '../lib/useForm' - -const EditNoteForm = ({ note = {} }) => { - const dialogRef = useRef() - const dispatch = useDispatch() - const handleSubmit = useForm(note) - - useLayoutEffect(() => { - const isOpen = Object.keys(note).length > 0 - if (isOpen) { - dialogRef.current.setAttribute('open', true) - } - }, [note]) - - return ( - -
{ - await dispatch(editNote(data)) - dialogRef.current.removeAttribute('open') - })} - > -

Edit Note

- -
- -
- -
-
-
- ) -} - -export default EditNoteForm diff --git a/examples/with-redux-toolkit/lib/slices/clockSlice.js b/examples/with-redux-toolkit/lib/slices/clockSlice.js deleted file mode 100644 index e124758bcccc4..0000000000000 --- a/examples/with-redux-toolkit/lib/slices/clockSlice.js +++ /dev/null @@ -1,21 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit' - -const clockSlice = createSlice({ - name: 'clock', - initialState: { - lastUpdate: 0, - light: true, - }, - reducers: { - tick: (state, action) => { - state.lastUpdate = action.payload.lastUpdate - state.light = !!action.payload.light - }, - }, -}) - -export const selectClock = (state) => state.clock - -export const { tick } = clockSlice.actions - -export default clockSlice.reducer diff --git a/examples/with-redux-toolkit/lib/slices/counterSlice.js b/examples/with-redux-toolkit/lib/slices/counterSlice.js deleted file mode 100644 index 6f29b261967a8..0000000000000 --- a/examples/with-redux-toolkit/lib/slices/counterSlice.js +++ /dev/null @@ -1,53 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit' - -const counterSlice = createSlice({ - name: 'counter', - initialState: { - value: 0, - }, - reducers: { - // Redux Toolkit allows us to write "mutating" logic in reducers. It - // doesn't actually mutate the state because it uses the Immer library, - // which detects changes to a "draft state" and produces a brand new - // immutable state based off those changes - increment: (state) => { - state.value += 1 - }, - decrement: (state) => { - state.value -= 1 - }, - reset: (state) => { - state.value = 0 - }, - incrementByAmount: (state, action) => { - state.value += action.payload - }, - }, -}) - -/** - * Extract count from root state - * - * @param {Object} state The root state - * @returns {number} The current count - */ -export const selectCount = (state) => state.counter.value - -export const { - increment, - decrement, - reset, - incrementByAmount, -} = counterSlice.actions - -// The function below is called a thunk and allows us to perform async logic. It -// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This -// will call the thunk with the `dispatch` function as the first argument. Async -// code can then be executed and other actions can be dispatched -export const incrementAsync = (amount) => (dispatch) => { - setTimeout(() => { - dispatch(incrementByAmount(amount)) - }, 1000) -} - -export default counterSlice.reducer diff --git a/examples/with-redux-toolkit/lib/slices/notesSlice.js b/examples/with-redux-toolkit/lib/slices/notesSlice.js deleted file mode 100644 index d2a0494f156c2..0000000000000 --- a/examples/with-redux-toolkit/lib/slices/notesSlice.js +++ /dev/null @@ -1,148 +0,0 @@ -import { createAsyncThunk, createSelector, createSlice } from '@reduxjs/toolkit' - -export const addNote = createAsyncThunk( - 'notes/addNote', - async (newNote, thunkAPI) => { - try { - const response = await fetch('/api/notes', { - method: 'POST', - body: JSON.stringify(newNote), - headers: { - 'Content-Type': 'application/json', - }, - }) - - if (!response.ok) { - const error = await response.json() - - return thunkAPI.rejectWithValue({ error: error.errors }) - } - - return response.json() - } catch (error) { - return thunkAPI.rejectWithValue({ error: error.message }) - } - } -) - -export const loadNotes = createAsyncThunk( - 'notes/loadNotes', - async (_, thunkAPI) => { - try { - const response = await fetch('/api/notes') - - return response.json() - } catch (error) { - return thunkAPI.rejectWithValue({ error: error.message }) - } - } -) - -export const editNote = createAsyncThunk( - 'notes/editNote', - async (updates, thunkAPI) => { - const { id, title, content } = updates - - try { - const response = await fetch(`/api/notes?noteId=${id}`, { - method: 'PUT', - body: JSON.stringify({ title, content }), - headers: { - 'Content-Type': 'application/json', - }, - }) - - return response.json() - } catch (error) { - return thunkAPI.rejectWithValue({ error: error.message }) - } - } -) - -export const deleteNote = createAsyncThunk( - 'notes/deleteNote', - async (id, thunkAPI) => { - try { - await fetch(`/api/notes?noteId=${id}`, { method: 'DELETE' }) - return id - } catch (error) { - return thunkAPI.rejectWithValue({ error: error.message }) - } - } -) - -const notesSlice = createSlice({ - name: 'notes', - initialState: { - notes: [], - loading: 'idle', - }, - reducers: {}, - extraReducers: { - [addNote.pending]: (state) => { - delete state.error - }, - [addNote.fulfilled]: (state, action) => { - state.notes.push(action.payload) - }, - [addNote.rejected]: (state, action) => { - state.error = action.payload.error - }, - [loadNotes.pending]: (state) => { - state.notes = [] - state.loading = 'loading' - }, - [loadNotes.fulfilled]: (state, action) => { - state.notes = action.payload - state.loading = 'loaded' - }, - [loadNotes.rejected]: (state, action) => { - state.loading = 'error' - state.error = action.payload.error - }, - [editNote.pending]: (state, action) => { - const note = state.notes.find((note) => note.id === action.meta.arg.id) - state.tempNote = Object.assign({}, note) - note.title = action.meta.arg.title || note.title - note.content = action.meta.arg.content || note.content - }, - [editNote.fulfilled]: (state, action) => { - const note = state.notes.find((note) => note.id === action.payload.id) - delete state.tempNote - Object.assign(note, action.payload) - }, - [editNote.rejected]: (state, action) => { - const note = state.notes.find((note) => note.id === action.meta.arg.id) - state.error = action.payload.error || action.error.message - Object.assign(note, state.tempNote) - delete state.tempNote - }, - [deleteNote.pending]: (state, action) => { - const position = state.notes.findIndex( - (note) => note.id === action.meta.arg - ) - state.backupNote = Object.assign({}, state.notes[position]) - state.backupPosition = position - state.notes.splice(position, 1) - }, - [deleteNote.fulfilled]: (state) => { - delete state.backupNote - delete state.backupPosition - }, - [deleteNote.rejected]: (state) => { - state.notes.splice(state.backupPosition, 0, state.backupNote) - delete state.backupPosition - delete state.backupNote - }, - }, -}) - -export const selectNotes = createSelector( - (state) => ({ - notes: state.notes.notes, - error: state.notes.error, - }), - (state) => state -) - -export default notesSlice.reducer diff --git a/examples/with-redux-toolkit/lib/useForm.js b/examples/with-redux-toolkit/lib/useForm.js deleted file mode 100644 index eab1cb928322e..0000000000000 --- a/examples/with-redux-toolkit/lib/useForm.js +++ /dev/null @@ -1,18 +0,0 @@ -const useForm = (defaultValues = {}) => (handler) => async (event) => { - event.preventDefault() - event.persist() - const form = event.target - const data = Array.from(form.elements) - .filter((element) => element.hasAttribute('name')) - .reduce( - (object, element) => ({ - ...object, - [element.name]: element.value, - }), - defaultValues - ) - await handler(data) - form.reset() -} - -export default useForm diff --git a/examples/with-redux-toolkit/lib/useInterval.js b/examples/with-redux-toolkit/lib/useInterval.js deleted file mode 100644 index 066d08ee254b5..0000000000000 --- a/examples/with-redux-toolkit/lib/useInterval.js +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect, useRef } from 'react' - -// https://overreacted.io/making-setinterval-declarative-with-react-hooks/ -const useInterval = (callback, delay) => { - const savedCallback = useRef() - useEffect(() => { - savedCallback.current = callback - }, [callback]) - useEffect(() => { - const handler = (...args) => savedCallback.current(...args) - - if (delay !== null) { - const id = setInterval(handler, delay) - return () => clearInterval(id) - } - }, [delay]) -} - -export default useInterval diff --git a/examples/with-redux-toolkit/package.json b/examples/with-redux-toolkit/package.json deleted file mode 100644 index c570a09083459..0000000000000 --- a/examples/with-redux-toolkit/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "with-redux-toolkit", - "version": "1.0.0", - "scripts": { - "dev": "next", - "build": "next build", - "start": "next start" - }, - "dependencies": { - "@nano-sql/core": "^2.3.7", - "@reduxjs/toolkit": "^1.3.6", - "next": "latest", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "react-redux": "^7.2.0" - }, - "license": "MIT" -} diff --git a/examples/with-redux-toolkit/pages/_app.js b/examples/with-redux-toolkit/pages/_app.js deleted file mode 100644 index 0ceac76fabe3b..0000000000000 --- a/examples/with-redux-toolkit/pages/_app.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Provider } from 'react-redux' - -import store from '../store' - -const MyApp = ({ Component, pageProps }) => { - return ( - - - - ) -} - -export default MyApp diff --git a/examples/with-redux-toolkit/pages/api/notes.js b/examples/with-redux-toolkit/pages/api/notes.js deleted file mode 100644 index 640885f99e35d..0000000000000 --- a/examples/with-redux-toolkit/pages/api/notes.js +++ /dev/null @@ -1,112 +0,0 @@ -import { nSQL } from '@nano-sql/core' - -const connectMiddleware = (handler) => async (req, res) => { - const dbName = 'with-redux-toolkit' - - if (!nSQL().listDatabases().includes(dbName)) { - await nSQL().createDatabase({ - id: dbName, - mode: 'PERM', - tables: [ - { - name: 'notes', - model: { - 'id:uuid': { pk: true }, - 'title:string': { notNull: true }, - 'content:string': { notNull: true }, - 'createdAt:date': { default: () => new Date() }, - }, - }, - ], - version: 1, - }) - } - nSQL().useDatabase(dbName) - - return handler(req, res) -} -const saveNote = async (req, res) => { - const { title, content } = req.body - const errors = {} - - if (!title) errors['title'] = 'Title is required' - - if (!content) errors['content'] = 'Content is required' - - if (Object.keys(errors).length > 0) - return res.status(422).json({ - statusCode: 422, - message: 'Unprocessable Entity', - errors, - }) - - const [note] = await nSQL('notes').query('upsert', { title, content }).exec() - - res.status(201).json(note) -} -const listNotes = async (_, res) => { - const notes = await nSQL('notes').query('select').exec() - - res.json(notes) -} -const updateNote = async (req, res) => { - const { noteId } = req.query - const [note] = await nSQL() - .query('select') - .where(['id', '=', noteId]) - .limit(1) - .exec() - - if (!note) - return res.status(404).json({ - statusCode: 404, - message: 'Not Found', - }) - - const { title = note.title, content = note.content } = req.body - const [noteUpdated] = await nSQL('notes') - .query('upsert', { title, content }) - .where(['id', '=', noteId]) - .limit(1) - .exec() - - res.json(noteUpdated) -} -const removeNote = async (req, res) => { - const { noteId } = req.query - const [note] = await nSQL() - .query('select') - .where(['id', '=', noteId]) - .limit(1) - .exec() - - if (!note) - return res.status(404).json({ - statusCode: 404, - message: 'Not Found', - }) - - await nSQL('notes').query('delete').where(['id', '=', noteId]).limit(1).exec() - - res.status(204).send(null) -} - -const handler = (req, res) => { - switch (req.method) { - case 'POST': - return saveNote(req, res) - case 'GET': - return listNotes(req, res) - case 'PUT': - return updateNote(req, res) - case 'DELETE': - return removeNote(req, res) - default: - return res.status(404).json({ - statusCode: 404, - message: 'Not Found', - }) - } -} - -export default connectMiddleware(handler) diff --git a/examples/with-redux-toolkit/pages/index.js b/examples/with-redux-toolkit/pages/index.js deleted file mode 100644 index da290a63db2c4..0000000000000 --- a/examples/with-redux-toolkit/pages/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import { useDispatch } from 'react-redux' - -import Clock from '../components/clock' -import Counter from '../components/counter' -import { tick } from '../lib/slices/clockSlice' -import useInterval from '../lib/useInterval' - -const IndexPage = () => { - const dispatch = useDispatch() - // Tick the time every second - useInterval(() => { - dispatch(tick({ light: true, lastUpdate: Date.now() })) - }, 1000) - - return ( - <> - - - - ) -} - -export default IndexPage diff --git a/examples/with-redux-toolkit/pages/notes.js b/examples/with-redux-toolkit/pages/notes.js deleted file mode 100644 index b535b3060cbdd..0000000000000 --- a/examples/with-redux-toolkit/pages/notes.js +++ /dev/null @@ -1,57 +0,0 @@ -import Dynamic from 'next/dynamic' -import Head from 'next/head' -import { useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' - -import AddNoteForm from '../components/add-note' -import { deleteNote, loadNotes, selectNotes } from '../lib/slices/notesSlice' - -const EditNoteForm = Dynamic(import('../components/edit-note'), { ssr: false }) -const Notes = () => { - const [selectedNote, setSelectedNote] = useState() - const dispatch = useDispatch() - const { notes } = useSelector(selectNotes) - - useEffect(() => { - async function dispatchLoadNotes() { - await dispatch(loadNotes()) - } - dispatchLoadNotes() - }, [dispatch]) - - const renderNote = (note) => ( -
  • - {note.title} -
    - {note.content} -
    - - -
  • - ) - - return ( - <> - - Next.js with Redux Toolkit | Notes App - - -
    -

    All Notes

    -
      {notes.map(renderNote)}
    - - - ) -} - -export default Notes diff --git a/examples/with-redux-toolkit/store.js b/examples/with-redux-toolkit/store.js deleted file mode 100644 index 0aed7ecf13d32..0000000000000 --- a/examples/with-redux-toolkit/store.js +++ /dev/null @@ -1,14 +0,0 @@ -import { configureStore } from '@reduxjs/toolkit' - -import clockReducer from './lib/slices/clockSlice' -import counterReducer from './lib/slices/counterSlice' -import notesReducer from './lib/slices/notesSlice' - -export default configureStore({ - reducer: { - counter: counterReducer, - clock: clockReducer, - notes: notesReducer, - }, - devTools: true, -}) diff --git a/examples/with-redux-wrapper/package.json b/examples/with-redux-wrapper/package.json index 2e64d346a9a8e..1f8fbca12c9cb 100644 --- a/examples/with-redux-wrapper/package.json +++ b/examples/with-redux-wrapper/package.json @@ -1,6 +1,5 @@ { - "name": "with-redux-wrapper", - "version": "1.0.0", + "private": true, "scripts": { "dev": "next", "build": "next build", @@ -8,13 +7,12 @@ }, "dependencies": { "next": "9.4.1", - "next-redux-wrapper": "^6.0.1", - "react": "^16.12.0", - "react-dom": "^16.12.0", + "next-redux-wrapper": "^7.0.2", + "react": "^17.0.2", + "react-dom": "^17.0.2", "react-redux": "7.1.3", "redux": "4.0.5", "redux-devtools-extension": "2.13.8", "redux-thunk": "2.3.0" - }, - "license": "MIT" + } } diff --git a/examples/with-redux-wrapper/pages/index.js b/examples/with-redux-wrapper/pages/index.js index 1030b9380e427..07fb7fcfbdb2c 100644 --- a/examples/with-redux-wrapper/pages/index.js +++ b/examples/with-redux-wrapper/pages/index.js @@ -18,7 +18,7 @@ const Index = (props) => { return } -export const getStaticProps = wrapper.getStaticProps(async ({ store }) => { +export const getStaticProps = wrapper.getStaticProps((store) => () => { store.dispatch(serverRenderClock(true)) store.dispatch(addCount()) }) diff --git a/examples/with-redux-wrapper/pages/other.js b/examples/with-redux-wrapper/pages/other.js index 13b69f550220c..baddaee9b8a90 100644 --- a/examples/with-redux-wrapper/pages/other.js +++ b/examples/with-redux-wrapper/pages/other.js @@ -18,12 +18,10 @@ const Other = (props) => { return } -export const getServerSideProps = wrapper.getServerSideProps( - async ({ store }) => { - store.dispatch(serverRenderClock(true)) - store.dispatch(addCount()) - } -) +export const getServerSideProps = wrapper.getServerSideProps((store) => () => { + store.dispatch(serverRenderClock(true)) + store.dispatch(addCount()) +}) const mapDispatchToProps = (dispatch) => { return { diff --git a/examples/with-redux/README.md b/examples/with-redux/README.md index 36106fb648a59..b25fa6e8435cf 100644 --- a/examples/with-redux/README.md +++ b/examples/with-redux/README.md @@ -1,18 +1,8 @@ -# Redux example +# Redux Toolkit TypeScript Example -This example shows how to integrate Redux in Next.js. +This example shows how to integrate Next.js with [Redux Toolkit](https://redux-toolkit.js.org). -Usually splitting your app state into `pages` feels natural but sometimes you'll want to have global state for your app. This is an example on how you can use Redux that also works with Next.js's universal rendering approach. - -In the first example we are going to display a digital clock that updates every second. The first render is happening in the server and then the browser will take over. To illustrate this, the server rendered clock will have a different background color (black) than the client one (grey). - -To illustrate SSG and SSR, go to `/ssg` and `/ssr`, those pages are using Next.js data fetching methods to get the date in the server and return it as props to the page, and then the browser will hydrate the store and continue updating the date. - -The trick here for supporting universal Redux is to separate the cases for the client and the server. When we are on the server we want to create a new store every time, otherwise different users data will be mixed up. If we are in the client we want to use always the same store. That's what we accomplish on `store.js`. - -All components have access to the Redux store using `useSelector`, `useDispatch` or `connect` from `react-redux`. - -On the server side every request initializes a new store, because otherwise different user data can be mixed up. On the client side the same store is used, even between page changes. +The **Redux Toolkit** is a standardized way to write Redux logic (create actions and reducers, setup the store with some default middlewares like redux devtools extension). This example demonstrates each of these features with Next.js ## Deploy your own diff --git a/examples/with-redux/components/clock.js b/examples/with-redux/components/clock.js deleted file mode 100644 index 7cabc8e5ac8b4..0000000000000 --- a/examples/with-redux/components/clock.js +++ /dev/null @@ -1,40 +0,0 @@ -import { useSelector, shallowEqual } from 'react-redux' - -const useClock = () => { - return useSelector( - (state) => ({ - lastUpdate: state.lastUpdate, - light: state.light, - }), - shallowEqual - ) -} - -const formatTime = (time) => { - // cut off except hh:mm:ss - return new Date(time).toJSON().slice(11, 19) -} - -const Clock = () => { - const { lastUpdate, light } = useClock() - return ( -
    - {formatTime(lastUpdate)} - -
    - ) -} - -export default Clock diff --git a/examples/with-redux/components/counter.js b/examples/with-redux/components/counter.js deleted file mode 100644 index 12cecc8861867..0000000000000 --- a/examples/with-redux/components/counter.js +++ /dev/null @@ -1,35 +0,0 @@ -import { useSelector, useDispatch } from 'react-redux' - -const useCounter = () => { - const count = useSelector((state) => state.count) - const dispatch = useDispatch() - const increment = () => - dispatch({ - type: 'INCREMENT', - }) - const decrement = () => - dispatch({ - type: 'DECREMENT', - }) - const reset = () => - dispatch({ - type: 'RESET', - }) - return { count, increment, decrement, reset } -} - -const Counter = () => { - const { count, increment, decrement, reset } = useCounter() - return ( -
    -

    - Count: {count} -

    - - - -
    - ) -} - -export default Counter diff --git a/examples/with-redux/components/nav.js b/examples/with-redux/components/nav.js deleted file mode 100644 index 9c9ead65ecccb..0000000000000 --- a/examples/with-redux/components/nav.js +++ /dev/null @@ -1,26 +0,0 @@ -import Link from 'next/link' - -const Nav = () => { - return ( - - ) -} - -export default Nav diff --git a/examples/with-redux/components/page.js b/examples/with-redux/components/page.js deleted file mode 100644 index 21ef1bb4e9c7f..0000000000000 --- a/examples/with-redux/components/page.js +++ /dev/null @@ -1,26 +0,0 @@ -import { useDispatch } from 'react-redux' -import useInterval from '../lib/useInterval' -import Clock from './clock' -import Counter from './counter' -import Nav from './nav' - -export default function Page() { - const dispatch = useDispatch() - - // Tick the time every second - useInterval(() => { - dispatch({ - type: 'TICK', - light: true, - lastUpdate: Date.now(), - }) - }, 1000) - - return ( - <> -