diff --git a/.alexignore b/.alexignore new file mode 100644 index 000000000000..1bf6581c26b1 --- /dev/null +++ b/.alexignore @@ -0,0 +1,2 @@ +CODE_OF_CONDUCT.md +examples/ diff --git a/.alexrc b/.alexrc new file mode 100644 index 000000000000..157d1da8cca5 --- /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 37e0a229afb6..40f0064d34df 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,10 @@ 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/development/basic/legacy-decorators/**/* +test-timings.json +packages/next-swc/crates/** +bench/nested-deps/pages/** +bench/nested-deps/components/** diff --git a/.eslintrc.json b/.eslintrc.json index 2a93dc678591..26824c94ba66 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": { @@ -23,11 +30,15 @@ }, "overrides": [ { - "files": ["test/**/*.test.js"], + "files": ["test/**/*.js", "test/**/*.ts", "**/*.test.ts"], "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", + "jest/no-export": "off" } }, { "files": ["**/__tests__/**"], "env": { "jest": true } }, diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..08f58f77658a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +packages/next/bundles/** -text +packages/next/compiled/** -text diff --git a/.github/.kodiak.toml b/.github/.kodiak.toml index a90b3113f653..ee535afe225c 100644 --- a/.github/.kodiak.toml +++ b/.github/.kodiak.toml @@ -13,6 +13,7 @@ notify_on_conflict = false [merge.message] title = "pull_request_title" body = "pull_request_body" +include_coauthors= true include_pr_number = true body_type = "markdown" strip_html_comments = true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6f97a392b21a..76e9888bb1b2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,24 @@ # Learn how to add code owners here: # https://help.github.com/en/articles/about-code-owners -* @timneutkens @Timer @ijjk @lfades -/docs/ @timneutkens @Timer @ijjk @lfades @chibicode -/examples/ @timneutkens @Timer @ijjk @lfades @chibicode +* @timneutkens @ijjk @shuding @huozhi +/.github/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia +/docs/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia @leerob @lfades @molebox +/examples/ @timneutkens @ijjk @shuding @leerob @lfades + +# SWC Build (@padmaia) + +/packages/next/build/ @timneutkens @ijjk @shuding @padmaia @huozhi + +# Image Component (@styfle) + +/examples/image-component/ @timneutkens @ijjk @shuding @styfle +/packages/next/build/webpack/loaders/next-image-loader.js @timneutkens @ijjk @shuding @styfle +/packages/next/client/image.tsx @timneutkens @ijjk @shuding @styfle +/packages/next/image-types/ @timneutkens @ijjk @shuding @styfle +/packages/next/server/image-config.ts @timneutkens @ijjk @shuding @styfle +/packages/next/server/image-optimizer.ts @timneutkens @ijjk @shuding @styfle +/packages/next/server/serve-static.ts @timneutkens @ijjk @shuding @styfle +/packages/next/server/config.ts @timneutkens @ijjk @shuding @styfle +/test/integration/image-optimizer/ @timneutkens @ijjk @shuding @styfle +/test/integration/image-component/ @timneutkens @ijjk @shuding @styfle diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml index d6b3957f5a83..440f9434bf33 100644 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -1,67 +1,73 @@ 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 -inputs: - - type: description +body: + - type: markdown attributes: value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible. - - type: description + - type: markdown attributes: value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section. - - type: description + - type: markdown attributes: value: 'Please first verify if your issue exists in the Next.js canary release line: `npm install next@canary`.' - - type: description + - type: markdown attributes: value: 'next@canary is the beta version of Next.js. It includes all features and fixes that are pending to land on the stable release line.' - type: input attributes: label: What version of Next.js are you using? description: 'For example: 10.0.1' + validations: required: true - type: input attributes: label: What version of Node.js are you using? description: 'For example: 12.0.0' + validations: required: true - type: input attributes: label: What browser are you using? description: 'For example: Chrome, Safari' + validations: required: true - type: input attributes: label: What operating system are you using? description: 'For example: macOS, Windows' + validations: required: true - type: input attributes: label: How are you deploying your application? description: 'For example: next start, next export, Vercel, Other platform' + validations: required: true - type: textarea attributes: label: Describe the Bug description: A clear and concise description of what the bug is. + validations: required: true - type: textarea attributes: label: Expected Behavior description: A clear and concise description of what you expected to happen. + validations: required: true - type: textarea attributes: label: To Reproduce description: Steps to reproduce the behavior, please provide a clear code snippets that always reproduces the issue or a GitHub repository. Screenshots can be provided in the issue body below. + validations: required: true - - type: description + - type: markdown attributes: value: Before posting the issue go through the steps you've written down to make sure the steps provided are detailed and clear. - - type: description + - type: markdown attributes: value: Contributors should be able to follow the steps provided in order to reproduce the bug. - - type: description + - type: markdown attributes: value: These steps are used to add integration tests to ensure the same issue does not happen again. Thanks in advance! diff --git a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml index 25da453dfd7a..8535a441bf4a 100644 --- a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml @@ -1,66 +1,73 @@ 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 -inputs: - - type: description +body: + - type: markdown attributes: value: Thanks for taking the time to file a examples bug report! Please fill out this form as completely as possible. - - type: description + - type: markdown attributes: value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section. - type: input attributes: label: What example does this report relate to? description: 'For example: with-styled-components' + validations: required: true - type: input attributes: label: What version of Next.js are you using? description: 'For example: 10.0.1' + validations: required: true - type: input attributes: label: What version of Node.js are you using? description: 'For example: 12.0.0' + validations: required: true - type: input attributes: label: What browser are you using? description: 'For example: Chrome, Safari' + validations: required: true - type: input attributes: label: What operating system are you using? description: 'For example: macOS, Windows' + validations: required: true - type: input attributes: label: How are you deploying your application? description: 'For example: next start, next export, Vercel, Other platform' + validations: required: true - type: textarea attributes: label: Describe the Bug description: A clear and concise description of what the bug is. + validations: required: true - type: textarea attributes: label: Expected Behavior description: A clear and concise description of what you expected to happen. + validations: required: true - type: textarea attributes: label: To Reproduce description: Steps to reproduce the behavior, please provide a clear code snippets that always reproduces the issue or a GitHub repository. Screenshots can be provided in the issue body below. + validations: required: true - - type: description + - type: markdown attributes: value: Before posting the issue go through the steps you've written down to make sure the steps provided are detailed and clear. - - type: description + - type: markdown attributes: value: Contributors should be able to follow the steps provided in order to reproduce the bug. - - type: description + - type: markdown attributes: value: Thanks in advance! diff --git a/.github/ISSUE_TEMPLATE/3.feature_request.yml b/.github/ISSUE_TEMPLATE/3.feature_request.yml index c4fbcccf7078..2655aff44d14 100644 --- a/.github/ISSUE_TEMPLATE/3.feature_request.yml +++ b/.github/ISSUE_TEMPLATE/3.feature_request.yml @@ -1,27 +1,28 @@ 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 -inputs: - - type: description +body: + - type: markdown attributes: value: Thanks for taking the time to file a feature request! Please fill out this form as completely as possible. - - type: description + - type: markdown attributes: value: 'Feature requests will be converted to the GitHub Discussions "Ideas" section.' - type: textarea attributes: label: Describe the feature you'd like to request description: A clear and concise description of what you want and what your use case is. + validations: required: true - type: textarea attributes: label: Describe the solution you'd like description: A clear and concise description of what you want to happen. + validations: required: true - type: textarea attributes: label: Describe alternatives you've considered description: A clear and concise description of any alternative solutions or features you've considered. + validations: required: true diff --git a/.github/actions/next-stats-action/Dockerfile b/.github/actions/next-stats-action/Dockerfile index bf86727921a9..f533f918d9d2 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 e483ffcfe64b..2f2d9afc1578 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 0b39ed8a2e67..52b9fd32ab91 100644 --- a/.github/actions/next-stats-action/src/index.js +++ b/.github/actions/next-stats-action/src/index.js @@ -45,6 +45,10 @@ if (!allowedActions.has(actionInfo.actionName) && !actionInfo.isRelease) { await checkoutRef(actionInfo.prRef, diffRepoDir) } + if (actionInfo.isRelease) { + process.env.STATS_IS_RELEASE = 'true' + } + // load stats config from allowed locations const { statsConfig, relativeStatsAppDir } = loadStatsConfig() @@ -102,14 +106,27 @@ 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 }) } + await fs.copy( + path.join(__dirname, '../native'), + path.join(dir, 'packages/next-swc/native') + ) + // TODO: remove after next stable release (current v12.0.4) + await fs.copy( + path.join(__dirname, '../native'), + path.join(dir, 'packages/next/native') + ) logger(`Linking packages in ${dir}`) const pkgPaths = await linkPackages(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 534ab34229e5..d06ce49d2dc1 100644 --- a/.github/actions/next-stats-action/src/prepare/repo-setup.js +++ b/.github/actions/next-stats-action/src/prepare/repo-setup.js @@ -73,6 +73,10 @@ module.exports = (actionInfo) => { const packedPkgPath = path.join(pkgPath, `${pkg}-packed.tgz`) const pkgDataPath = path.join(pkgPath, 'package.json') + if (!fs.existsSync(pkgDataPath)) { + console.log(`Skipping ${pkgDataPath}`) + continue + } const pkgData = require(pkgDataPath) const { name } = pkgData pkgDatas.set(name, { @@ -93,6 +97,25 @@ 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/swc') { + if (!pkgData.files) { + pkgData.files = [] + } + pkgData.files.push('native') + console.log( + 'using swc binaries: ', + await exec(`ls ${path.join(path.dirname(pkgDataPath), 'native')}`) + ) + } + if (pkg === 'next') { + if (pkgDatas.get('@next/swc')) { + pkgData.dependencies['@next/swc'] = + pkgDatas.get('@next/swc').packedPkgPath + } else { + pkgData.files.push('native') + } + } await fs.writeFile( pkgDataPath, JSON.stringify(pkgData, null, 2), @@ -104,7 +127,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 e8ddf32dea70..f0dc07bd87a0 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]{16}(\.|-)/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 da8660336f90..ead33c713c82 100644 --- a/.github/labeler.json +++ b/.github/labeler.json @@ -1,13 +1,42 @@ { "labels": { - "type: example": ["examples/**"], - "type: documentation": ["docs/**", "errors/**"], - "type: create-next-app": ["packages/create-next-app/**"], + "area: examples": ["examples/**"], + "area: documentation": ["docs/**", "errors/**"], + "area: create-next-app": ["packages/create-next-app/**"], "type: next": [ "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/**", + "packages/next-swc/**" + ], + "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": "kdy1" }, + { "type": "user", "pattern": "timneutkens" } + ], + "created-by: Next.js docs team": [ + { "type": "user", "pattern": "MaedahBatool" }, + { "type": "user", "pattern": "molebox" }, + { "type": "user", "pattern": "ismaelrumzan" } ] } } diff --git a/.github/lock.yml b/.github/lock.yml index a836a1e1e94a..ae1e70313fe4 100644 --- a/.github/lock.yml +++ b/.github/lock.yml @@ -1,6 +1,6 @@ # Configuration for lock-threads - https://github.com/dessant/lock-threads # Number of days of inactivity before a closed issue or pull request is locked -daysUntilLock: 365 +daysUntilLock: 45 # Comment to post before locking. Set to `false` to disable -lockComment: false +lockComment: 'This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please open a new issue with a reproduction. Thank you.' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000000..dd585c536542 --- /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 by running `yarn lint` diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 176952e1e6a6..0e49e6e14444 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -7,17 +7,41 @@ 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 + # we build a dev binary for use in CI so skip downloading + # canary next-swc binaries in the monorepo + NEXT_SKIP_NATIVE_POSTINSTALL: 1 outputs: docsChange: ${{ steps.docs-change.outputs.DOCS_CHANGE }} + isRelease: ${{ steps.check-release.outputs.IS_RELEASE }} steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + - 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,21 +49,37 @@ 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/* + - 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: path: ./* - key: ${{ github.sha }} + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} lint: runs-on: ubuntu-latest needs: build steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + - uses: actions/cache@v2 id: restore-build with: path: ./* - key: ${{ github.sha }} + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + - run: ./scripts/check-manifests.js - run: yarn lint checkPrecompiled: @@ -49,262 +89,1274 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + run: sudo ethtool -K eth0 tx off rx off + + - uses: actions/checkout@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - run: mv .git .git-bak + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - 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 + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - run: rm -rf .git && mv .git-bak .git if: ${{needs.build.outputs.docsChange != 'docs only change'}} + - run: ./scripts/check-pre-compiled.sh + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - uses: EndBug/add-and-commit@v7 + if: ${{ failure() }} + with: + add: 'packages/next/compiled packages/next/bundles --force' + message: '⚙ Update compiled files' + testUnit: name: Test Unit runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 - HEADLESS: true steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + - uses: actions/cache@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* - key: ${{ github.sha }} + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} - - run: node run-tests.js --timings --type unit -g 1/1 + - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native - testIntegration: - name: Test Integration + - 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 + needs: [build, build-native-dev] env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 - HEADLESS: true - strategy: - fail-fast: false - matrix: - group: [1, 2, 3, 4, 5, 6] steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + - 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 }} + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} - # 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 + - uses: actions/download-artifact@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native - - run: xvfb-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 if: ${{needs.build.outputs.docsChange != 'docs only change'}} - testElectron: - name: Test Electron + - run: 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'}} + + - name: Upload test trace + if: always() + uses: actions/upload-artifact@v2 + with: + name: test-trace + if-no-files-found: ignore + retention-days: 2 + path: | + test/traces + + testProd: + name: Test Production runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 - HEADLESS: true - TEST_ELECTRON: 1 steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + + - 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 }} + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native - # 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 if: ${{needs.build.outputs.docsChange != 'docs only change'}} - - run: yarn add -W --dev spectron@7.0.0 electron@5.0.0 + - run: node run-tests.js --type production + name: Run test/production if: ${{needs.build.outputs.docsChange != 'docs only change'}} - - run: xvfb-run node run-tests.js test/integration/with-electron/test/index.test.js + - run: NEXT_TEST_MODE=start node run-tests.js --type e2e + name: Run test/e2e (production) if: ${{needs.build.outputs.docsChange != 'docs only change'}} - testYarnPnP: + testIntegration: + name: Test Integration runs-on: ubuntu-latest + needs: [build, build-native-dev] env: - NODE_OPTIONS: '--unhandled-rejections=strict' - YARN_COMPRESSION_LEVEL: '0' + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + TEST_TIMINGS_TOKEN: ${{ secrets.TEST_TIMINGS_TOKEN }} + strategy: + fail-fast: false + matrix: + group: [1, 2, 3, 4, 5, 6] steps: - - uses: actions/checkout@v2 + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} with: - fetch-depth: 25 + node-version: 14 - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') - id: docs-change + - run: echo ${{needs.build.outputs.docsChange}} - - run: yarn install --frozen-lockfile --check-files - if: ${{steps.docs-change.outputs.DOCS_CHANGE != 'docs only change'}} + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off - - run: bash ./test-pnp.sh - if: ${{steps.docs-change.outputs.DOCS_CHANGE != 'docs only change'}} + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} - testsPass: - name: thank you, next - runs-on: ubuntu-latest - needs: [lint, checkPrecompiled, testIntegration, testUnit, testYarnPnP] - steps: - - run: exit 0 + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native + + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + if: ${{needs.build.outputs.docsChange != 'docs only change'}} - testFutureDependencies: - name: Webpack 5 (Basic, Production, Acceptance) + - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/6 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - name: Upload test trace + if: always() + uses: actions/upload-artifact@v2 + with: + name: test-trace + if-no-files-found: ignore + retention-days: 2 + path: | + test/traces + + testElectron: + name: Test Electron runs-on: ubuntu-latest + needs: [build, build-native-dev] env: NEXT_TELEMETRY_DISABLED: 1 NEXT_TEST_JOB: 1 - HEADLESS: true - NEXT_PRIVATE_TEST_WEBPACK5_MODE: 1 - + TEST_ELECTRON: 1 steps: - - uses: actions/checkout@v2 + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} with: - fetch-depth: 25 + node-version: 14 - - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') - id: docs-change + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} - - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native - - run: yarn install --check-files - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + - 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/{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' }} + - run: xvfb-run node run-tests.js test/integration/with-electron/test/index.test.js + if: ${{needs.build.outputs.docsChange != 'docs only change'}} - testLegacyReact: - name: React 16 + Webpack 4 (Basic, Production, Acceptance) + testYarnPnP: runs-on: ubuntu-latest + needs: [build, build-native-dev] env: - NEXT_TELEMETRY_DISABLED: 1 - NEXT_TEST_JOB: 1 - HEADLESS: true - + NODE_OPTIONS: '--unhandled-rejections=strict' + YARN_COMPRESSION_LEVEL: '0' steps: - - uses: actions/checkout@v2 + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} 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' }} + node-version: 14 - - 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' }} + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} - - run: yarn install --check-files - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native - - run: yarn list react react-dom - if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + - run: bash ./scripts/test-pnp.sh + if: ${{needs.build.outputs.docsChange != 'docs only change'}} - - 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' }} + testsPass: + name: thank you, next + runs-on: ubuntu-latest + needs: + [ + lint, + check-examples, + test-native, + checkPrecompiled, + testIntegration, + testUnit, + testYarnPnP, + testDev, + testProd, + ] + steps: + - run: exit 0 testFirefox: name: Test Firefox (production) runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] env: - HEADLESS: true - BROWSERNAME: 'firefox' + BROWSER_NAME: 'firefox' NEXT_TELEMETRY_DISABLED: 1 steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + - uses: actions/cache@v2 if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: path: ./* - key: ${{ github.sha }} + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native + - 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'}} testSafari: name: Test Safari (production) runs-on: ubuntu-latest - needs: build + needs: [build, build-native-dev] 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: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + + # 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' + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native + + # 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: name: Test Safari 10.1 (nav) runs-on: ubuntu-latest - needs: [build, testSafari] + needs: [build, testSafari, build-native-dev] 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: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + + # 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 }} + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native + + - 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'}} + testFirefoxNode17: + name: Test Firefox Node.js 17 + runs-on: ubuntu-latest + needs: [build, testFirefox, build-native-dev] + env: + BROWSER_NAME: 'firefox' + NEXT_TELEMETRY_DISABLED: 1 + steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 17 + check-latest: true + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native + - 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'}} + publishRelease: + if: ${{ needs.build.outputs.isRelease == 'true' }} name: Potentially publish release runs-on: ubuntu-latest - needs: build + needs: + - build + - build-wasm + - build-native + - build-windows-i686 + - build-windows-aarch64 + - build-linux-musl + - build-linux-arm7 + - build-linux-aarch64 + - build-android-aarch64 + - build-linux-aarch64-musl env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN_ELEVATED }} steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + + # 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 }} + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} - - run: ./publish-release.sh + - uses: actions/download-artifact@v2 + with: + name: next-swc-binaries + path: packages/next-swc/native - prStats: + - uses: actions/download-artifact@v2 + with: + name: wasm-binaries + path: packages/next-swc/crates/wasm + + - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc + - run: ./scripts/publish-native.js $GITHUB_REF + - run: ./scripts/publish-release.sh + + releaseStats: name: Release Stats runs-on: ubuntu-latest - needs: [publishRelease] + needs: [publishRelease, build-native-dev] steps: + - name: Setup node + uses: actions/setup-node@v2 + if: ${{ steps.docs-change.outputs.docsChange != 'docs only change' }} + with: + node-version: 14 + - uses: actions/cache@v2 id: restore-build with: path: ./* - key: ${{ github.sha }} - - run: ./release-stats.sh + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - uses: actions/download-artifact@v2 + with: + name: next-swc-dev-binary + path: packages/next-swc/native + + - run: cp -r packages/next-swc/native .github/actions/next-stats-action/native + + - run: ./scripts/release-stats.sh - uses: ./.github/actions/next-stats-action env: PR_STATS_COMMENT_TOKEN: ${{ secrets.PR_STATS_COMMENT_TOKEN }} + + build-native-dev: + name: Build dev binary for tests + runs-on: ubuntu-18.04 + steps: + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + + - 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-11-15 + + - name: Cache cargo registry + uses: actions/cache@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: ~/.cargo/registry + key: stable-ubuntu-18.04-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-ubuntu-18.04-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-swc/native/next-swc.linux-x64-gnu.node + key: dev-next-swc-nightly-2021-11-15-linux-x64-gnu-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + # We use restore-key to pick latest cache. + # We will not get exact match, but doc says + # "If there are multiple partial matches for a restore key, the action returns the most recently created cache." + # So we get latest cache + - name: Cache built files + uses: actions/cache@v2 + with: + path: ./packages/next-swc/target + key: next-swc-cargo-cache-ubuntu-18.04--${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + next-swc-cargo-cache-ubuntu-18.04 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: yarn build-native + env: + MACOSX_DEPLOYMENT_TARGET: '10.13' + working-directory: packages/next-swc + + - name: Upload artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: next-swc-dev-binary + path: packages/next-swc/native/next-swc.linux-x64-gnu.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-11-15 + profile: minimal + - run: cd packages/next-swc && cargo test + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + + test-wasm: + name: Test the wasm build + runs-on: ubuntu-18.04 + needs: [build, build-native-dev, build-wasm-dev] + + steps: + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: wasm-dev-binary + path: packages/next-swc/crates/wasm/pkg-nodejs + + - run: ls packages/next-swc/crates/wasm + + - uses: actions/download-artifact@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + with: + name: next-swc-dev-binary + path: packages/next-swc/native + + # node version needs to be 16+ to use --no-addons option + - name: Setup node + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + uses: actions/setup-node@v2 + with: + node-version: 16 + check-latest: true + + - run: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - run: node ./scripts/setup-wasm.mjs + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - run: TEST_WASM=true xvfb-run node run-tests.js test/integration/production/test/index.test.js + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + # Build binaries for publishing + build-native: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + 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' }} + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + check-latest: true + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-11-15 + 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-swc/native/next-swc.${{ matrix.name }}.node + key: next-swc-nightly-2021-11-15-${{ matrix.target }}-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + - name: Cross build aarch64 setup + if: ${{ matrix.target == 'aarch64-apple-darwin' }} + 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"; + # We use restore-key to pick latest cache. + # We will not get exact match, but doc says + # "If there are multiple partial matches for a restore key, the action returns the most recently created cache." + # So we get latest cache + - name: Cache built files + uses: actions/cache@v2 + with: + path: ./packages/next-swc/target + key: next-swc-cargo-cache-${{ matrix.os }}--${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + next-swc-cargo-cache-${{ matrix.os }} + + - name: 'Build' + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: yarn build-native --release --target ${{ matrix.target }} + env: + MACOSX_DEPLOYMENT_TARGET: '10.13' + working-directory: packages/next-swc + + - name: Upload artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: next-swc-binaries + path: packages/next-swc/native/next-swc.${{ matrix.name }}.node + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache + + build-windows-i686: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - windows-i686 - node@14 + runs-on: windows-latest + env: + CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 32 + CARGO_PROFILE_RELEASE_LTO: 'false' + steps: + - name: Install node x86 + run: | + choco install nodejs-lts --x86 -y --force + refreshenv + + - name: Set 32bit Node.js path + run: | + echo "C:\\Program Files (x86)\\nodejs" >> $GITHUB_PATH + shell: bash + + - name: Node.js arch + run: node -e "console.log(process.arch)" + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-11-15 + override: true + target: i686-pc-windows-msvc + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next-swc/native/next-swc.win32-ia32-msvc.node + key: next-swc-nightly-2021-11-15-win32-ia32-msvc-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + shell: bash + run: yarn build-native --release --target i686-pc-windows-msvc + working-directory: packages/next-swc + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next-swc/native/next-swc.win32-ia32-msvc.node + + build-windows-aarch64: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - windows-aarch64 - node@14 + runs-on: windows-latest + steps: + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-11-15 + override: true + target: aarch64-pc-windows-msvc + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next-swc/native/next-swc.win32-arm64-msvc.node + key: next-swc-nightly-2021-11-15-win32-arm64-msvc-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + shell: bash + run: yarn build-native --release --target aarch64-pc-windows-msvc + working-directory: packages/next-swc + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next-swc/native/next-swc.win32-arm64-msvc.node + + build-linux-musl: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - linux-musl - node@lts + runs-on: ubuntu-latest + steps: + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + - name: Login to registry + run: | + docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY_URL + env: + DOCKER_REGISTRY_URL: ghcr.io + DOCKER_USERNAME: ${{ github.actor }} + DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache + uses: actions/cache@v2 + with: + path: | + target/ + key: linux-musl-publish-integration + + - name: Pull docker image + run: | + docker pull ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine + docker tag ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine builder + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next-swc/native/next-swc.linux-x64-musl.node + key: next-swc-nightly-2021-11-15-linux-x64-musl-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + - name: 'Build' + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: | + docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd)/packages/next-swc:/build -w /build builder sh -c "npm i -g @napi-rs/cli@1.2.1 && yarn build-native --release --target x86_64-unknown-linux-musl" + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next-swc/native/next-swc.linux-x64-musl.node + + build-linux-aarch64: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - aarch64-unknown-linux-gnu - node@14 + runs-on: ubuntu-18.04 + steps: + - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-11-15 + override: true + target: aarch64-unknown-linux-gnu + + - name: Cache + uses: actions/cache@v2 + with: + path: | + target/ + key: aarch64-linux-gnu-publish-integration + + - name: Install cross compile toolchain + run: | + sudo apt-get update + sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu -y + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next-swc/native/next-swc.linux-arm64-gnu.node + key: next-swc-nightly-2021-11-15-linux-arm64-gnu-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + - name: Cross build aarch64 + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: yarn build-native --release --target aarch64-unknown-linux-gnu + working-directory: packages/next-swc + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next-swc/native/next-swc.linux-arm64-gnu.node + + build-linux-aarch64-musl: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - aarch64-unknown-linux-musl - node@14 + runs-on: ubuntu-18.04 + steps: + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-11-15 + override: true + target: aarch64-unknown-linux-musl + + - name: Cache + uses: actions/cache@v2 + with: + path: | + target/ + key: aarch64-linux-musl-publish-integration + + - name: Install cross compile toolchain + run: | + sudo apt-get update + sudo apt-get install gcc-aarch64-linux-gnu -y + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next-swc/native/next-swc.linux-arm64-musl.node + key: next-swc-nightly-2021-11-15-linux-arm64-musl-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + - name: Cross build aarch64 + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: yarn build-native --release --target aarch64-unknown-linux-musl + working-directory: packages/next-swc + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next-swc/native/next-swc.linux-arm64-musl.node + + build-linux-arm7: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - arm7-unknown-linux-gnu - node@14 + runs-on: ubuntu-18.04 + steps: + - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-11-15 + override: true + target: armv7-unknown-linux-gnueabihf + + - name: Cache + uses: actions/cache@v2 + with: + path: | + target/ + key: arm7-linux-gnu-publish-integration + + - name: Install cross compile toolchain + run: | + sudo apt-get update + sudo apt-get install gcc-arm-linux-gnueabihf -y + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next-swc/native/next-swc.linux-arm-gnueabihf.node + key: next-swc-nightly-2021-11-15-linux-arm-gnueabihf-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + - name: Cross build aarch64 + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + run: yarn build-native --release --target armv7-unknown-linux-gnueabihf + working-directory: packages/next-swc + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next-swc/native/next-swc.linux-arm-gnueabihf.node + + build-android-aarch64: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + name: next-swc - Android - aarch64 + runs-on: macos-latest + steps: + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + # we use checkout here instead of the build cache since + # it can fail to restore in different OS' + - uses: actions/checkout@v2 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-11-15 + override: true + target: aarch64-linux-android + + - name: Cache native binary + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next-swc/native/next-swc.android-arm64.node + key: next-swc-nightly-2021-11-15-android-arm64-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' }} + shell: bash + run: | + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android24-clang" + yarn build-native --release --target aarch64-linux-android + working-directory: packages/next-swc + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next-swc/native/next-swc.android-arm64.node + + build-wasm: + needs: build + if: ${{ needs.build.outputs.isRelease == 'true' }} + strategy: + matrix: + target: [web, nodejs] + runs-on: ubuntu-latest + steps: + - uses: actions/cache@v2 + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-11-15 + override: true + target: wasm32-unknown-unknown + + - name: Cache + uses: actions/cache@v2 + with: + path: | + ~/.cargo/ + **/target/ + key: ${{ runner.os }}-publish-integration + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Build + run: (wasm-pack build packages/next-swc/crates/wasm --release --scope=next --target ${{ matrix.target }}) + + - name: Add target to folder name + run: mv packages/next-swc/crates/wasm/pkg packages/next-swc/crates/wasm/pkg-${{ matrix.target }} + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: wasm-binaries + path: packages/next-swc/crates/wasm/pkg-* + + build-wasm-dev: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }}-${{ github.run_number }}-${{ github.run_attempt }} + + - name: Setup node + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + uses: actions/setup-node@v2 + with: + node-version: 14 + + - name: Install Rust + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-11-15 + override: true + target: wasm32-unknown-unknown + + - name: Cache + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + uses: actions/cache@v2 + with: + path: | + ~/.cargo/ + **/target/ + key: ${{ runner.os }}-publish-integration + + - name: Cache wasm binary + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: binary-cache + uses: actions/cache@v2 + with: + path: packages/next-swc/crates/wasm/pkg-nodejs + key: dev-wasm-next-swc-nightly-2021-11-15-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + - name: Install wasm-pack + if: ${{needs.build.outputs.docsChange != 'docs only change' && steps.binary-cache.outputs.cache-hit != 'true'}} + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Build + if: ${{needs.build.outputs.docsChange != 'docs only change' && steps.binary-cache.outputs.cache-hit != 'true'}} + run: (wasm-pack build packages/next-swc/crates/wasm --dev --scope=next --target nodejs) + + - name: Add target to folder name + if: ${{needs.build.outputs.docsChange != 'docs only change' && steps.binary-cache.outputs.cache-hit != 'true'}} + run: mv packages/next-swc/crates/wasm/pkg packages/next-swc/crates/wasm/pkg-nodejs + + - name: Upload artifact + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + uses: actions/upload-artifact@v2 + with: + name: wasm-dev-binary + path: packages/next-swc/crates/wasm/pkg-nodejs diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml index ce58d11cab7c..093016a7beaf 100644 --- a/.github/workflows/cancel.yml +++ b/.github/workflows/cancel.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 2 steps: - - uses: styfle/cancel-workflow-action@0.5.0 + - uses: styfle/cancel-workflow-action@0.9.1 with: workflow_id: 444921, 444987 access_token: ${{ github.token }} diff --git a/.github/workflows/pull_request_stats.yml b/.github/workflows/pull_request_stats.yml index 4c4874cdb9f4..a6b481f7b2a0 100644 --- a/.github/workflows/pull_request_stats.yml +++ b/.github/workflows/pull_request_stats.yml @@ -5,8 +5,95 @@ on: name: Generate Pull Request Stats jobs: + build-native-dev: + name: Build dev binary for tests + runs-on: ubuntu-18.04 + steps: + # https://github.com/actions/virtual-environments/issues/1187 + - name: tune linux network + run: sudo ethtool -K eth0 tx off rx off + + - 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-11-15 + + - name: Cache cargo registry + uses: actions/cache@v1 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + path: ~/.cargo/registry + key: stable-ubuntu-18.04-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-ubuntu-18.04-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-swc/native/next-swc.linux-x64-gnu.node + key: dev-next-swc-nightly-2021-11-15-linux-x64-gnu-${{ hashFiles('.github/workflows/build_test_deploy.yml', 'packages/next-swc/**') }} + + # We use restore-key to pick latest cache. + # We will not get exact match, but doc says + # "If there are multiple partial matches for a restore key, the action returns the most recently created cache." + # So we get latest cache + - name: Cache built files + uses: actions/cache@v2 + with: + path: ./packages/next-target + key: next-swc-cargo-cache-ubuntu-18.04--${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + next-swc-cargo-cache-ubuntu-18.04 + + # since the repo's dependencies aren't installed we need + # to install napi globally + - run: npm i -g @napi-rs/cli@1.2.1 + + - name: Build + if: ${{ steps.binary-cache.outputs.cache-hit != 'true' && steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + run: yarn build-native + env: + MACOSX_DEPLOYMENT_TARGET: '10.13' + working-directory: packages/next-swc + + - name: Upload artifact + uses: actions/upload-artifact@v2.2.4 + with: + name: next-swc-dev-binary + path: packages/next-swc/native/next-swc.linux-x64-gnu.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 + stats: name: PR Stats + needs: build-native-dev runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -15,5 +102,15 @@ jobs: - run: echo ::set-output name=DOCS_CHANGE::$(node skip-docs-change.js echo 'not-docs-only-change') id: docs-change + + - uses: actions/download-artifact@v2 + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + with: + name: next-swc-dev-binary + path: packages/next-swc/native + + - run: cp -r packages/next-swc/native .github/actions/next-stats-action/native + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} + - uses: ./.github/actions/next-stats-action if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..daf1de6af755 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,20 @@ +name: 'Stale issue handler' +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@main + id: stale + name: 'Close stale issues with no reproduction' + with: + only-labels: 'please add a complete reproduction' + close-issue-message: 'This issue has been automatically closed after 30 days of inactivity with no reproduction. If you are running into a similar issue, please open a new issue with a reproduction. Thank you.' + days-before-issue-close: 30 + days-before-pr-close: -1 + days-before-pr-stale: -1 + exempt-issue-labels: 'blocked,must,should,keep' diff --git a/.github/workflows/test_macos.yml b/.github/workflows/test_macos.yml deleted file mode 100644 index 573425c04aae..000000000000 --- 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 000000000000..8b56f79f4d9b --- /dev/null +++ b/.github/workflows/test_react_experimental.yml @@ -0,0 +1,49 @@ +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 + + - 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 9eb76fe8ee43..4411c0a521a5 100644 --- a/.github/workflows/test_react_next.yml +++ b/.github/workflows/test_react_next.yml @@ -6,48 +6,44 @@ 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: npm i -g playwright-chromium@1.14.1 && npx playwright install-deps - - run: node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3 + - run: node run-tests.js --timings -g ${{ matrix.group }}/6 diff --git a/.gitignore b/.gitignore index f043ec68d93f..a1e1b057fd45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # build output dist .next +target # dependencies node_modules @@ -12,6 +13,7 @@ test/node_modules # logs & pids *.log pids +*.cpuprofile # coverage .nyc_output @@ -22,6 +24,7 @@ test/**/out* test/**/next-env.d.ts .DS_Store /e2e-tests +test/tmp/** # Editors **/.idea @@ -37,3 +40,6 @@ test-timings.json # Vercel .vercel .now + +# Cache +*.tsbuildinfo diff --git a/.prettierignore b/.prettierignore index 522a3d1a67ea..f63e5c00ba3f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,9 +10,13 @@ packages/react-dev-overlay/lib/** **/__tmp__/** lerna.json .github/actions/next-stats-action/.work +packages/next-swc/crates/**/* 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/** +bench/nested-deps/pages/**/* +bench/nested-deps/components/**/* diff --git a/.prettierignore_staged b/.prettierignore_staged index e888b2691913..014df0fc5117 100644 --- a/.prettierignore_staged +++ b/.prettierignore_staged @@ -1,6 +1,7 @@ **/.next/** **/_next/** **/dist/** +packages/next-swc/crates/** packages/next/compiled/**/* packages/next/bundles/webpack/packages/*.runtime.js lerna.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 3a6858c2fd0d..8c78fbbb7103 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,9 +10,13 @@ "request": "launch", "cwd": "${workspaceFolder}", "runtimeExecutable": "yarn", - "runtimeArgs": ["run", "debug", "dev", "test/integration/basic"], + "runtimeArgs": ["run", "debug", "dev", "bench/nested-deps"], "skipFiles": ["/**"], - "port": 9229 + "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"], + "port": 9229, + "env": { + "NEXT_PRIVATE_LOCAL_WEBPACK5": "1" + } }, { "name": "Launch app build", @@ -20,10 +24,27 @@ "request": "launch", "cwd": "${workspaceFolder}", "runtimeExecutable": "yarn", - "runtimeArgs": ["run", "debug", "build", "test/integration/basic"], + "runtimeArgs": ["run", "debug", "build", "bench/nested-deps"], "skipFiles": ["/**"], "port": 9229, - "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"] + "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"], + "env": { + "NEXT_PRIVATE_LOCAL_WEBPACK5": "1" + } + }, + { + "name": "Launch app build trace jaeger", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "yarn", + "runtimeArgs": ["run", "clean-trace-jaeger"], + "skipFiles": ["/**"], + "port": 9229, + "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"], + "env": { + "NEXT_PRIVATE_LOCAL_WEBPACK5": "1" + } }, { "name": "Launch app production", @@ -31,9 +52,12 @@ "request": "launch", "cwd": "${workspaceFolder}", "runtimeExecutable": "yarn", - "runtimeArgs": ["run", "debug", "start", "test/integration/basic"], + "runtimeArgs": ["run", "debug", "start", "bench/nested-deps"], "skipFiles": ["/**"], - "port": 9229 + "port": 9229, + "env": { + "NEXT_PRIVATE_LOCAL_WEBPACK5": "1" + } }, { "type": "node", @@ -41,7 +65,10 @@ "name": "Attach to existing debugger", "port": 9229, "skipFiles": ["/**"], - "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"] + "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"], + "env": { + "NEXT_PRIVATE_LOCAL_WEBPACK5": "1" + } }, { "name": "Launch this example", @@ -51,7 +78,10 @@ "runtimeExecutable": "yarn", "runtimeArgs": ["run", "debug", "dev", "${fileDirname}"], "skipFiles": ["/**"], - "port": 9229 + "port": 9229, + "env": { + "NEXT_PRIVATE_LOCAL_WEBPACK5": "1" + } } ] } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 008c8f15e765..33c391bf3acf 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.22.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/instrument.js b/bench/instrument.js deleted file mode 100644 index 38176de31cbe..000000000000 --- 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 000000000000..9df615af05da --- /dev/null +++ b/bench/nested-deps/.gitignore @@ -0,0 +1,2 @@ +components/* +CPU* \ No newline at end of file diff --git a/bench/nested-deps/bench.mjs b/bench/nested-deps/bench.mjs new file mode 100644 index 000000000000..d8ad44c23d85 --- /dev/null +++ b/bench/nested-deps/bench.mjs @@ -0,0 +1,199 @@ +import { spawn } from 'child_process' +import fetch from 'node-fetch' +import { + existsSync, + readFileSync, + writeFileSync, + unlinkSync, + promises as fs, +} from 'fs' +import treeKill from 'tree-kill' + +async function killApp(instance) { + await new Promise((resolve, reject) => { + treeKill(instance.pid, (err) => { + if (err) { + if ( + process.platform === 'win32' && + typeof err.message === 'string' && + (err.message.includes(`no running instance of the task`) || + err.message.includes(`not found`)) + ) { + // Windows throws an error if the process is already stopped + // + // Command failed: taskkill /pid 6924 /T /F + // ERROR: The process with PID 6924 (child process of PID 6736) could not be terminated. + // Reason: There is no running instance of the task. + return resolve() + } + return reject(err) + } + + resolve() + }) + }) +} + +class File { + constructor(path) { + this.path = path + this.originalContent = existsSync(this.path) + ? readFileSync(this.path, 'utf8') + : null + } + + write(content) { + if (!this.originalContent) { + this.originalContent = content + } + writeFileSync(this.path, content, 'utf8') + } + + replace(pattern, newValue) { + const currentContent = readFileSync(this.path, 'utf8') + if (pattern instanceof RegExp) { + if (!pattern.test(currentContent)) { + throw new Error( + `Failed to replace content.\n\nPattern: ${pattern.toString()}\n\nContent: ${currentContent}` + ) + } + } else if (typeof pattern === 'string') { + if (!currentContent.includes(pattern)) { + throw new Error( + `Failed to replace content.\n\nPattern: ${pattern}\n\nContent: ${currentContent}` + ) + } + } else { + throw new Error(`Unknown replacement attempt type: ${pattern}`) + } + + const newContent = currentContent.replace(pattern, newValue) + this.write(newContent) + } + + prepend(line) { + const currentContent = readFileSync(this.path, 'utf8') + this.write(line + '\n' + currentContent) + } + + delete() { + unlinkSync(this.path) + } + + restore() { + this.write(this.originalContent) + } +} + +function runNextCommandDev(argv, opts = {}) { + const nextBin = '../../node_modules/.bin/next' + const cwd = process.cwd() + const env = { + ...process.env, + NODE_ENV: undefined, + __NEXT_TEST_MODE: 'true', + ...opts.env, + } + + const nodeArgs = opts.nodeArgs || [] + return new Promise((resolve, reject) => { + const instance = spawn( + 'node', + [...nodeArgs, '--no-deprecation', nextBin, ...argv], + { + cwd, + env, + } + ) + let didResolve = false + + function handleStdout(data) { + const message = data.toString() + const bootupMarkers = { + dev: /compiled .*successfully/i, + start: /started server/i, + } + if ( + (opts.bootupMarker && opts.bootupMarker.test(message)) || + bootupMarkers[opts.nextStart ? 'start' : 'dev'].test(message) + ) { + if (!didResolve) { + didResolve = true + resolve(instance) + } + } + + if (typeof opts.onStdout === 'function') { + opts.onStdout(message) + } + + if (opts.stdout !== false) { + process.stdout.write(message) + } + } + + function handleStderr(data) { + const message = data.toString() + if (typeof opts.onStderr === 'function') { + opts.onStderr(message) + } + + if (opts.stderr !== false) { + process.stderr.write(message) + } + } + + instance.stdout.on('data', handleStdout) + instance.stderr.on('data', handleStderr) + + instance.on('close', () => { + instance.stdout.removeListener('data', handleStdout) + instance.stderr.removeListener('data', handleStderr) + if (!didResolve) { + didResolve = true + resolve() + } + }) + + instance.on('error', (err) => { + reject(err) + }) + }) +} + +function waitFor(millis) { + return new Promise((resolve) => setTimeout(resolve, millis)) +} + +async function main() { + await fs.rmDir('.next', { recursive: true }) + const file = new File('pages/index.jsx') + try { + const instance = await runNextCommandDev(['dev', '--port', '3000']) + const res = await fetch('http://localhost:3000/') + if (res.status !== 200) { + throw new Error('Fetching / failed') + } + + await waitFor(3000) + + file.prepend('// First edit') + + await waitFor(5000) + + file.prepend('// Second edit') + + await waitFor(5000) + + file.prepend('// Third edit') + + await waitFor(5000) + + await killApp(instance) + await fs.rmDir('.next', { recursive: true }) + } finally { + file.restore() + } +} + +main() diff --git a/bench/nested-deps/fuzzponent.js b/bench/nested-deps/fuzzponent.js new file mode 100755 index 000000000000..4904669441a8 --- /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 000000000000..004e6c18198b --- /dev/null +++ b/bench/nested-deps/next.config.js @@ -0,0 +1,9 @@ +const idx = process.execArgv.indexOf('--cpu-prof') +if (idx >= 0) process.execArgv.splice(idx, 1) + +module.exports = { + eslint: { + ignoreDuringBuilds: true, + }, + swcMinify: true, +} diff --git a/bench/nested-deps/package.json b/bench/nested-deps/package.json new file mode 100644 index 000000000000..ad48206bed2d --- /dev/null +++ b/bench/nested-deps/package.json @@ -0,0 +1,11 @@ +{ + "scripts": { + "prepare": "rimraf components && mkdir components && node ./fuzzponent.js -d 2 -s 206 -o components", + "dev": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 node ../../node_modules/next/dist/bin/next dev", + "build": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 node ../../node_modules/next/dist/bin/next build", + "start": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 node ../../node_modules/next/dist/bin/next start", + "dev-nocache": "rimraf .next && yarn dev", + "dev-cpuprofile-nocache": "rimraf .next && cross-env NEXT_PRIVATE_LOCAL_WEBPACK5=1 node --cpu-prof ../../node_modules/next/dist/bin/next", + "build-nocache": "rimraf .next && yarn build" + } +} diff --git a/bench/nested-deps/pages/index.jsx b/bench/nested-deps/pages/index.jsx new file mode 100644 index 000000000000..f199ad545abf --- /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 eb3c593c98f8..000000000000 --- 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 1c409ad384ba..170f4fb050eb 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 871256707ddb..2167f3033655 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 c4bad36c88df..ab1225800472 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 8989739c9039..e23ed3e6f26a 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 2b5d50457a13..827cdaae7748 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 000000000000..d311332afdef --- /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 03dcbb6f638e..000000000000 --- 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/check-pre-compiled.sh b/check-pre-compiled.sh deleted file mode 100755 index f0865c9db428..000000000000 --- a/check-pre-compiled.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -yarn --cwd packages/next/bundles -cp packages/next/bundles/node_modules/webpack5/lib/hmr/HotModuleReplacement.runtime.js packages/next/bundles/webpack/packages/ -cp packages/next/bundles/node_modules/webpack5/lib/hmr/JavascriptHotModuleReplacement.runtime.js packages/next/bundles/webpack/packages/ -yarn --cwd packages/next ncc-compiled - -# Make sure to exit with 1 if there are changes after running ncc-compiled -# step to ensure we get any changes committed - -if [[ ! -z $(git status -s) ]];then - echo "Detected changes" - git status - exit 1 -fi diff --git a/contributing.md b/contributing.md index 6bfc13c554a6..83de4b822218 100644 --- a/contributing.md +++ b/contributing.md @@ -1,28 +1,65 @@ # 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. + + _Note: You may need to repeat this step if your types get outdated._ + +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.) + +## Building + +You can build the project, including all type definitions, with: + +```bash +yarn build +# - or - +yarn prepublish +``` + +By default the latest canary of the next-swc binaries will be installed and used. If you are actively working on Rust code or you need to test out the most recent Rust code that hasn't been published as a canary yet you can [install Rust](https://www.rust-lang.org/tools/install) and run `yarn --cwd packages/next-swc build-native`. + +If you need to clean the project for any reason, use `yarn clean`. -Make sure you have `chromedriver` installed for your Chrome version. You can install it with +## Testing -- `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` +See the [testing readme](./test/readme.md) for information on writing tests. -Running all tests: +### Running tests ```sh yarn testonly @@ -34,25 +71,33 @@ If you would like to run the tests in headless mode (with the browser windows hi yarn testheadless ``` -If you would like to use a specific Chrome/Chromium binary to run tests you can specify it with +Running a specific test suite (e.g. `production`) inside of the `test/integration` directory: ```sh -CHROME_BIN='path/to/chrome/bin' yarn testonly +yarn testonly --testPathPattern "production" ``` -Running a specific test suite inside of the `test/integration` directory: +Running one test in the `production` test suite: ```sh -yarn testonly --testPathPattern "production" +yarn testonly --testPathPattern "production" -t "should allow etag header support" ``` -Running just one test in the `production` test suite: +### Linting + +To check the formatting of your code: ```sh -yarn testonly --testPathPattern "production" -t "should allow etag header support" +yarn lint ``` -## Running the integration apps +If you get errors, you can fix them with: + +```sh +yarn lint-fix +``` + +### Running the example apps Running examples can be done with: @@ -77,7 +122,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 a local dependency in package.json 1. In your app's `package.json`, replace: @@ -88,7 +137,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 +164,59 @@ EXAMPLE=./test/integration/basic yarn install --force ``` +#### Troubleshooting + +- If you see the below error while running `yarn dev` with next: + +``` +Failed to load SWC binary, see more info here: https://nextjs.org/docs/messages/failed-loading-swc +``` + +Try to add the below section to your `package.json`, then run again + +```json +"optionalDependencies": { + "@next/swc-linux-x64-gnu": "canary", + "@next/swc-win32-x64-msvc": "canary", + "@next/swc-darwin-x64": "canary", + "@next/swc-darwin-arm64": "canary" +}, +``` + +### 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 effect). + +## 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 +226,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 +257,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 273c943d9492..2f284c26d803 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 d20824316e58..f897c89f206e 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/compiler.md b/docs/advanced-features/compiler.md new file mode 100644 index 000000000000..58b540ebf725 --- /dev/null +++ b/docs/advanced-features/compiler.md @@ -0,0 +1,131 @@ +--- +description: Learn about the Next.js Compiler, written in Rust, which transforms and minifies your Next.js application. +--- + +# Next.js Compiler + +
+ Version History + +| Version | Changes | +| --------- | --------------------------------------------------------------- | +| `v12.0.0` | Next.js Compiler [introduced](https://nextjs.org/blog/next-12). | + +
+ +The Next.js Compiler, written in Rust using [SWC](http://swc.rs/), allows Next.js to transform and minify your JavaScript code for production. This replaces Babel for individual files and Terser for minifying output bundles. + +Compilation using the Next.js Compiler is 17x faster than Babel and enabled by default since Next.js version 12. If you have an existing Babel configuration or are using [unsupported features](#unsupported-features), your application will opt-out of the Next.js Compiler and continue using Babel. + +## Why SWC? + +[SWC](http://swc.rs/) is an extensible Rust-based platform for the next generation of fast developer tools. + +SWC can be used for compilation, minification, bundling, and more – and is designed to be extended. It's something you can call to perform code transformations (either built-in or custom). Running those transformations happens through higher-level tools like Next.js. + +We chose to build on SWC for a few reasons: + +- **Extensibility:** SWC can be used as a Crate inside Next.js, without having to fork the library or workaround design constraints. +- **Performance:** We were able to achieve ~3x faster Fast Refresh and ~5x faster builds in Next.js by switching to SWC, with more room for optimization still in progress. +- **WebAssembly:** Rust's support for WASM is essential for supporting all possible platforms and taking Next.js development everywhere. +- **Community:** The Rust community and ecosystem are amazing and still growing. + +## Experimental Features + +### Minification + +You can opt-in to using the Next.js compiler for minification. This is 7x faster than Terser. + +```js +// next.config.js + +module.exports = { + swcMinify: true, +} +``` + +If you have feedback about `swcMinify`, please share it on the [feedback discussion](https://github.com/vercel/next.js/discussions/30237). + +### Styled Components + +We're working to port `babel-plugin-styled-components` to the Next.js Compiler. + +First, update to the latest version of Next.js: `npm install next@latest`. Then, update your `next.config.js` file: + +```js +// next.config.js + +module.exports = { + experimental: { + // ssr and displayName are configured by default + styledComponents: true, + }, +} +``` + +Currently, only the `ssr` and `displayName` transforms have been implemented. These two transforms are the main requirement for using `styled-components` in Next.js. + +### Jest + +Jest support not only includes the transformation previously provided by Babel, but also simplifies configuring Jest together with Next.js including: + +- Auto mocking of `.css`, `.module.css` (and their `.scss` variants), and image imports +- Automatically sets up `transform` using SWC +- Loading `.env` (and all variants) into `process.env` +- Ignores `node_modules` from test resolving and transforms +- Ignoring `.next` from test resolving +- Loads `next.config.js` for flags that enable experimental SWC transforms + +First, update to the latest version of Next.js: `npm install next@latest`. Then, update your `jest.config.js` file: + +```js +// jest.config.js +const nextJest = require('next/jest') + +// Providing the path to your Next.js app which will enable loading next.config.js and .env files +const createJestConfig = nextJest({ dir }) + +// Any custom config you want to pass to Jest +const customJestConfig = { + setupFilesAfterEnv: ['/jest.setup.js'], +} + +// createJestConfig is exported in this way to ensure that next/jest can load the Next.js configuration, which is async +module.exports = createJestConfig(customJestConfig) +``` + +### Legacy Decorators + +Next.js will automatically detect `experimentalDecorators` in `jsconfig.json` or `tsconfig.json` and apply that. This is commonly used with older versions of libraries like `mobx`. + +This flag is only supported for compatibility with existing applications. We do not recommend using legacy decorators in new applications. + +First, update to the latest version of Next.js: `npm install next@latest`. Then, update your `jsconfig.json` or `tsconfig.json` file: + +```js +{ + "compilerOptions": { + "experimentalDecorators": true + } +} +``` + +### importSource + +Next.js will automatically detect `jsxImportSource` in `jsconfig.json` or `tsconfig.json` and apply that. This is commonly used with libraries like Theme UI. + +First, update to the latest version of Next.js: `npm install next@latest`. Then, update your `jsconfig.json` or `tsconfig.json` file: + +```js +{ + "compilerOptions": { + "jsxImportSource": true + } +} +``` + +## Unsupported Features + +When your application has a `.babelrc` file, Next.js will automatically fall back to using Babel for transforming individual files. This ensures backwards compatibility with existing applications that leverage custom Babel plugins. + +If you're using a custom Babel setup, [please share your configuration](https://github.com/vercel/next.js/discussions/30174). We're working to port as many commonly used Babel transformations as possible, as well as supporting plugins in the future. diff --git a/docs/advanced-features/custom-app.md b/docs/advanced-features/custom-app.md index 571bf79da5a6..7add6c1d9b0c 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. -- 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). +- 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`](/docs/api-reference/data-fetching/getInitialProps.md) 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 243a76ce5164..6dfa999892be 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 f1ef1fb01c68..9fcba6659183 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 **cannot** 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: @@ -56,7 +56,7 @@ app.prepare().then(() => { > `server.js` doesn't go through babel or webpack. Make sure the syntax and sources this file requires are compatible with the current node version you are running. -Then, to run the custom server you'll need to update the `scripts` in `package.json`, like so: +To run the custom server you'll need to update the `scripts` in `package.json` like so: ```json "scripts": { diff --git a/docs/advanced-features/customizing-babel-config.md b/docs/advanced-features/customizing-babel-config.md index f8fcd8c5d3f5..aee52289f158 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 3c3b30e73550..d119ccffc15c 100644 --- a/docs/advanced-features/debugging.md +++ b/docs/advanced-features/debugging.md @@ -4,74 +4,99 @@ 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 -First, start Next.js with the inspect flag: +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 + +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 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 +ready - started server on 0.0.0.0:3000, url: 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 +> 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. -### Using Chrome DevTools +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. -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. +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). -### Using the Debugger in Visual Studio Code +### Debugging on Windows -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: +Windows users may run into an issue when using `NODE_OPTIONS='--inspect'` as that syntax is not supported on Windows platforms. To get around this, install the [`cross-env`](https://www.npmjs.com/package/cross-env) package as a development dependency (`--dev` with NPM or `-D` for Yarn) and replace the `dev` script with the following. ```json -{ - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "attach", - "name": "Launch Program", - "skipFiles": ["/**"], - "port": 9229 - } - ] -} +"dev": "cross-env NODE_OPTIONS='--inspect' next dev", ``` -Now hit F5 or select **Debug: Start Debugging** from the Command Palette and you can start your debugging session. - -## Step 3: Put breakpoints and see what happens - -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. +`cross-env` will set the `NODE_OPTIONS` environment variable regardless of which platform you are on (including Mac, Linux, and Windows) and allow you to debug consistently across devices and operating systems. -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 3cb20a86ad0e..fd92848067e9 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 0564c2421b2d..8272276b4741 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, }, ], }, @@ -97,6 +100,8 @@ module.exports = { domains: [ { + // Note: subdomains must be included in the domain value to be matched + // e.g. www.example.com should be used if that is the expected hostname domain: 'example.com', defaultLocale: 'en-US', }, @@ -119,6 +124,7 @@ module.exports = { For example if you have `pages/blog.js` the following urls will be available: - `example.com/blog` +- `www.example.com/blog` - `example.fr/blog` - `example.nl/blog` - `example.nl/nl-BE/blog` @@ -136,6 +142,48 @@ When using Domain Routing, if a user with the `Accept-Language` header `fr;q=0.9 When using Sub-path Routing, the user would be redirected to `/fr`. +### Prefixing the Default Locale + +With Next.js 12 and [Middleware](/docs/middleware.md), we can add a prefix to the default locale with a [workaround](https://github.com/vercel/next.js/discussions/18419). + +For example, here's a `next.config.js` file with support for a few languages. Note the `"default"` locale has been added intentionally. + +```js +// next.config.js + +module.exports = { + i18n: { + locales: ['default', 'en', 'de', 'fr'], + defaultLocale: 'default', + localeDetection: false, + }, + trailingSlash: true, +} +``` + +Next, we can use [Middleware](/docs/middleware.md) to add custom routing rules: + +```js +// pages/_middleware.ts + +import { NextRequest, NextResponse } from 'next/server' + +const PUBLIC_FILE = /\.(.*)$/ + +export function middleware(request: NextRequest) { + const shouldHandleLocale = + !PUBLIC_FILE.test(request.nextUrl.pathname) && + !request.nextUrl.pathname.includes('/api/') && + request.nextUrl.locale === 'default' + + return shouldHandleLocale + ? NextResponse.redirect(`/en${request.nextUrl.href}`) + : undefined +} +``` + +This [Middleware](/docs/middleware.md) skips adding the default prefix to [API Routes](/docs/api-routes/introduction.md) and [public](/docs/basic-features/static-file-serving.md) files like fonts or images. If a request is made to the default locale, we redirect to our prefix `/en`. + ### Disabling Automatic Locale Detection The automatic locale detection can be disabled with: @@ -201,6 +249,18 @@ export default function IndexPage(props) { } ``` +Note that to handle switching only the `locale` while preserving all routing information such as [dynamic route](/docs/routing/dynamic-routes.md) query values or hidden href query values, you can provide the `href` parameter as an object: + +```jsx +import { useRouter } from 'next/router' +const router = useRouter() +const { pathname, asPath, query } = router +// change just the locale and maintain all other route information including href's query +router.push({ pathname, query }, asPath, { locale: nextLocale }) +``` + +See [here](/docs/api-reference/next/router.md#with-url-object) for more information on the object structure for `router.push`. + If you have a `href` that already includes the locale you can opt-out of automatically handling the locale prefixing: ```jsx @@ -217,9 +277,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 +291,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 +346,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). You can workaround these limits with custom routing using [Middleware](/docs/middleware.md) in Next.js 12. diff --git a/docs/advanced-features/measuring-performance.md b/docs/advanced-features/measuring-performance.md index 329cddd452cf..789d94bb8759 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 12cd2215871c..8015f82667cd 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 1fce98d049e5..7d288941b0cb 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/output-file-tracing.md b/docs/advanced-features/output-file-tracing.md new file mode 100644 index 000000000000..b5ba359591af --- /dev/null +++ b/docs/advanced-features/output-file-tracing.md @@ -0,0 +1,24 @@ +--- +description: Next.js automatically traces which files are needed by each page to allow for easy deployment of your application. Learn how it works here. +--- + +# Output File Tracing + +During a build, Next.js will automatically trace each page and its dependencies to determine all of the files that are needed for deploying a production version of your application. + +This feature helps reduce the size of deployments drastically. Previously, when deploying with Docker you would need to have all files from your package's `dependencies` installed to run `next start`. Starting with Next.js 12, you can leverage Output File Tracing in the `.next/` directory to only include the necessary files. + +Furthermore, this removes the need for the deprecated `serverless` target which can cause various issues and also creates unnecessary duplication. + +## How It Works + +During `next build`, Next.js will use [`@vercel/nft`](https://github.com/vercel/nft) to statically analyze `import`, `require`, and `fs` usage to determine all files that a page might load. + +Next.js' production server is also traced for its needed files and output at `.next/next-server.js.nft.json` which can be leveraged in production. + +To leverage the `.nft.json` files emitted to the `.next` output directory, you can read the list of files in each trace which are relative to the `.nft.json` file and then copy them to your deployment location. + +## Caveats + +- There are some cases that Next.js might fail to include required files, or might incorrectly include unused files. In those cases, you can export page configs props `unstable_includeFiles` and `unstable_excludeFiles` respectively. Each prop accepts an array of [globs]() relative to the project's root to either include or exclude in the trace. +- Currently, Next.js does not do anything with the emitted `.nft.json` files. The files must be read by your deployment platform, for example [Vercel](https://vercel.com), to create a minimal deployment. In a future release, a new command is planned to utilize these `.nft.json` files. diff --git a/docs/advanced-features/preview-mode.md b/docs/advanced-features/preview-mode.md index 465d4354e48f..511974bad426 100644 --- a/docs/advanced-features/preview-mode.md +++ b/docs/advanced-features/preview-mode.md @@ -9,19 +9,21 @@ description: Next.js has the preview mode for statically generated pages. You ca
Examples
@@ -221,6 +223,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/react-18.md b/docs/advanced-features/react-18.md new file mode 100644 index 000000000000..ad3cfce10e22 --- /dev/null +++ b/docs/advanced-features/react-18.md @@ -0,0 +1,152 @@ +# React 18 + +[React 18](https://reactjs.org/blog/2021/06/08/the-plan-for-react-18.html) adds new features including, Suspense, automatic batching of updates, APIs like `startTransition`, and a new streaming API for server rendering with support for `React.lazy`. + +React 18 is still in beta. Read more about React 18's [release plan](https://github.com/reactwg/react-18/discussions) and discussions from the [working group](https://github.com/reactwg/react-18/discussions). + +### React 18 Usage in Next.js + +Ensure you have the `beta` version of React installed: + +```jsx +npm install next@latest react@beta react-dom@beta +``` + +### Enable SSR Streaming (Alpha) + +Concurrent features in React 18 include built-in support for server-side Suspense and SSR streaming support, allowing you to server-render pages using HTTP streaming. + +This is an experimental feature in Next.js 12, but once enabled, SSR will use the same [Edge Runtime](/docs/api-reference/edge-runtime.md) as [Middleware](/docs/middleware.md). + +To enable, use the experimental flag `concurrentFeatures: true`: + +```jsx +// next.config.js +module.exports = { + experimental: { + concurrentFeatures: true, + }, +} +``` + +Once enabled, you can use Suspense and SSR streaming for all pages. This also means that you can use Suspense-based data-fetching, `next/dynamic`, and React's built-in `React.lazy` with Suspense boundaries. + +```jsx +import dynamic from 'next/dynamic' +import { lazy, Suspense } from 'react' + +import Content from '../components/content' + +// These two ways are identical: +const Profile = dynamic(() => import('./profile'), { suspense: true }) +const Footer = lazy(() => import('./footer')) + +export default function Home() { + return ( +
+ }> + {/* A component that uses Suspense-based */} + + + }> + + + }> +
+ +
+ ) +} +``` + +## React Server Components + +React Server Components allow us to render everything, including the components themselves, on the server. This is fundamentally different from server-side rendering where you're pre-generating HTML on the server. With Server Components, there's **zero client-side JavaScript needed,** making page rendering faster. This improves the user experience of your application, pairing the best parts of server-rendering with client-side interactivity. + +### Enable React Server Components (Alpha) + +To use React Server Components, ensure you have React 18 installed. Then, turn on the `concurrentFeatures` and `serverComponents` options in `next.config.js`: + +```jsx +// next.config.js +module.exports = { + experimental: { + concurrentFeatures: true, + serverComponents: true, + }, +} +``` + +Next, if you already have customized `pages/_document` component, you need to remove the `getInitialProps` static method and the `getServerSideProps` export if there’s any, otherwise it won't work with server components. If no custom Document component is provided, Next.js will fallback to a default one like below. + +```jsx +// pages/_document.js +import { Html, Head, Main, NextScript } from 'next/document' + +export default function Document() { + return ( + + + +
+ + + + ) +} +``` + +Then, you can start using React Server Components. [See our example](https://github.com/vercel/next-rsc-demo) for more information. + +### Server Components APIs (Alpha) + +To run a component on the server, append `.server.js` to the end of the filename. For example `./pages/home.server.js` is a Server Component. + +For client components, add `.client.js`. For example, `./components/avatar.client.js`. + +You can then import other server or client components from any server component. Note: a server component **can not** be imported by a client component. Components without "server/client" extensions will be treated as "universal component" and can be used and rendered by both sides, depending on where it is imported. For example: + +```jsx +// pages/home.server.js + +import { Suspense } from 'react' + +import Profile from '../components/profile.server' +import Content from '../components/content.client' + +export default function Home() { + return ( +
+

Welcome to React Server Components

+ + + + +
+ ) +} +``` + +The `` and `` components will always be server-side rendered and streamed to the client, and will not be included by the client runtime. However `` will still be hydrated on the client-side, like normal React components. + +To see a full example, check out [link to the demo and repository](https://github.com/vercel/next-rsc-demo). + +## **Supported Next.js APIs** + +- `next/link` / `next/image` +- `next/document` / `next/app` +- Dynamic routing + +## **Unsupported Next.js APIs** + +While RSC and SSR streaming is still in the alpha stage, not all Next.js APIs are supported. The following Next.js APIs have limited functionality inside Server Components: + +- React internals: Most of React hooks such as `useContext`, `useState`, `useReducer`, `useEffect` and `useLayoutEffect` are not supported as of today since Server Components are executed per requests and aren't stateful. +- `next/head` +- Partial: Note that Inside `.client.js` components `useRouter` is supported +- Styled JSX +- CSS Modules +- Next.js I18n +- `getInitialProps`, `getStaticProps` and `getStaticPaths` + +React 18 without SSR streaming isn't affected. diff --git a/docs/advanced-features/security-headers.md b/docs/advanced-features/security-headers.md new file mode 100644 index 000000000000..c51718bdcb08 --- /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 ce949cbd4822..aa07ea4cf598 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 4451680a3544..e37bd2ababcf 100644 --- a/docs/advanced-features/static-html-export.md +++ b/docs/advanced-features/static-html-export.md @@ -11,27 +11,13 @@ description: Export your Next.js app to static HTML, and run it standalone witho -`next export` allows you to export your app to static HTML, which can be run standalone without the need of a Node.js server. +`next export` allows you to export your Next.js application to static HTML, which can be run standalone without the need of a Node.js server. It is recommended to only use `next export` if you don't need any of the [unsupported features](#unsupported-features) requiring a server. -The exported app supports almost every feature of Next.js, including dynamic routes, prefetching, preloading and dynamic imports. +If you're looking to build a hybrid site where only _some_ pages are prerendered to static HTML, Next.js already does that automatically. Learn more about [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) and [Incremental Static Regeneration](/docs/basic-features/data-fetching.md#incremental-static-regeneration). -`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` -> `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). -> -> 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. -> -> `next export` also causes features like [Incremental Static Generation](/docs/basic-features/data-fetching.md#fallback-true) and [Regeneration](/docs/basic-features/data-fetching.md#incremental-static-regeneration) to be disabled, as they require [`next start`](/docs/api-reference/cli.md#production) or a serverless deployment to function. - -## How to use it - -Develop your app as you normally do with Next.js. Then run: - -```bash -next build && next export -``` - -For that you may want to update the scripts in your `package.json` like this: +Update your build script in `package.json` to use `next export`: ```json "scripts": { @@ -39,37 +25,48 @@ For that you may want to update the scripts in your `package.json` like this: } ``` -And run it with: +Running `npm run build` will generate an `out` directory. -```bash -npm run build -``` - -Then you'll have a static version of your app in the `out` directory. +`next export` builds an HTML version of your app. During `next build`, [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) and [`getStaticPaths`](/docs/basic-features/data-fetching.md#getstaticpaths-static-generation) will generate an HTML file for each page in your `pages` directory (or more for [dynamic routes](/docs/routing/dynamic-routes.md). Then, `next export` will copy the already exported files into the correct directory. `getInitialProps` will generate the HTML files during `next export` instead of `next build`. -By default `next export` doesn't require any configuration. -It will output a static HTML file for each page in your `pages` directory (or more for [dynamic routes](/docs/routing/dynamic-routes.md), where it will call [`getStaticPaths`](/docs/basic-features/data-fetching.md#getstaticpaths-static-generation) and generate pages based on the result). For more advanced scenarios, you can define a parameter called [`exportPathMap`](/docs/api-reference/next.config.js/exportPathMap.md) in your [`next.config.js`](/docs/api-reference/next.config.js/introduction.md) file to configure exactly which pages will be generated. -## Deployment +## Supported Features + +The majority of core Next.js features needed to build a static site are supported, including: + +- [Dynamic Routes when using `getStaticPaths`](/docs/routing/dynamic-routes.md) +- Prefetching with `next/link` +- Preloading JavaScript +- [Dynamic Imports](/docs/advanced-features/dynamic-import.md) +- Any styling options (e.g. CSS Modules, styled-jsx) +- [Client-side data fetching](/docs/basic-features/data-fetching.md#fetching-data-on-the-client-side) +- [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) +- [`getStaticPaths`](/docs/basic-features/data-fetching.md#getstaticpaths-static-generation) +- [Image Optimization](/docs/basic-features/image-optimization.md) using a [custom loader](/docs/basic-features/image-optimization.md#loader) -By default, `next export` will generate an `out` directory, which can be served by any static hosting service or CDN. +## Unsupported Features -> We strongly recommend using [Vercel](https://vercel.com/) even if your Next.js app is fully static. [Vercel](https://vercel.com/) is optimized to make static Next.js apps blazingly fast. `next export` works with Zero Config deployments on Vercel. +Features that require a Node.js server, or dynamic logic that cannot be computed during the build process, are not supported: -## Caveats +- [Image Optimization](/docs/basic-features/image-optimization.md) (default loader) +- [Internationalized Routing](/docs/advanced-features/i18n-routing.md) +- [API Routes](/docs/api-routes/introduction.md) +- [Rewrites](/docs/api-reference/next.config.js/rewrites.md) +- [Redirects](/docs/api-reference/next.config.js/redirects.md) +- [Headers](/docs/api-reference/next.config.js/headers.md) +- [Middleware](/docs/middleware.md) +- [Incremental Static Regeneration](/docs/basic-features/data-fetching.md#incremental-static-regeneration) +- [`fallback: true`](/docs/basic-features/data-fetching.md#fallback-true) +- [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering) -- With `next export`, we build an HTML version of your app. At export time, we call [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) for each page that exports it, and pass the result to the page's component. It's also possible to use the older [`getInitialProps`](/docs/api-reference/data-fetching/getInitialProps.md) API instead of `getStaticProps`, but it comes with a few caveats: +### `getInitialProps` - - `getInitialProps` cannot be used alongside `getStaticProps` or `getStaticPaths` on any given page. If you have dynamic routes, instead of using `getStaticPaths` you'll need to configure the [`exportPathMap`](/docs/api-reference/next.config.js/exportPathMap.md) parameter in your [`next.config.js`](/docs/api-reference/next.config.js/introduction.md) file to let the exporter know which HTML files it should output. - - When `getInitialProps` is called during export, the `req` and `res` fields of its [`context`](/docs/api-reference/data-fetching/getInitialProps.md#context-object) parameter will be empty objects, since during export there is no server running. - - `getInitialProps` **will be called on every client-side navigation**, if you'd like to only fetch data at build-time, switch to `getStaticProps`. - - `getInitialProps` should fetch from an API and cannot use Node.js-specific libraries or the file system like `getStaticProps` can. +It's possible to use the [`getInitialProps`](/docs/api-reference/data-fetching/getInitialProps.md) API instead of `getStaticProps`, but it comes with a few caveats: - It's recommended to use and migrate towards `getStaticProps` over `getInitialProps` whenever possible. +- `getInitialProps` cannot be used alongside `getStaticProps` or `getStaticPaths` on any given page. If you have dynamic routes, instead of using `getStaticPaths` you'll need to configure the [`exportPathMap`](/docs/api-reference/next.config.js/exportPathMap.md) parameter in your [`next.config.js`](/docs/api-reference/next.config.js/introduction.md) file to let the exporter know which HTML files it should output. +- When `getInitialProps` is called during export, the `req` and `res` fields of its [`context`](/docs/api-reference/data-fetching/getInitialProps.md#context-object) parameter will be empty objects, since during export there is no server running. +- `getInitialProps` **will be called on every client-side navigation**, if you'd like to only fetch data at build-time, switch to `getStaticProps`. +- `getInitialProps` should fetch from an API and cannot use Node.js-specific libraries or the file system like `getStaticProps` can. -- The [`fallback: true`](/docs/basic-features/data-fetching.md#fallback-true) mode of `getStaticPaths` is not supported when using `next export`. -- [API Routes](/docs/api-routes/introduction.md) are not supported by this method because they can't be prerendered to HTML. -- [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering) cannot be used within pages because the method requires a server. Consider using [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) instead. -- [Internationalized Routing](/docs/advanced-features/i18n-routing.md) is not supported as it requires Next.js' server-side routing. -- The [`next/image`](/docs/api-reference/next/image.md) component's default loader is not supported when using `next export`. However, other [loader](/docs/basic-features/image-optimization.md#loader) options will work. +We recommend migrating towards `getStaticProps` over `getInitialProps` whenever possible. diff --git a/docs/advanced-features/using-mdx.md b/docs/advanced-features/using-mdx.md new file mode 100644 index 000000000000..fc0411f6b1ec --- /dev/null +++ b/docs/advanced-features/using-mdx.md @@ -0,0 +1,192 @@ +--- +description: Learn how to use @next/mdx in your Next.js project. +--- + +# Using MDX with Next.js + +MDX is a superset of markdown that lets you write JSX directly in your markdown files. It is a powerful way to add dynamic interactivity, and embed components within your content, helping you to bring your pages to life. + +Next.js supports MDX through a number of different means, this page will outline some of the ways you can begin integrating MDX into your Next.js project. + +## Why use MDX? + +Authoring in markdown is an intuitive way to write content, its terse syntax, once adopted, can enable you to write content that is both readable and maintainable. Because you can use `HTML` elements in your markdown, you can also get creative when styling your markdown pages. + +However, because markdown is essentially static content, you can't create _dynamic_ content based on user interactivity. Where MDX shines is in its ability to let you create and use your React components directly in the markup. This opens up a wide range of possibilities when composing your sites pages with interactivity in mind. + +## MDX Plugins + +Internally MDX uses remark and rehype. Remark is a markdown processor powered by a plugins ecosystem. This plugin ecosystem lets you parse code, transform `HTML` elements, change syntax, extract frontmatter, and more. + +Rehype is an `HTML` processor, also powered by a plugin ecosystem. Similar to remark, these plugins let you manipulate, sanitize, compile and configure all types of data, elements and content. + +To use a plugin from either remark or rehype, you will need to add it to the MDX packages config. + +## `@next/mdx` + +The `@next/mdx` package is configured in the `next.config.js` file at your projects root. **It sources data from local files**, allowing you to create pages with a `.mdx` extension, directly in your `/pages` directory. + +### Setup `@next/mdx` in Next.js + +The following steps outline how to setup `@next/mdx` in your Next.js project: + +1. Install the required packages: + + ```bash + npm install @next/mdx @mdx-js/loader + ``` + +2. Require the package and configure to support top level `.mdx` pages. The following adds the `options` object key allowing you to pass in any plugins: + + ```js + // next.config.js + + const withMDX = require('@next/mdx')({ + extension: /\.mdx?$/, + options: { + remarkPlugins: [], + rehypePlugins: [], + }, + }) + module.exports = withMDX({ + pageExtensions: ['js', 'jsx', 'md', 'mdx'], + }) + ``` + +3. Create a new MDX page within the `/pages` directory: + + ```bash + - /pages + - my-mdx-page.mdx + - package.json + ``` + +## Using Components, Layouts and Custom Elements + +You can now import a React component directly inside your MDX page: + +```md +import { MyComponent } from 'my-components' + +# My MDX page + +This is a list in markdown: + +- One +- Two +- Three + +Checkout my React component: + + +``` + +### Frontmatter + +Frontmatter is a YAML like key/value pairing that can be used to store data about a page. `@next/mdx` does **not** support frontmatter by default, though there are many solutions for adding frontmatter to your MDX content, such as [gray-matter](https://github.com/jonschlinkert/gray-matter). + +To access page metadata with `@next/mdx`, you can export a meta object from within the `.mdx` file: + +```md +export const meta = { +author: 'Rich Haines' +} + +# My MDX page +``` + +### Layouts + +To add a layout to your MDX page, create a new component and import it into the MDX page. Then you can wrap the MDX page with your layout component: + +```md +import { MyComponent, MyLayoutComponent } from 'my-components' + +export const meta = { +author: 'Rich Haines' +} + +# My MDX Page with a Layout + +This is a list in markdown: + +- One +- Two +- Three + +Checkout my React component: + + + +export default = ({ children }) => {children} +``` + +### Custom Elements + +One of the pleasant aspects of using markdown, is that it maps to native `HTML` elements, making writing fast, and intuitive: + +```md +# H1 heading + +## H2 heading + +This is a list in markdown: + +- One +- Two +- Three +``` + +The above generates the following `HTML`: + +```html +

H1 heading

+ +

H2 heading

+ +

This is a list in markdown:

+ +
    +
  • One
  • +
  • Two
  • +
  • Three
  • +
+``` + +When you want to style your own elements to give a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to `HTML` elements. To do this you use the `MDXProvider` and pass a components object as a prop. Each object key in the components object maps to a `HTML` element name. + +```jsx +// pages/index.js + +import { MDXProvider } from '@mdx-js/react' +import Image from 'next/image' +import { Heading, Text, Pre, Code, Table } from 'my-components' + +const ResponsiveImage = (props) => ( + {props.alt} +) + +const components = { + img: ResponsiveImage, + h1: Heading.H1, + h2: Heading.H2, + p: Text, + code: Pre, + inlineCode: Code, +} + +export default function Post(props) { + return ( + +
+ + ) +} +``` + +## Helpful Links + +- [MDX](https://mdxjs.com) +- [`@next/mdx`](https://www.npmjs.com/package/@next/mdx) +- [remark](https://github.com/remarkjs/remark) +- [rehype](https://github.com/rehypejs/rehype) diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md index bd9959252349..b79edb5f0bbc 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 1b96f94179d2..3a00e008ac08 100644 --- a/docs/api-reference/create-next-app.md +++ b/docs/api-reference/create-next-app.md @@ -4,27 +4,36 @@ 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 +npx create-next-app@latest # or 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@latest --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? `create-next-app` allows you to create a new Next.js app within seconds. It is officially maintained by the creators of Next.js, and includes a number of benefits: -- **Interactive Experience**: Running `npx create-next-app` (with no arguments) launches an interactive experience that guides you through setting up a project. +- **Interactive Experience**: Running `npx create-next-app@latest` (with no arguments) launches an interactive experience that guides you through setting up a project. - **Zero Dependencies**: Initializing a project is as quick as one second. Create Next App has zero dependencies. - **Offline Support**: Create Next App will automatically detect if you're offline and bootstrap your project using your local package cache. - **Support for Examples**: Create Next App can bootstrap your application using an example from the Next.js examples collection (e.g. `npx create-next-app --example api-routes`). diff --git a/docs/api-reference/data-fetching/getInitialProps.md b/docs/api-reference/data-fetching/getInitialProps.md index f46b34615f56..611533a9b3fc 100644 --- a/docs/api-reference/data-fetching/getInitialProps.md +++ b/docs/api-reference/data-fetching/getInitialProps.md @@ -139,6 +139,6 @@ For more information on what to do next, we recommend the following sections: diff --git a/docs/api-reference/edge-runtime.md b/docs/api-reference/edge-runtime.md new file mode 100644 index 000000000000..95e3f914472a --- /dev/null +++ b/docs/api-reference/edge-runtime.md @@ -0,0 +1,103 @@ +--- +description: The Next.js Edge Runtime is based on standard Web APIs. Learn more about the supported APIs available. +--- + +# Edge Runtime + +The Next.js Edge Runtime is based on standard Web APIs, which is used by [Middleware](/docs/middleware.md). + +## Runtime APIs + +### Globals + +- [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) +- [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) +- [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) + +### Base64 + +- [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob): Decodes a string of data which has been encoded using base-64 encoding +- [`btoa`](https://developer.mozilla.org/en-US/docs/Web/API/btoa): Creates a base-64 encoded ASCII string from a string of binary data + +### Encoding + +- [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder): Takes a stream of code points as input and emits a stream of bytes (UTF8) +- [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder): Takes a stream of bytes as input and emit a stream of code points + +### Environment + +- `process.env`: Holds an object with all environment variables for both production and development in the exact same way as any other page or API in Next.js + +### Fetch + +The [Web Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) can be used from the runtime, enabling you to use Middleware as a proxy, or connect to external storage APIs + +A potential caveat to using the Fetch API in a Middleware function is latency. For example, if you have a Middleware function running a fetch request to New York, and a user accesses your site from London, the request will be resolved from the nearest Edge to the user (in this case, London), to the origin of the request, New York. There is a risk this could happen on every request, making your site slow to respond. When using the Fetch API, you _must_ make sure it does not run on every single request made. + +### Streams + +- [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream): Consists of a pair of streams: a writable stream known as its writable side, and a readable stream, known as its readable side. Writes to the writable side, result in new data being made available for reading from the readable side. Support for web streams is quite limited at the moment, although it is more extended in the development environment +- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream): A readable stream of byte data +- [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream): A standard abstraction for writing streaming data to a destination, known as a sink + +### Timers + +- [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval): Schedules a function to execute every time a given number of milliseconds elapses +- [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval): Cancels the repeated execution set using `setInterval()` +- [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout): Schedules a function to execute in a given amount of time +- [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout): Cancels the delayed execution set using `setTimeout()` + +### Web + +- [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers): A [WHATWG](https://whatwg.org/) implementation of the headers API +- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL): A WHATWG implementation of the URL API. +- [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams): A WHATWG implementation of `URLSearchParams` + +### Crypto + +- [`Crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto): The `Crypto` interface represents basic cryptography features available in the current context +- [`crypto.randomUUID`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID): Lets you generate a v4 UUID using a cryptographically secure random number generator +- [`crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues): Lets you get cryptographically strong random values +- [`crypto.subtle`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/subtle): A read-only property that returns a [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) which can then be used to perform low-level cryptographic operations + +### Logging + +- [`console.debug`](https://developer.mozilla.org/en-US/docs/Web/API/console/debug): Outputs a message to the console with the log level debug +- [`console.info`](https://developer.mozilla.org/en-US/docs/Web/API/console/info): Informative logging of information. You may use string substitution and additional arguments with this method +- [`console.clear`](https://developer.mozilla.org/en-US/docs/Web/API/console/clear): Clears the console +- [`console.dir`](https://developer.mozilla.org/en-US/docs/Web/API/console/dir): Displays an interactive listing of the properties of a specified JavaScript object +- [`console.count`](https://developer.mozilla.org/en-US/docs/Web/API/console/count): Log the number of times this line has been called with the given label +- [`console.time`](https://developer.mozilla.org/en-US/docs/Web/API/console/time): Starts a timer with a name specified as an input parameter + +## Unsupported APIs + +The Edge Runtime has some restrictions including: + +- Native Node.js APIs **are not supported**. For example, you can't read or write to the filesystem +- Node Modules _can_ be used, as long as they implement ES Modules and do not use any native Node.js APIs +- Calling `require` directly is **not allowed**. Use ES Modules instead + +The following JavaScript language features are disabled, and **will not work:** + +- [`eval`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval): Evaluates JavaScript code represented as a string +- [`new Function(evalString)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function): Creates a new function with the code provided as an argument + +The following Web APIs are currently not supported, but will be in the future: + +- [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController): Abort one or more Web requests when desired + +## Related + + + + diff --git a/docs/api-reference/next.config.js/basepath.md b/docs/api-reference/next.config.js/basepath.md index 143763f0f16f..0df919b35a05 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/build-indicator.md b/docs/api-reference/next.config.js/build-indicator.md new file mode 100644 index 000000000000..cccaa3d4bfad --- /dev/null +++ b/docs/api-reference/next.config.js/build-indicator.md @@ -0,0 +1,29 @@ +--- +description: In development mode, pages include an indicator to let you know if your new code it's being compiled. You can opt-out of it here. +--- + +# Build indicator + +When you edit your code, and Next.js is compiling the application, a compilation indicator appears in the bottom right corner of the page. + +> **Note:** This indicator is only present in development mode and will not appear when building and running the app in production mode. + +In some cases this indicator can be misplaced on your page, for example, when conflicting with a chat launcher. To change its position, open `next.config.js` and set the `buildActivityPosition` in the `devIndicators` object to `bottom-right` (default), `bottom-left`, `top-right` or `top-left`: + +```js +module.exports = { + devIndicators: { + buildActivityPosition: 'bottom-right', + }, +} +``` + +In some cases this indicator might not be useful for you. To remove it, open `next.config.js` and disable the `buildActivity` config in `devIndicators` object: + +```js +module.exports = { + devIndicators: { + buildActivity: false, + }, +} +``` 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 60ebbed5abd2..20056521d6e7 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 f457b9083d17..a3f0d6132424 100644 --- a/docs/api-reference/next.config.js/custom-page-extensions.md +++ b/docs/api-reference/next.config.js/custom-page-extensions.md @@ -10,11 +10,40 @@ 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**: 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. +> **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`, `_middleware.js` as well as files under `pages/api/`. For example, setting `pageExtensions: ['page.tsx', 'page.ts']` means the following files: `_document.tsx`, `_app.tsx`, `_middleware.ts`, `pages/users.tsx` and `pages/api/users.ts` will have to be renamed to `_document.page.tsx`, `_app.page.tsx`, `_middleware.page.ts`, `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`, `_middleware.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 d339d1ae1454..6b4535c7e80f 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 000000000000..bfa79ad3d8b6 --- /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 9f93567c721e..a5e4420ffa11 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 73623b08a289..7ac83a2b5229 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 @@ -70,7 +83,7 @@ module.exports = { }, ], }, - ], + ] }, } ``` @@ -96,7 +109,7 @@ module.exports = { }, ], }, - ], + ] }, } ``` @@ -122,7 +135,7 @@ module.exports = { }, ], }, - ], + ] }, } ``` @@ -144,7 +157,125 @@ 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', + }, + ], + }, + ] }, } ``` @@ -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: 'world', + }, + ], + }, ] }, } @@ -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 000000000000..650c678bfd07 --- /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 a4e531b4ae75..6ecd28297c89 100644 --- a/docs/api-reference/next.config.js/introduction.md +++ b/docs/api-reference/next.config.js/introduction.md @@ -4,29 +4,51 @@ description: learn more about the configuration file used by Next.js to handle y # next.config.js -For custom advanced behavior of Next.js, you can create a `next.config.js` in the root of your project directory (next to `package.json`). +For custom advanced configuration of Next.js, you can create a `next.config.js` or `next.config.mjs` file in the root of your project directory (next to `package.json`). `next.config.js` is a regular Node.js module, not a JSON file. It gets used by the Next.js server and build phases, and it's not included in the browser build. 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 +``` + +If you need [ECMAScript modules](https://nodejs.org/api/esm.html), you can use `next.config.mjs`: + +```js +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + /* config options here */ +} + +export default 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-L5). Phases can be imported from `next/constants`: ```js const { PHASE_DEVELOPMENT_SERVER } = require('next/constants') @@ -44,7 +66,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 d442b81ac1ad..b844bf84506f 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 4ee91c3fc48f..6a8e110db82f 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. @@ -37,7 +45,24 @@ 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. +- `permanent` `true` or `false` - if `true` will use the 308 status code which instructs clients/search engines to cache the redirect forever, if `false` will use the 307 status code which is temporary and is not cached. +- `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 a6f4efcfcf36..3a54cf4288ef 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 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 @@ -89,6 +148,8 @@ module.exports = { } ``` +Note: for static pages from the [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) or [prerendering](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) params from rewrites will be parsed on the client after hydration and provided in the query. + ## Path Matching Path matches are allowed, for example `/blog/:slug` will match `/blog/hello-world` (no nested paths): @@ -140,6 +201,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 +320,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 +396,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 4fad79dfcd24..ee2757f872cc 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.config.js/url-imports.md b/docs/api-reference/next.config.js/url-imports.md new file mode 100644 index 000000000000..bb50c6ffe6b1 --- /dev/null +++ b/docs/api-reference/next.config.js/url-imports.md @@ -0,0 +1,94 @@ +--- +description: Configure Next.js to allow importing modules from external URLs (experimental). +--- + +# URL Imports + +URL imports are an experimental feature that allows you to import modules directly from external servers (instead of from the local disk). + +> **Warning**: This feature is experimental. Only use domains that you trust to download and execute on your machine. Please exercise +> discretion, and caution until the feature is flagged as stable. + +To opt-in, add the allowed URL prefixes inside `next.config.js`: + +```js +module.exports = { + experimental: { + urlImports: ['https://example.com/modules/'], + }, +} +``` + +Then, you can import modules directly from URLs: + +```js +import { a, b, c } from 'https://example.com/modules/some/module.js' +``` + +URL Imports can be used everywhere normal package imports can be used. + +## Security Model + +This feature is being designed with **security as the top priority**. To start, we added an experimental flag forcing you to explicitly allow the domains you accept URL imports from. We're working to take this further by limiting URL imports to execute in the browser sandbox using the [Edge Runtime](/docs/api-reference/edge-runtime.md). This runtime is used by [Middleware](/docs/middleware.md) as well as [Next.js Live](https://vercel.com/live). + +## Lockfile + +When using URL imports, Next.js will create a lockfile in the `next.lock` directory. +This directory is intended to be committed to Git and should **not be included** in your `.gitignore` file. + +- When running `next dev`, Next.js will download and add all newly discovered URL Imports to your lockfile +- When running `next build`, Next.js will use only the lockfile to build the application for production + +Typically, no network requests are needed and any outdated lockfile will cause the build to fail. +One exception is resources that respond with `Cache-Control: no-cache`. +These resources will have a `no-cache` entry in the lockfile and will always be fetched from the network on each build. + +## Examples + +### Skypack + +```js +import confetti from 'https://cdn.skypack.dev/canvas-confetti' +import { useEffect } from 'react' + +export default () => { + useEffect(() => { + confetti() + }) + return

Hello

+} +``` + +### Static Image Imports + +```js +import Image from 'next/image' +import logo from 'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png' + +export default () => ( +
+ +
+) +``` + +### URLs in CSS + +```css +.className { + background: url('https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png'); +} +``` + +### Asset Imports + +```js +import Image from 'next/image' + +const logo = new URL( + 'https://github.com/vercel/next.js/raw/canary/test/integration/production/public/vercel.png', + import.meta.url +) + +export default () =>
{logo.pathname}
+``` diff --git a/docs/api-reference/next/amp.md b/docs/api-reference/next/amp.md index 2e58efb82dce..6cc212791168 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 b642c18cd576..96a6d14a5ea3 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 by using the `dangerouslySetInnerHTML` property: + +```jsx + + + + ) +} +``` + +## 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 000000000000..b0d5fd60fd99 --- /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-options-type.md b/errors/invalid-dynamic-options-type.md new file mode 100644 index 000000000000..e5aa443ac58e --- /dev/null +++ b/errors/invalid-dynamic-options-type.md @@ -0,0 +1,31 @@ +# Invalid options type in a `next/dynamic` call + +#### Why This Error Occurred + +You have an invalid options type in a `next/dynamic` call. The options must be an object literal. + +#### Possible Ways to Fix It + +**Before** + +```jsx +import dynamic from 'next/dynamic' + +const options = { loading: () =>

...

, ssr: false } +const DynamicComponent = dynamic(() => import('../components/hello'), options) +``` + +**After** + +```jsx +import dynamic from 'next/dynamic' + +const DynamicComponent = dynamic(() => import('../components/hello'), { + loading: () =>

...

, + ssr: false, +}) +``` + +### Useful Links + +- [Dynamic Import](https://nextjs.org/docs/advanced-features/dynamic-import) diff --git a/errors/invalid-dynamic-suspense.md b/errors/invalid-dynamic-suspense.md new file mode 100644 index 000000000000..11e4d6134d73 --- /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-getstaticpaths-value.md b/errors/invalid-getstaticpaths-value.md index f1e8543d9503..f34640ba00af 100644 --- a/errors/invalid-getstaticpaths-value.md +++ b/errors/invalid-getstaticpaths-value.md @@ -35,6 +35,7 @@ There are two required properties: } } ``` -1. `fallback`: this property is a [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean), specifying whether or not a fallback version of this page should be generated. +1. `fallback`: this property can be a [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean), specifying whether or not a fallback version of this page should be generated, or a string `'blocking'` to wait for the generation: - Enabling `fallback` (via `true`) allows you to return a subset of all the possible paths that should be statically generated. At runtime, Next.js will statically generate the remaining paths the **first time they are requested**. Consecutive calls to the path will be served as-if it was statically generated at build-time. This reduces build times when dealing with thousands or millions of pages. - Disabling `fallback` (via `false`) requires you return the full collection of paths you would like to statically generate at build-time. At runtime, any path that was not generated at build-time **will 404**. + - If `fallback` is `'blocking'`, new paths not returned by getStaticPaths will wait for the HTML to be generated, identical to SSR (hence why blocking), and then be cached for future requests so it only happens once per path. diff --git a/errors/invalid-getstaticprops-value.md b/errors/invalid-getstaticprops-value.md index ccbfd7232d07..16fcf6a9df15 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 1a1be32af883..b840d14a61f9 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 c060c62728da..581724439222 100644 --- a/errors/invalid-images-config.md +++ b/errors/invalid-images-config.md @@ -17,9 +17,16 @@ module.exports = { imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // limit of 50 domains values domains: [], + // path prefix for Image Optimization API, useful with `loader` 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, + // ordered list of acceptable optimized image formats (mime types) + formats: ['image/webp'], }, } ``` @@ -27,3 +34,4 @@ module.exports = { ### 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/invalid-project-dir-casing.md b/errors/invalid-project-dir-casing.md new file mode 100644 index 000000000000..8f650f95acac --- /dev/null +++ b/errors/invalid-project-dir-casing.md @@ -0,0 +1,16 @@ +# Invalid Project Directory Casing + +#### Why This Error Occurred + +When starting Next.js, the current directory is a different casing than the actual directory on your filesystem. This can cause files to resolve inconsistently. + +This can occur when using a case-insensitive filesystem. For example, opening PowerShell on Windows navigating to `cd path/to/myproject` instead of `cd path/to/MyProject`. + +#### Possible Ways to Fix It + +Ensure the casing for the current working directory matches the actual case of the real directory. Use a terminal that enforces case-sensitivity. + +### Useful Links + +- [Next.js CLI documentation](https://nextjs.org/docs/api-reference/cli) +- [Case sensitivity in filesystems](https://en.wikipedia.org/wiki/Case_sensitivity#In_filesystems) diff --git a/errors/large-page-data.md b/errors/large-page-data.md new file mode 100644 index 000000000000..62600879b441 --- /dev/null +++ b/errors/large-page-data.md @@ -0,0 +1,13 @@ +# Large Page Data + +#### Why This Error Occurred + +One of your pages includes a large amount of page data (>= 128KB). This can negatively impact performance since page data must be parsed by the client before the page is hydrated. + +#### Possible Ways to Fix It + +Reduce the amount of data returned from `getStaticProps`, `getServerSideProps`, or `getInitialProps` to only the essential data to render the page. + +### Useful Links + +- [Data Fetching Documentation](https://nextjs.org/docs/basic-features/data-fetching) diff --git a/errors/link-multiple-children.md b/errors/link-multiple-children.md new file mode 100644 index 000000000000..54476589c690 --- /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 000000000000..33fec658066e --- /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 000000000000..d57216c99fdb --- /dev/null +++ b/errors/manifest.json @@ -0,0 +1,529 @@ +{ + "routes": [ + { + "title": "Messages", + "heading": true, + "routes": [ + { + "title": "react-hydration-error", + "path": "/errors/react-hydration-error.md" + }, + { + "title": "beta-middleware", + "path": "/errors/beta-middleware.md" + }, + { + "title": "failed-loading-swc", + "path": "/errors/failed-loading-swc.md" + }, + { + "title": "deprecated-target-config", + "path": "/errors/deprecated-target-config.md" + }, + { + "title": "custom-document-image-import", + "path": "/errors/custom-document-image-import.md" + }, + { + "title": "large-page-data", + "path": "/errors/large-page-data.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-server-import-in-page", + "path": "/errors/no-server-import-in-page.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": "sharp-version-avif", + "path": "/errors/sharp-version-avif.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" + }, + { + "title": "invalid-project-dir-casing", + "path": "/errors/invalid-project-dir-casing.md" + }, + { + "title": "swc-disabled", + "path": "/errors/swc-disabled.md" + }, + { + "title": "swc-minify-enabled", + "path": "/errors/swc-minify-enabled.md" + }, + { + "title": "middleware-new-signature", + "path": "/errors/middleware-new-signature.md" + }, + { + "title": "experimental-jest-transformer", + "path": "/errors/experimental-jest-transformer.md" + }, + { + "title": "invalid-dynamic-options-type", + "path": "/errors/invalid-dynamic-options-type.md" + } + ] + } + ] +} diff --git a/errors/max-custom-routes-reached.md b/errors/max-custom-routes-reached.md new file mode 100644 index 000000000000..94398ff98777 --- /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/middleware-new-signature.md b/errors/middleware-new-signature.md new file mode 100644 index 000000000000..b7bb43307b5c --- /dev/null +++ b/errors/middleware-new-signature.md @@ -0,0 +1,37 @@ +# Deprecated Middleware API Signature + +#### Why This Error Occurred + +Your application is using a Middleware function that is using parameters from the deprecated API. + +```typescript +// _middleware.js +import { NextResponse } from 'next/server' + +export function middleware(event) { + if (event.request.nextUrl.pathname === '/blocked') { + event.respondWith( + new NextResponse(null, { + status: 403, + }) + ) + } +} +``` + +#### Possible Ways to Fix It + +Update to use the new API for Middleware: + +```typescript +// _middleware.js +import { NextResponse } from 'next/server' + +export function middleware(request) { + if (request.nextUrl.pathname === '/blocked') { + return new NextResponse(null, { + status: 403, + }) + } +} +``` diff --git a/errors/module-not-found.md b/errors/module-not-found.md new file mode 100644 index 000000000000..5cd6401e0e13 --- /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 000000000000..e30b02517738 --- /dev/null +++ b/errors/next-config-error.md @@ -0,0 +1,16 @@ +# next.config.js Loading Error + +#### Why This Error Occurred + +When attempting to load your `next.config.js` or `next.config.mjs` file, an error occurred. This could be due to a syntax error or attempting to `require`/`import` 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. + +> Note: This config file is not transpiled by Next.js, so only use features supported by your current Node.js version. + +### 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 000000000000..0df5c6efa712 --- /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 000000000000..9a03dcb26a3d --- /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 5fdcc8bbb773..e69525ad03d2 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 000000000000..1d968090e1f1 --- /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 000000000000..771e99674d5c --- /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 000000000000..6f05aa9a7251 --- /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 000000000000..2f94c774b0ca --- /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 000000000000..431b61e4588d --- /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 `config.staticPageGenerationTimeout` 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 000000000000..9c9a76819dd7 --- /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, webp, and avif 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 658ad57f2857..e8c73f1c6996 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 889fe55bcbc6..f121c889d04b 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 dbc5a2ed346f..77733fea9472 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 26f5a03e1420..75b1c80c1d49 100644 --- a/errors/prerender-error.md +++ b/errors/prerender-error.md @@ -7,5 +7,8 @@ While prerendering a page an error occurred. This can occur for many reasons fro #### Possible Ways to Fix It - 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 code that assumes a prop is available, even when it might not be +- Set default values for all dynamic pages' props (avoid `undefined`, use `null` instead so it can be serialized) - 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-hydration-error.md b/errors/react-hydration-error.md new file mode 100644 index 000000000000..86c280d42a3d --- /dev/null +++ b/errors/react-hydration-error.md @@ -0,0 +1,53 @@ +# React Hydration Error + +#### Why This Error Occurred + +While rendering your application, there was a difference between the React tree that was pre-rendered (SSR/SSG) and the React tree that rendered during the first render in the Browser. The first render is called Hydration which is a [feature of React](https://reactjs.org/docs/react-dom.html#hydrate). + +This can cause the React tree to be out of sync with the DOM and result in unexpected content/attributes being present. + +#### Possible Ways to Fix It + +In general this issue is caused by using a specific library or application code that is relying on something that could differ between pre-rendering and the browser. An example of this is using `window` in a component's rendering. + +An example: + +```jsx +function MyComponent() { + // This condition depends on `window`. During the first render of the browser the `color` variable will be different + const color = typeof window !== 'undefined' ? 'red' : 'blue + // As color is passed as a prop there is a mismatch between what was rendered server-side vs what was rendered in the first render + return

Hello World!

+} +``` + +How to fix it: + +```jsx +// In order to prevent the first render from being different you can use `useEffect` which is only executed in the browser and is executed during hydration +import { useEffect, useState } from 'react' +function MyComponent() { + // The default value is 'blue', it will be used during pre-rendering and the first render in the browser (hydration) + const [color, setColor] = useState('blue') + // During hydration `useEffect` is called. `window` is available in `useEffect`. In this case because we know we're in the browser checking for window is not needed. If you need to read something from window that is fine. + // By calling `setColor` in `useEffect` a render is triggered after hydrating, this causes the "browser specific" value to be available. In this case 'red'. + useEffect(() => setColor('red'), []) + // As color is a state passed as a prop there is no mismatch between what was rendered server-side vs what was rendered in the first render. After useEffect runs the color is set to 'red' + return

Hello World!

+} +``` + +Common causes with css-in-js libraries: + +- When using Styled Components / Emotion + - When css-in-js libraries are not set up for pre-rendering (SSR/SSG) it will often lead to a hydration mismatch. In general this means the application has to follow the Next.js example for the library. For example if `pages/_document` is missing and the Babel plugin is not added. + - Possible fix for Styled Components: https://github.com/vercel/next.js/tree/canary/examples/with-styled-components + - If you want to leverage Styled Components with the new Next.js Compiler in Next.js 12 there is an [experimental flag available](https://github.com/vercel/next.js/discussions/30174#discussion-3643870) + - Possible fix for Emotion: https://github.com/vercel/next.js/tree/canary/examples/with-emotion +- When using other css-in-js libraries + - Similar to Styled Components / Emotion css-in-js libraries generally need configuration specified in their examples in the [examples directory](https://github.com/vercel/next.js/tree/canary/examples) + +### Useful Links + +- [React Hydration Documentation](https://reactjs.org/docs/react-dom.html#hydrate) +- [Josh Comeau's article on React Hydration](https://www.joshwcomeau.com/react/the-perils-of-rehydration/) diff --git a/errors/react-version.md b/errors/react-version.md index cfa4412e8846..713254102491 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 000000000000..96903efad9ca --- /dev/null +++ b/errors/sharp-missing-in-production.md @@ -0,0 +1,19 @@ +# 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) +- [`next/image` Documentation](https://nextjs.org/docs/api-reference/next/image) diff --git a/errors/sharp-version-avif.md b/errors/sharp-version-avif.md new file mode 100644 index 000000000000..dcd90aea3662 --- /dev/null +++ b/errors/sharp-version-avif.md @@ -0,0 +1,24 @@ +# Sharp Version Does Not Support AVIF + +#### Why This Error Occurred + +The `next/image` component's default loader uses [`sharp`](https://www.npmjs.com/package/sharp) if its installed. + +You are seeing this error because you have an outdated version of [`sharp`](https://www.npmjs.com/package/sharp) installed that does not support the AVIF image format. + +AVIF support was added to [`sharp`](https://www.npmjs.com/package/sharp) in version 0.27.0 (December 2020) so your installed version is likely older. + +#### Possible Ways to Fix It + +- Install the latest version of `sharp` by running `yarn add sharp@latest` in your project directory +- If you're using the `NEXT_SHARP_PATH` environment variable, then update the `sharp` install referenced in that path, for example `cd "$NEXT_SHARP_PATH/../" && yarn add sharp@latest` +- If you cannot upgrade `sharp`, you can instead disable AVIF by configuring [`formats`](https://nextjs.org/docs/api-reference/next/image#image-formats) in your `next.config.js` + +After choosing an option above, reboot the server by running either `next dev` or `next start` for development or production respectively. + +> 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) +- [`next/image` Documentation](https://nextjs.org/docs/api-reference/next/image) diff --git a/errors/static-dir-deprecated.md b/errors/static-dir-deprecated.md index f15dc12d814c..76e13986aed9 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 000000000000..419731812b4d --- /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 `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/swc-disabled.md b/errors/swc-disabled.md new file mode 100644 index 000000000000..701f260c785b --- /dev/null +++ b/errors/swc-disabled.md @@ -0,0 +1,17 @@ +# SWC disabled + +#### Why This Message Occurred + +Next.js now uses Rust-based compiler [SWC](https://swc.rs/) to compile JavaScript/TypeScript. This new compiler is up to 17x faster than Babel when compiling individual files and up to 5x faster Fast Refresh. + +Next.js provides full backwards compatibility with applications that have [custom Babel configuration](https://nextjs.org/docs/advanced-features/customizing-babel-config). All transformations that Next.js handles by default like styled-jsx and tree-shaking of `getStaticProps` / `getStaticPaths` / `getServerSideProps` have been ported to Rust. + +When an application has custom Babel configuration Next.js will automatically opt-out of using SWC for compiling JavaScript/Typescript and will fall back to using Babel in the same way that it was used in Next.js 11. + +Many of the integrations with external libraries that currently require custom Babel transformations will be ported to Rust-based SWC transforms in the near future. These include but are not limited to: + +- Styled Components +- Emotion +- Relay + +In order to prioritize transforms that will help you adopt SWC please provide your `.babelrc` on [the feedback thread](https://github.com/vercel/next.js/discussions/30174). diff --git a/errors/swc-minify-enabled.md b/errors/swc-minify-enabled.md new file mode 100644 index 000000000000..712affcf27a7 --- /dev/null +++ b/errors/swc-minify-enabled.md @@ -0,0 +1,7 @@ +# SWC minify enabled + +#### Why This Message Occurred + +The application has enabled `swcMinify` in `next.config.js`. By opting in minification will happen using the [SWC](https://swc.rs) minifier instead of Terser. This new minifier is 7x faster than Terser with comparable output. We're actively working on optimizing the output size and minification speed further. + +If you have feedback about the minification, please provide it on [the feedback thread](https://github.com/vercel/next.js/discussions/30237). diff --git a/errors/template.md b/errors/template.md new file mode 100644 index 000000000000..711da0c52abf --- /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 50db5b6b5ad7..df352d8e0706 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 000000000000..6c5e222c3069 --- /dev/null +++ b/errors/webpack5.md @@ -0,0 +1,46 @@ +# Webpack 5 Adoption + +#### Why This Message Occurred + +Next.js has adopted webpack 5 as the default for compilation. 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 has been 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 could still opt-out and use webpack 4 to help with backwards compatibility using `webpack5: false` in `next.config.js` +- In Next.js 12 webpack 4 support was 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 03d46cea4d39..902d79e24b7f 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 a08741654ad6..6513b825ee0a 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 1213e4471715..4db9d29709f0 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 3c62fecf57f3..349de02f4d84 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 d577702ebefa..349de02f4d84 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 d033597980c0..349de02f4d84 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 abea34df8353..8aec1e56c79c 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 a5caa9e79081..a40dca11de1d 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 68ca3be46917..6385acf21368 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 7c0ddd6572ff..448f8254297e 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 0e652164cffe..59349fb0e57d 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 ce16f159e4c9..bcbf81f2410c 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 2d5126981830..96c454e0527c 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 43263e3486f1..c1de5dc4ba83 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 320060be8778..9b4209908adb 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 c42045d94020..88af4336ccc6 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 5fdee8ab744b..5ba1b348401f 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 0338eb264847..bc1300882acc 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 1a31922c94a9..1c51cf4d7e38 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 eb2b811a582b..e4329bea00b6 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 0dcb066bbcbd..0a29f3ce8898 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 7ed15b6ff90a..44a12e118ddc 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 a7e21e6f70ab..ac005757491a 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 e3397b2a9fc4..9328d2584636 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 b43827f60cb6..568cc38e0b84 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 5183ca216652..c8f176bd5e2c 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 883f3d7720f0..4b23e0ce0f19 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 334bf15feedb..87e9fa6321f9 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/data.js b/examples/api-routes/data.js index ce2acde5041c..f57a9d0786fd 100644 --- a/examples/api-routes/data.js +++ b/examples/api-routes/data.js @@ -61,7 +61,7 @@ export const people = [ }, { id: '7', - name: 'Beru Whitesun lars', + name: 'Beru Whitesun Lars', height: '165', mass: '75', hair_color: 'brown', diff --git a/examples/api-routes/package.json b/examples/api-routes/package.json index f6156d9d3fa1..56e1c8107ad3 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", - "swr": "0.1.18" - }, - "license": "MIT" + "react": "^17.0.2", + "react-dom": "^17.0.2", + "swr": "^1.0.1" + } } diff --git a/examples/auth0/.env.local.example b/examples/auth0/.env.local.example index 4946e625c14e..40f6027fa2d3 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 90cd8b521b4c..e2b2d97bb438 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 84fe93be3df9..ffcf3450c589 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 16f771f656d6..96caf0e6d5c9 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/auth0/pages/advanced/ssr-profile.js b/examples/auth0/pages/advanced/ssr-profile.js index 84d96d4dbee4..bbe88c59c751 100644 --- a/examples/auth0/pages/advanced/ssr-profile.js +++ b/examples/auth0/pages/advanced/ssr-profile.js @@ -21,7 +21,7 @@ export async function getServerSideProps({ req, res }) { // Here you can check authentication status directly before rendering the page, // however the page would be a serverless function, which is more expensive and // slower than a static page with client side authentication - const session = await auth0.getSession(req) + const session = await auth0.getSession(req, res) if (!session || !session.user) { res.writeHead(302, { diff --git a/examples/basic-css/README.md b/examples/basic-css/README.md index a7e56f8b5f0d..0f6d8517a7b7 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 99b864c87fd0..349de02f4d84 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 06f34a95483e..e53128be2496 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 f3b2ce33a212..d7485059ef3f 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 c9156b6a3be4..b74a6e5ad721 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 cf3be86f0b0f..be2edfd854e7 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 5ebee62574ca..d0dbbd103524 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 7b7aa2c7727d..4f11a03dc6cc 100644 --- a/examples/blog-starter-typescript/next-env.d.ts +++ b/examples/blog-starter-typescript/next-env.d.ts @@ -1,2 +1,5 @@ /// -/// +/// + +// 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 1a0e3a0536cb..4ddf0e92bf33 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 9b11f54f9095..1f77e63e773c 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 9fb517645aee..33ad091d26d8 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 bcfee0c665a7..beb978227b03 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/) @@ -31,15 +37,22 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu - [Storyblok](/examples/cms-storyblok) - [GraphCMS](/examples/cms-graphcms) - [Kontent](/examples/cms-kontent) +- [Umbraco Heartcore](/examples/cms-umbraco-heartcore) ## 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 +``` 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/components/meta.js b/examples/blog-starter/components/meta.js index d9c48d5831e1..349fc3d73a7a 100644 --- a/examples/blog-starter/components/meta.js +++ b/examples/blog-starter/components/meta.js @@ -7,29 +7,29 @@ export default function Meta() { - + - + - + + - -
- {'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 269563a0a240..000000000000 --- 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 771065eeed1e..000000000000 --- 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 d841b8ef6baf..000000000000 --- 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 e124758bcccc..000000000000 --- 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 6f29b261967a..000000000000 --- 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 d2a0494f156c..000000000000 --- 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 eab1cb928322..000000000000 --- 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 066d08ee254b..000000000000 --- 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 c570a0908345..000000000000 --- 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 0ceac76fabe3..000000000000 --- 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 640885f99e35..000000000000 --- 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 da290a63db2c..000000000000 --- 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 b535b3060cbd..000000000000 --- 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 0aed7ecf13d3..000000000000 --- 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 2e64d346a9a8..1f8fbca12c9c 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 1030b9380e42..07fb7fcfbdb2 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 13b69f550220..baddaee9b8a9 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 36106fb648a5..b25fa6e8435c 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 7cabc8e5ac8b..000000000000 --- 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 12cecc886186..000000000000 --- 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 9c9ead65eccc..000000000000 --- 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 21ef1bb4e9c7..000000000000 --- 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 ( - <> -