diff --git a/.alexignore b/.alexignore new file mode 100644 index 0000000000000..1bf6581c26b1e --- /dev/null +++ b/.alexignore @@ -0,0 +1,2 @@ +CODE_OF_CONDUCT.md +examples/ diff --git a/.alexrc b/.alexrc new file mode 100644 index 0000000000000..157d1da8cca53 --- /dev/null +++ b/.alexrc @@ -0,0 +1,21 @@ +{ + "allow": [ + "attacks", + "color", + "dead", + "execute", + "executed", + "executes", + "execution", + "executions", + "failed", + "failure", + "failures", + "fire", + "fires", + "hook", + "hooks", + "host-hostess", + "invalid" + ] +} diff --git a/.eslintignore b/.eslintignore index 1e7644e7ef801..02f6d89cd328d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,6 +2,26 @@ node_modules **/.next/** **/_next/** **/dist/** -examples/with-ioc/** +e2e-tests/** +examples/with-eslint/** +examples/with-typescript-eslint-jest/** examples/with-kea/** -packages/next/compiled/**/* \ No newline at end of file +examples/with-custom-babel-config/** +examples/with-flow/** +examples/with-mobx-state-tree/** +examples/with-mobx/** +packages/next/bundles/webpack/packages/*.runtime.js +packages/next/compiled/**/* +packages/react-refresh-utils/**/*.js +packages/react-dev-overlay/lib/** +**/__tmp__/** +.github/actions/next-stats-action/.work +packages/next-codemod/transforms/__testfixtures__/**/* +packages/next-codemod/transforms/__tests__/**/* +packages/next-codemod/**/*.js +packages/next-codemod/**/*.d.ts +packages/next-env/**/*.d.ts +packages/create-next-app/templates/** +test/integration/eslint/** +test-timings.json +packages/next/build/swc/tests/fixture/** diff --git a/.eslintrc.json b/.eslintrc.json index e0e871115f83c..1cee5507a5901 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,7 @@ { "root": true, - "parser": "babel-eslint", - "plugins": ["react", "react-hooks"], + "parser": "@babel/eslint-parser", + "plugins": ["react", "react-hooks", "jest", "import"], "env": { "browser": true, "commonjs": true, @@ -9,18 +9,37 @@ "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": { "react": { "version": "detect" - } + }, + "import/internal-regex": "^next/" }, "overrides": [ + { + "files": ["test/**/*.test.js"], + "extends": ["plugin:jest/recommended"], + "rules": { + "jest/expect-expect": "off", + "jest/no-disabled-tests": "off", + "jest/no-conditional-expect": "off", + "jest/valid-title": "off", + "jest/no-interpolation-in-snapshots": "off" + } + }, { "files": ["**/__tests__/**"], "env": { "jest": true } }, { "files": ["**/*.ts", "**/*.tsx"], @@ -82,6 +101,49 @@ "packages/create-next-app/templates/**/*" ], "rules": { "react/react-in-jsx-scope": "off" } + }, + { + "files": ["examples/**/*"], + "rules": { + "import/no-anonymous-default-export": [ + "error", + { + // React components: + "allowArrowFunction": false, + "allowAnonymousClass": false, + "allowAnonymousFunction": false, + + // Non-React stuff: + "allowArray": true, + "allowCallExpression": true, + "allowLiteral": true, + "allowObject": true + } + ] + } + }, + { + "files": ["packages/**"], + "rules": { + "no-shadow": ["warn", { "builtinGlobals": false }], + "import/no-extraneous-dependencies": [ + "error", + { "devDependencies": false } + ] + } + }, + { + "files": ["packages/**/*.tsx", "packages/**/*.ts"], + "rules": { + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "args": "all", + "argsIgnorePattern": "^_", + "ignoreRestSiblings": true + } + ] + } } ], "rules": { diff --git a/.github/.kodiak.toml b/.github/.kodiak.toml new file mode 100644 index 0000000000000..a90b3113f6533 --- /dev/null +++ b/.github/.kodiak.toml @@ -0,0 +1,18 @@ +# .kodiak.toml +version = 1 + +[merge] +automerge_label = "ready to land" +require_automerge_label = false +method = "squash" +delete_branch_on_merge = true +optimistic_updates = true +prioritize_ready_to_merge = true +notify_on_conflict = false + +[merge.message] +title = "pull_request_title" +body = "pull_request_body" +include_pr_number = true +body_type = "markdown" +strip_html_comments = true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index af2e4246fca4f..5b64af494dbb6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,9 +1,6 @@ # Learn how to add code owners here: # https://help.github.com/en/articles/about-code-owners -* @Timer -/packages/ @timneutkens @Timer @ijjk @lfades -/examples/ @lfades @Timer -/test/ @timneutkens @Timer @ijjk @lfades -/bench/ @timneutkens @Timer -/errors/ @Timer +* @timneutkens @ijjk @shuding @styfle @huozhi @padmaia +/docs/ @timneutkens @ijjk @shuding @styfle @huozhi @padmaia @leerob @lfades +/examples/ @timneutkens @ijjk @shuding @leerob @lfades diff --git a/.github/ISSUE_TEMPLATE/1.Bug_report.md b/.github/ISSUE_TEMPLATE/1.Bug_report.md deleted file mode 100644 index 0deaa884aae96..0000000000000 --- a/.github/ISSUE_TEMPLATE/1.Bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a bug report for the Next.js core / examples ---- - -# Bug report - -## Describe the bug - -A clear and concise description of what the bug is. - -## To Reproduce - -Steps to reproduce the behavior, please provide code snippets or a repository: - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -## Expected behavior - -A clear and concise description of what you expected to happen. - -## Screenshots - -If applicable, add screenshots to help explain your problem. - -## System information - -- OS: [e.g. macOS, Windows] -- Browser (if applies) [e.g. chrome, safari] -- Version of Next.js: [e.g. 6.0.2] -- Version of Node.js: [e.g. 10.10.0] - -## Additional context - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml new file mode 100644 index 0000000000000..440f9434bf332 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml @@ -0,0 +1,73 @@ +name: Bug Report +description: Create a bug report for the Next.js core +labels: 'template: bug' +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: markdown + attributes: + value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section. + - type: markdown + attributes: + value: 'Please first verify if your issue exists in the Next.js canary release line: `npm install next@canary`.' + - 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: 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: markdown + attributes: + value: Contributors should be able to follow the steps provided in order to reproduce the bug. + - 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.Feature_request.md b/.github/ISSUE_TEMPLATE/2.Feature_request.md deleted file mode 100644 index 92a257d1a7e80..0000000000000 --- a/.github/ISSUE_TEMPLATE/2.Feature_request.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Feature request -about: Create a feature request for the Next.js core ---- - -# Feature request - -## Is your feature request related to a problem? Please describe. - -A clear and concise description of what you want and what your use case is. - -## Describe the solution you'd like - -A clear and concise description of what you want to happen. - -## Describe alternatives you've considered - -A clear and concise description of any alternative solutions or features you've considered. - -## Additional context - -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/2.example_bug_report.yml b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml new file mode 100644 index 0000000000000..8535a441bf4a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2.example_bug_report.yml @@ -0,0 +1,73 @@ +name: Example Bug Report +description: Create a bug report for the examples +labels: 'type: example,template: bug' +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: 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: 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: markdown + attributes: + value: Contributors should be able to follow the steps provided in order to reproduce the bug. + - 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 new file mode 100644 index 0000000000000..2655aff44d149 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3.feature_request.yml @@ -0,0 +1,28 @@ +name: Feature Request +description: Create a feature request for the Next.js core +labels: 'template: story' +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: 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/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3ea766fd6d3a1..5cb26d8d1312c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Ask a question - url: https://github.com/zeit/next.js/discussions + url: https://github.com/vercel/next.js/discussions about: Ask questions and discuss with other community members diff --git a/.github/actions/next-stats-action/.gitignore b/.github/actions/next-stats-action/.gitignore new file mode 100644 index 0000000000000..d1de1508938ce --- /dev/null +++ b/.github/actions/next-stats-action/.gitignore @@ -0,0 +1,3 @@ +**/node_modules +out.md +.work \ No newline at end of file diff --git a/.github/actions/next-stats-action/Dockerfile b/.github/actions/next-stats-action/Dockerfile new file mode 100644 index 0000000000000..f533f918d9d27 --- /dev/null +++ b/.github/actions/next-stats-action/Dockerfile @@ -0,0 +1,19 @@ +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/vercel/next-stats-action" + +COPY . /next-stats + +# Install node_modules +RUN cd /next-stats && yarn install --production + +RUN git config --global user.email 'stats@localhost' +RUN git config --global user.name 'next stats' + +RUN apt update +RUN apt install apache2-utils -y + +COPY entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/next-stats-action/README.md b/.github/actions/next-stats-action/README.md new file mode 100644 index 0000000000000..566a6296a68cc --- /dev/null +++ b/.github/actions/next-stats-action/README.md @@ -0,0 +1,93 @@ +# Next.js Stats GitHub Action + +> Downloads and runs project with provided configs gathering stats to compare branches + +See it in action at Next.js https://github.com/vercel/next.js + +## Getting Started + +1. Add a `.stats-app` folder to your project with a [`stats-config.js`](#stats-config) and any files to run against for example a test app that is to be built +2. Add the action to your [workflow](https://help.github.com/en/articles/configuring-a-workflow) +3. Enjoy the stats + +## Stats Config + +```TypeScript +const StatsConfig = { + // the Heading to show at the top of stats comments + commentHeading: 'Stats from current PR' | undefined, + commentReleaseHeading: 'Stats from current release' | undefined, + // the command to build your project if not done on post install + initialBuildCommand: undefined | string, + skipInitialInstall: undefined | boolean, + // the command to build the app (app source should be in `.stats-app`) + appBuildCommand: string, + appStartCommand: string | undefined, + // the main branch to compare against (what PRs will be merging into) + mainBranch: 'canary', + // the main repository path (relative to https://github.com/) + mainRepo: 'vercel/next.js', + // whether to attempt auto merging the main branch into PR before running stats + autoMergeMain: boolean | undefined, + // an array of configs for each run + configs: [ + { // first run's config + // title of the run + title: 'fastMode stats', + // whether to diff the outputted files (default: onOutputChange) + diff: 'onOutputChange' | false | undefined, + // config files to add before running diff (if `undefined` uses `configFiles`) + diffConfigFiles: [] | undefined, + // renames to apply to make file names deterministic + renames: [ + { + srcGlob: 'main-*.js', + dest: 'main.js' + } + ], + // config files to add before running (removed before successive runs) + configFiles: [ + { + path: './next.config.js', + content: 'module.exports = { fastMode: true }' + } + ], + // an array of file groups to diff/track + filesToTrack: [ + { + name: 'Pages', + globs: [ + 'build/pages/**/*.js' + ] + } + ], + // an array of URLs to fetch while `appStartCommand` is running + // will be output to fetched-pages/${pathname}.html + pagesToFetch: [ + 'https://localhost:$PORT/page-1' + ] + }, + { // second run's config + title: 'slowMode stats', + diff: false, + configFiles: [ + { + path: './next.config.js', + content: 'module.exports = { slowMode: true }' + } + ], + filesToTrack: [ + { + name: 'Main Bundles', + globs: [ + 'build/runtime/webpack-*.js', + 'build/runtime/main-*.js', + ] + } + ] + }, + ] +} + +module.exports = StatsConfig +``` diff --git a/.github/actions/next-stats-action/entrypoint.sh b/.github/actions/next-stats-action/entrypoint.sh new file mode 100755 index 0000000000000..5f5c38de22be1 --- /dev/null +++ b/.github/actions/next-stats-action/entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -eu # stop on error + +export HOME=/root + +node /next-stats/src/index.js diff --git a/.github/actions/next-stats-action/package.json b/.github/actions/next-stats-action/package.json new file mode 100644 index 0000000000000..a9fb8c4e081e7 --- /dev/null +++ b/.github/actions/next-stats-action/package.json @@ -0,0 +1,19 @@ +{ + "name": "get-stats", + "version": "1.0.0", + "main": "src/index.js", + "license": "MIT", + "dependencies": { + "async-sema": "^3.1.0", + "fs-extra": "^8.1.0", + "get-port": "^5.0.0", + "glob": "^7.1.4", + "gzip-size": "^5.1.1", + "minimatch": "^3.0.4", + "node-fetch": "^2.6.0", + "prettier": "^1.18.2", + "pretty-bytes": "^5.3.0", + "pretty-ms": "^5.0.0", + "semver": "7.3.4" + } +} diff --git a/.github/actions/next-stats-action/src/add-comment.js b/.github/actions/next-stats-action/src/add-comment.js new file mode 100644 index 0000000000000..2f2d9afc15785 --- /dev/null +++ b/.github/actions/next-stats-action/src/add-comment.js @@ -0,0 +1,274 @@ +const path = require('path') +const fs = require('fs').promises +const fetch = require('node-fetch') +const prettyMs = require('pretty-ms') +const logger = require('./util/logger') +const prettyBytes = require('pretty-bytes') +const { benchTitle } = require('./constants') + +const gzipIgnoreRegex = new RegExp(`(General|^Serverless|${benchTitle})`) + +const prettify = (val, type = 'bytes') => { + if (typeof val !== 'number') return 'N/A' + return type === 'bytes' ? prettyBytes(val) : prettyMs(val) +} + +const round = (num, places) => { + const placesFactor = Math.pow(10, places) + return Math.round(num * placesFactor) / placesFactor +} + +const shortenLabel = (itemKey) => + itemKey.length > 24 + ? `${itemKey.substr(0, 12)}..${itemKey.substr(itemKey.length - 12, 12)}` + : itemKey + +const twoMB = 2 * 1024 * 1024 + +module.exports = async function addComment( + results = [], + actionInfo, + statsConfig +) { + let comment = `# ${ + actionInfo.isRelease + ? statsConfig.commentReleaseHeading || 'Stats from current release' + : statsConfig.commentHeading || 'Stats from current PR' + }\n\n` + + const tableHead = `| | ${statsConfig.mainRepo} ${statsConfig.mainBranch} ${ + actionInfo.lastStableTag || '' + } | ${actionInfo.prRepo} ${actionInfo.prRef} | Change |\n| - | - | - | - |\n` + + for (let i = 0; i < results.length; i++) { + const result = results[i] + const isLastResult = i === results.length - 1 + let resultHasIncrease = false + let resultHasDecrease = false + let resultContent = '' + + Object.keys(result.mainRepoStats).forEach((groupKey) => { + const isBenchmark = groupKey === benchTitle + const mainRepoGroup = result.mainRepoStats[groupKey] + const diffRepoGroup = result.diffRepoStats[groupKey] + const itemKeys = new Set([ + ...Object.keys(mainRepoGroup), + ...Object.keys(diffRepoGroup), + ]) + let groupTable = tableHead + let mainRepoTotal = 0 + let diffRepoTotal = 0 + let totalChange = 0 + + itemKeys.forEach((itemKey) => { + const prettyType = itemKey.match(/(length|duration)/i) ? 'ms' : 'bytes' + const isGzipItem = itemKey.endsWith('gzip') + const mainItemVal = mainRepoGroup[itemKey] + const diffItemVal = diffRepoGroup[itemKey] + const useRawValue = isBenchmark && prettyType !== 'ms' + const mainItemStr = useRawValue + ? mainItemVal + : prettify(mainItemVal, prettyType) + + const diffItemStr = useRawValue + ? diffItemVal + : prettify(diffItemVal, prettyType) + + let change = '✓' + + // Don't show gzip values for serverless as they aren't + // deterministic currently + if (groupKey.startsWith('Serverless') && isGzipItem) return + // otherwise only show gzip values + else if (!isGzipItem && !groupKey.match(gzipIgnoreRegex)) return + + if ( + !itemKey.startsWith('buildDuration') || + (isBenchmark && itemKey.match(/req\/sec/)) + ) { + if (typeof mainItemVal === 'number') mainRepoTotal += mainItemVal + if (typeof diffItemVal === 'number') diffRepoTotal += diffItemVal + } + + // calculate the change + if (mainItemVal !== diffItemVal) { + if ( + typeof mainItemVal === 'number' && + typeof diffItemVal === 'number' + ) { + change = round(diffItemVal - mainItemVal, 2) + + // check if there is still a change after rounding + if (change !== 0) { + const absChange = Math.abs(change) + const warnIfNegative = isBenchmark && itemKey.match(/req\/sec/) + const warn = warnIfNegative + ? change < 0 + ? '⚠️ ' + : '' + : change > 0 + ? '⚠️ ' + : '' + change = `${warn}${change < 0 ? '-' : '+'}${ + useRawValue ? absChange : prettify(absChange, prettyType) + }` + } + } else { + change = 'N/A' + } + } + + groupTable += `| ${ + isBenchmark ? itemKey : shortenLabel(itemKey) + } | ${mainItemStr} | ${diffItemStr} | ${change} |\n` + }) + let groupTotalChange = '' + + totalChange = diffRepoTotal - mainRepoTotal + + if (totalChange !== 0) { + if (totalChange < 0) { + resultHasDecrease = true + groupTotalChange = ` Overall decrease ${isBenchmark ? '⚠️' : '✓'}` + } else { + if ( + (groupKey !== 'General' && totalChange > 5) || + totalChange > twoMB + ) { + resultHasIncrease = true + } + groupTotalChange = ` Overall increase ${isBenchmark ? '✓' : '⚠️'}` + } + } + + if (groupKey !== 'General' && groupKey !== benchTitle) { + let totalChangeSign = '' + + if (totalChange === 0) { + totalChange = '✓' + } else { + totalChangeSign = totalChange < 0 ? '-' : '⚠️ +' + } + totalChange = `${totalChangeSign}${ + typeof totalChange === 'number' + ? prettify(Math.abs(totalChange)) + : totalChange + }` + groupTable += `| Overall change | ${prettyBytes( + round(mainRepoTotal, 2) + )} | ${prettyBytes(round(diffRepoTotal, 2))} | ${totalChange} |\n` + } + + if (itemKeys.size > 0) { + resultContent += `
\n` + resultContent += `${groupKey}${groupTotalChange}\n\n` + resultContent += groupTable + resultContent += `\n
\n\n` + } + }) + + // add diffs + if (result.diffs) { + const diffHeading = '#### Diffs\n' + let diffContent = diffHeading + + Object.keys(result.diffs).forEach((itemKey) => { + const curDiff = result.diffs[itemKey] + diffContent += `
\n` + diffContent += `Diff for ${shortenLabel( + itemKey + )}\n\n` + + if (curDiff.length > 36 * 1000) { + diffContent += 'Diff too large to display' + } else { + diffContent += `\`\`\`diff\n${curDiff}\n\`\`\`` + } + diffContent += `\n
\n` + }) + + if (diffContent !== diffHeading) { + resultContent += diffContent + } + } + let increaseDecreaseNote = '' + + if (resultHasIncrease) { + increaseDecreaseNote = ' (Increase detected ⚠️)' + } else if (resultHasDecrease) { + increaseDecreaseNote = ' (Decrease detected ✓)' + } + + comment += `
\n` + comment += `${result.title}${increaseDecreaseNote}\n\n
\n\n` + comment += resultContent + comment += '
\n' + + if (!isLastResult) { + comment += `
\n` + } + } + if (process.env.LOCAL_STATS) { + const statsPath = path.resolve('pr-stats.md') + await fs.writeFile(statsPath, comment) + console.log(`Output PR stats to ${statsPath}`) + } else { + logger('\n--stats start--\n', comment, '\n--stats end--\n') + } + + if ( + actionInfo.customCommentEndpoint || + (actionInfo.githubToken && actionInfo.commentEndpoint) + ) { + logger(`Posting results to ${actionInfo.commentEndpoint}`) + + const body = { + body: comment, + ...(!actionInfo.githubToken + ? { + isRelease: actionInfo.isRelease, + commitId: actionInfo.commitId, + issueId: actionInfo.issueId, + } + : {}), + } + + if (actionInfo.customCommentEndpoint) { + logger(`Using body ${JSON.stringify({ ...body, body: 'OMITTED' })}`) + } + + try { + const res = await fetch(actionInfo.commentEndpoint, { + method: 'POST', + headers: { + ...(actionInfo.githubToken + ? { + Authorization: `bearer ${actionInfo.githubToken}`, + } + : { + 'content-type': 'application/json', + }), + }, + body: JSON.stringify(body), + }) + + if (!res.ok) { + logger.error(`Failed to post results ${res.status}`) + try { + logger.error(await res.text()) + } catch (_) { + /* no-op */ + } + } else { + logger('Successfully posted results') + } + } catch (err) { + logger.error(`Error occurred posting results`, err) + } + } else { + logger( + `Not posting results`, + actionInfo.githubToken ? 'No comment endpoint' : 'no GitHub token' + ) + } +} diff --git a/.github/actions/next-stats-action/src/constants.js b/.github/actions/next-stats-action/src/constants.js new file mode 100644 index 0000000000000..ba3ec39b2ffd5 --- /dev/null +++ b/.github/actions/next-stats-action/src/constants.js @@ -0,0 +1,32 @@ +const path = require('path') + +const benchTitle = 'Page Load Tests' +const workDir = path.join(__dirname, '../.work') +const mainRepoName = 'main-repo' +const diffRepoName = 'diff-repo' +const mainRepoDir = path.join(workDir, mainRepoName) +const diffRepoDir = path.join(workDir, diffRepoName) +const statsAppDir = path.join(workDir, 'stats-app') +const diffingDir = path.join(workDir, 'diff') +const yarnEnvValues = { + YARN_CACHE_FOLDER: path.join(workDir, 'yarn-cache'), +} +const allowedConfigLocations = [ + './', + '.stats-app', + 'test/.stats-app', + '.github/.stats-app', +] + +module.exports = { + benchTitle, + workDir, + diffingDir, + mainRepoName, + diffRepoName, + mainRepoDir, + diffRepoDir, + statsAppDir, + yarnEnvValues, + allowedConfigLocations, +} diff --git a/.github/actions/next-stats-action/src/index.js b/.github/actions/next-stats-action/src/index.js new file mode 100644 index 0000000000000..64b1b1c18e05d --- /dev/null +++ b/.github/actions/next-stats-action/src/index.js @@ -0,0 +1,140 @@ +const path = require('path') +const fs = require('fs-extra') +const exec = require('./util/exec') +const logger = require('./util/logger') +const runConfigs = require('./run') +const addComment = require('./add-comment') +const actionInfo = require('./prepare/action-info')() +const { mainRepoDir, diffRepoDir } = require('./constants') +const loadStatsConfig = require('./prepare/load-stats-config') +const { + cloneRepo, + checkoutRef, + mergeBranch, + getCommitId, + linkPackages, + getLastStable, +} = require('./prepare/repo-setup')(actionInfo) + +const allowedActions = new Set(['synchronize', 'opened']) + +if (!allowedActions.has(actionInfo.actionName) && !actionInfo.isRelease) { + logger( + `Not running for ${actionInfo.actionName} event action on repo: ${actionInfo.prRepo} and ref ${actionInfo.prRef}` + ) + process.exit(0) +} + +;(async () => { + try { + if (await fs.pathExists(path.join(__dirname, '../SKIP_NEXT_STATS.txt'))) { + console.log( + 'SKIP_NEXT_STATS.txt file present, exiting stats generation..' + ) + process.exit(0) + } + + const { stdout: gitName } = await exec( + 'git config user.name && git config user.email' + ) + console.log('git author result:', gitName) + + // clone PR/newer repository/ref first to get settings + if (!actionInfo.skipClone) { + await cloneRepo(actionInfo.prRepo, diffRepoDir) + await checkoutRef(actionInfo.prRef, diffRepoDir) + } + + // load stats config from allowed locations + const { statsConfig, relativeStatsAppDir } = loadStatsConfig() + + if (actionInfo.isLocal && actionInfo.prRef === statsConfig.mainBranch) { + throw new Error( + `'GITHUB_REF' can not be the same as mainBranch in 'stats-config.js'.\n` + + `This will result in comparing against the same branch` + ) + } + + if (actionInfo.isLocal) { + // make sure to use local repo location instead of the + // one provided in statsConfig + statsConfig.mainRepo = actionInfo.prRepo + } + + // clone main repository/ref + if (!actionInfo.skipClone) { + await cloneRepo(statsConfig.mainRepo, mainRepoDir) + await checkoutRef(statsConfig.mainBranch, mainRepoDir) + } + /* eslint-disable-next-line */ + actionInfo.commitId = await getCommitId(diffRepoDir) + + if (!actionInfo.skipClone) { + if (actionInfo.isRelease) { + logger('Release detected, resetting mainRepo to last stable tag') + const lastStableTag = await getLastStable(mainRepoDir, actionInfo.prRef) + if (!lastStableTag) throw new Error('failed to get last stable tag') + console.log('using latestStable', lastStableTag) + await checkoutRef(lastStableTag, mainRepoDir) + + /* eslint-disable-next-line */ + actionInfo.lastStableTag = lastStableTag + /* eslint-disable-next-line */ + actionInfo.commitId = await getCommitId(diffRepoDir) + + if (!actionInfo.customCommentEndpoint) { + /* eslint-disable-next-line */ + actionInfo.commentEndpoint = `https://api.github.com/repos/${statsConfig.mainRepo}/commits/${actionInfo.commitId}/comments` + } + } else if (statsConfig.autoMergeMain) { + logger('Attempting auto merge of main branch') + await mergeBranch(statsConfig.mainBranch, mainRepoDir, diffRepoDir) + } + } + + let mainRepoPkgPaths + let diffRepoPkgPaths + + // run install/initialBuildCommand + const repoDirs = [mainRepoDir, diffRepoDir] + + for (const dir of repoDirs) { + logger(`Running initial build for ${dir}`) + if (!actionInfo.skipClone) { + let buildCommand = `cd ${dir}${ + !statsConfig.skipInitialInstall + ? ' && yarn install --network-timeout 1000000' + : '' + }` + + if (statsConfig.initialBuildCommand) { + buildCommand += ` && ${statsConfig.initialBuildCommand}` + } + // allow 5 minutes node_modules install + building all packages + // in case of noisy environment slowing down initial repo build + await exec(buildCommand, false, { timeout: 5 * 60 * 1000 }) + } + + logger(`Linking packages in ${dir}`) + const pkgPaths = await linkPackages(dir) + + if (dir === mainRepoDir) mainRepoPkgPaths = pkgPaths + else diffRepoPkgPaths = pkgPaths + } + + // run the configs and post the comment + const results = await runConfigs(statsConfig.configs, { + statsConfig, + mainRepoPkgPaths, + diffRepoPkgPaths, + relativeStatsAppDir, + }) + await addComment(results, actionInfo, statsConfig) + logger('finished') + process.exit(0) + } catch (err) { + console.error('Error occurred generating stats:') + console.error(err) + process.exit(1) + } +})() diff --git a/.github/actions/next-stats-action/src/prepare/action-info.js b/.github/actions/next-stats-action/src/prepare/action-info.js new file mode 100644 index 0000000000000..fff1ffc955cb3 --- /dev/null +++ b/.github/actions/next-stats-action/src/prepare/action-info.js @@ -0,0 +1,99 @@ +const path = require('path') +const logger = require('../util/logger') +const { execSync } = require('child_process') +const releaseTypes = new Set(['release', 'published']) + +module.exports = function actionInfo() { + let { + ISSUE_ID, + SKIP_CLONE, + GITHUB_REF, + LOCAL_STATS, + GIT_ROOT_DIR, + GITHUB_ACTION, + COMMENT_ENDPOINT, + GITHUB_REPOSITORY, + GITHUB_EVENT_PATH, + PR_STATS_COMMENT_TOKEN, + } = process.env + + delete process.env.GITHUB_TOKEN + delete process.env.PR_STATS_COMMENT_TOKEN + + // only use custom endpoint if we don't have a token + const commentEndpoint = !PR_STATS_COMMENT_TOKEN && COMMENT_ENDPOINT + + if (LOCAL_STATS === 'true') { + const cwd = process.cwd() + const parentDir = path.join(cwd, '../..') + + if (!GITHUB_REF) { + // get the current branch name + GITHUB_REF = execSync(`cd "${cwd}" && git rev-parse --abbrev-ref HEAD`) + .toString() + .trim() + } + if (!GIT_ROOT_DIR) { + GIT_ROOT_DIR = path.join(parentDir, '/') + } + if (!GITHUB_REPOSITORY) { + GITHUB_REPOSITORY = path.relative(parentDir, cwd) + } + if (!GITHUB_ACTION) { + GITHUB_ACTION = 'opened' + } + } + + const info = { + commentEndpoint, + skipClone: SKIP_CLONE, + actionName: GITHUB_ACTION, + githubToken: PR_STATS_COMMENT_TOKEN, + customCommentEndpoint: !!commentEndpoint, + gitRoot: GIT_ROOT_DIR || 'https://github.com/', + prRepo: GITHUB_REPOSITORY, + prRef: GITHUB_REF, + isLocal: LOCAL_STATS, + commitId: null, + issueId: ISSUE_ID, + isRelease: + GITHUB_REPOSITORY === 'vercel/next.js' && + (GITHUB_REF || '').includes('canary'), + } + + // get comment + if (GITHUB_EVENT_PATH) { + const event = require(GITHUB_EVENT_PATH) + info.actionName = event.action || info.actionName + + if (releaseTypes.has(info.actionName)) { + info.isRelease = true + } else { + // Since GITHUB_REPOSITORY and REF might not match the fork + // use event data to get repository and ref info + const prData = event['pull_request'] + + if (prData) { + info.prRepo = prData.head.repo.full_name + info.prRef = prData.head.ref + info.issueId = prData.number + + if (!info.commentEndpoint) { + info.commentEndpoint = prData._links.comments || '' + } + // comment endpoint might be under `href` + if (typeof info.commentEndpoint === 'object') { + info.commentEndpoint = info.commentEndpoint.href + } + } + } + } + + logger('Got actionInfo:') + logger.json({ + ...info, + githubToken: PR_STATS_COMMENT_TOKEN ? 'found' : 'missing', + }) + + return info +} diff --git a/.github/actions/next-stats-action/src/prepare/load-stats-config.js b/.github/actions/next-stats-action/src/prepare/load-stats-config.js new file mode 100644 index 0000000000000..7dfdfec22fe89 --- /dev/null +++ b/.github/actions/next-stats-action/src/prepare/load-stats-config.js @@ -0,0 +1,41 @@ +const path = require('path') +const logger = require('../util/logger') +const { diffRepoDir, allowedConfigLocations } = require('../constants') + +// load stats-config +function loadStatsConfig() { + let statsConfig + let relativeStatsAppDir + + for (const configPath of allowedConfigLocations) { + try { + relativeStatsAppDir = configPath + statsConfig = require(path.join( + diffRepoDir, + configPath, + 'stats-config.js' + )) + break + } catch (_) { + /* */ + } + } + + if (!statsConfig) { + throw new Error( + `Failed to locate \`.stats-app\`, allowed locations are: ${allowedConfigLocations.join( + ', ' + )}` + ) + } + + logger( + 'Got statsConfig at', + path.join(relativeStatsAppDir, 'stats-config.js'), + statsConfig, + '\n' + ) + return { statsConfig, relativeStatsAppDir } +} + +module.exports = loadStatsConfig diff --git a/.github/actions/next-stats-action/src/prepare/repo-setup.js b/.github/actions/next-stats-action/src/prepare/repo-setup.js new file mode 100644 index 0000000000000..534ab34229e53 --- /dev/null +++ b/.github/actions/next-stats-action/src/prepare/repo-setup.js @@ -0,0 +1,112 @@ +const path = require('path') +const fs = require('fs-extra') +const exec = require('../util/exec') +const { remove } = require('fs-extra') +const logger = require('../util/logger') +const semver = require('semver') + +module.exports = (actionInfo) => { + return { + async cloneRepo(repoPath = '', dest = '') { + await remove(dest) + await exec(`git clone ${actionInfo.gitRoot}${repoPath} ${dest}`) + }, + async checkoutRef(ref = '', repoDir = '') { + await exec(`cd ${repoDir} && git fetch && git checkout ${ref}`) + }, + async getLastStable(repoDir = '', ref) { + const { stdout } = await exec(`cd ${repoDir} && git tag -l`) + const tags = stdout.trim().split('\n') + let lastStableTag + + for (let i = tags.length - 1; i >= 0; i--) { + const curTag = tags[i] + // stable doesn't include `-canary` or `-beta` + if (!curTag.includes('-') && !ref.includes(curTag)) { + if (!lastStableTag || semver.gt(curTag, lastStableTag)) { + lastStableTag = curTag + } + } + } + return lastStableTag + }, + async getCommitId(repoDir = '') { + const { stdout } = await exec(`cd ${repoDir} && git rev-parse HEAD`) + return stdout.trim() + }, + async resetToRef(ref = '', repoDir = '') { + await exec(`cd ${repoDir} && git reset --hard ${ref}`) + }, + async mergeBranch(ref = '', origRepoDir = '', destRepoDir = '') { + await exec(`cd ${destRepoDir} && git remote add upstream ${origRepoDir}`) + await exec(`cd ${destRepoDir} && git fetch upstream`) + + try { + await exec(`cd ${destRepoDir} && git merge upstream/${ref}`) + logger('Auto merge of main branch successful') + } catch (err) { + logger.error('Failed to auto merge main branch:', err) + + if (err.stdout && err.stdout.includes('CONFLICT')) { + await exec(`cd ${destRepoDir} && git merge --abort`) + logger('aborted auto merge') + } + } + }, + async linkPackages(repoDir = '') { + const pkgPaths = new Map() + const pkgDatas = new Map() + let pkgs + + try { + pkgs = await fs.readdir(path.join(repoDir, 'packages')) + } catch (err) { + if (err.code === 'ENOENT') { + console.log('no packages to link') + return pkgPaths + } + throw err + } + + for (const pkg of pkgs) { + const pkgPath = path.join(repoDir, 'packages', pkg) + const packedPkgPath = path.join(pkgPath, `${pkg}-packed.tgz`) + + const pkgDataPath = path.join(pkgPath, 'package.json') + const pkgData = require(pkgDataPath) + const { name } = pkgData + pkgDatas.set(name, { + pkgDataPath, + pkg, + pkgPath, + pkgData, + packedPkgPath, + }) + pkgPaths.set(name, packedPkgPath) + } + + for (const pkg of pkgDatas.keys()) { + const { pkgDataPath, pkgData } = pkgDatas.get(pkg) + + for (const pkg of pkgDatas.keys()) { + const { packedPkgPath } = pkgDatas.get(pkg) + if (!pkgData.dependencies || !pkgData.dependencies[pkg]) continue + pkgData.dependencies[pkg] = packedPkgPath + } + await fs.writeFile( + pkgDataPath, + JSON.stringify(pkgData, null, 2), + 'utf8' + ) + } + + // wait to pack packages until after dependency paths have been updated + // 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`) + } + return pkgPaths + }, + } +} diff --git a/.github/actions/next-stats-action/src/run/benchmark-url.js b/.github/actions/next-stats-action/src/run/benchmark-url.js new file mode 100644 index 0000000000000..e92956a8c8dcf --- /dev/null +++ b/.github/actions/next-stats-action/src/run/benchmark-url.js @@ -0,0 +1,32 @@ +const exec = require('../util/exec') + +const parseField = (stdout = '', field = '') => { + return stdout.split(field).pop().trim().split(/\s/).shift().trim() +} + +// benchmark a url +async function benchmarkUrl( + url = '', + options = { + reqTimeout: 60, + concurrency: 50, + numRequests: 2500, + } +) { + const { numRequests, concurrency, reqTimeout } = options + + const { stdout } = await exec( + `ab -n ${numRequests} -c ${concurrency} -s ${reqTimeout} "${url}"` + ) + const totalTime = parseFloat(parseField(stdout, 'Time taken for tests:'), 10) + const failedRequests = parseInt(parseField(stdout, 'Failed requests:'), 10) + const avgReqPerSec = parseFloat(parseField(stdout, 'Requests per second:')) + + return { + totalTime, + avgReqPerSec, + failedRequests, + } +} + +module.exports = benchmarkUrl diff --git a/.github/actions/next-stats-action/src/run/collect-diffs.js b/.github/actions/next-stats-action/src/run/collect-diffs.js new file mode 100644 index 0000000000000..eef07739d083f --- /dev/null +++ b/.github/actions/next-stats-action/src/run/collect-diffs.js @@ -0,0 +1,112 @@ +const path = require('path') +const fs = require('fs-extra') +const exec = require('../util/exec') +const glob = require('../util/glob') +const logger = require('../util/logger') +const { statsAppDir, diffingDir } = require('../constants') + +module.exports = async function collectDiffs( + filesToTrack = [], + initial = false +) { + if (initial) { + logger('Setting up directory for diffing') + // set-up diffing directory + await fs.remove(diffingDir) + await fs.mkdirp(diffingDir) + await exec(`cd ${diffingDir} && git init`) + } else { + // remove any previous files in case they won't be overwritten + const toRemove = await glob('!(.git)', { cwd: diffingDir, dot: true }) + + await Promise.all( + toRemove.map((file) => fs.remove(path.join(diffingDir, file))) + ) + } + const diffs = {} + + await Promise.all( + filesToTrack.map(async (fileGroup) => { + const { globs } = fileGroup + const curFiles = [] + + await Promise.all( + globs.map(async (pattern) => { + curFiles.push(...(await glob(pattern, { cwd: statsAppDir }))) + }) + ) + + for (let file of curFiles) { + const absPath = path.join(statsAppDir, file) + + const diffDest = path.join(diffingDir, file) + await fs.copy(absPath, diffDest) + } + + if (curFiles.length > 0) { + await exec( + `cd "${process.env.LOCAL_STATS ? process.cwd() : diffingDir}" && ` + + `yarn prettier --write ${curFiles + .map((f) => path.join(diffingDir, f)) + .join(' ')}` + ) + } + }) + ) + + await exec(`cd ${diffingDir} && git add .`, true) + + if (initial) { + await exec(`cd ${diffingDir} && git commit -m 'initial commit'`) + } else { + let { stdout: renamedFiles } = await exec( + `cd ${diffingDir} && git diff --name-status HEAD` + ) + renamedFiles = renamedFiles + .trim() + .split('\n') + .filter((line) => line.startsWith('R')) + + diffs._renames = [] + + for (const line of renamedFiles) { + const [, prev, cur] = line.split('\t') + await fs.move(path.join(diffingDir, cur), path.join(diffingDir, prev)) + diffs._renames.push({ + prev, + cur, + }) + } + + await exec(`cd ${diffingDir} && git add .`) + + let { stdout: changedFiles } = await exec( + `cd ${diffingDir} && git diff --name-only HEAD` + ) + changedFiles = changedFiles.trim().split('\n') + + for (const file of changedFiles) { + const fileKey = path.basename(file) + const hasFile = await fs.exists(path.join(diffingDir, file)) + + if (!hasFile) { + diffs[fileKey] = 'deleted' + continue + } + + try { + let { stdout } = await exec( + `cd ${diffingDir} && git diff --minimal HEAD ${file}` + ) + stdout = (stdout.split(file).pop() || '').trim() + if (stdout.length > 0) { + diffs[fileKey] = stdout + } + } catch (err) { + console.error(`Failed to diff ${file}: ${err.message}`) + diffs[fileKey] = `failed to diff` + } + } + } + return diffs +} diff --git a/.github/actions/next-stats-action/src/run/collect-stats.js b/.github/actions/next-stats-action/src/run/collect-stats.js new file mode 100644 index 0000000000000..d01d1a5e4fd23 --- /dev/null +++ b/.github/actions/next-stats-action/src/run/collect-stats.js @@ -0,0 +1,149 @@ +const path = require('path') +const fs = require('fs-extra') +const getPort = require('get-port') +const fetch = require('node-fetch') +const glob = require('../util/glob') +const gzipSize = require('gzip-size') +const logger = require('../util/logger') +const { spawn } = require('../util/exec') +const { parse: urlParse } = require('url') +const benchmarkUrl = require('./benchmark-url') +const { statsAppDir, diffingDir, benchTitle } = require('../constants') + +module.exports = async function collectStats( + runConfig = {}, + statsConfig = {}, + fromDiff = false +) { + const stats = { + [benchTitle]: {}, + } + const orderedStats = { + [benchTitle]: {}, + } + const curDir = fromDiff ? diffingDir : statsAppDir + + const hasPagesToFetch = + Array.isArray(runConfig.pagesToFetch) && runConfig.pagesToFetch.length > 0 + + const hasPagesToBench = + Array.isArray(runConfig.pagesToBench) && runConfig.pagesToBench.length > 0 + + if ( + !fromDiff && + statsConfig.appStartCommand && + (hasPagesToFetch || hasPagesToBench) + ) { + const port = await getPort() + const child = spawn(statsConfig.appStartCommand, { + cwd: curDir, + env: { + PORT: port, + }, + stdio: 'pipe', + }) + let exitCode = null + let logStderr = true + child.stdout.on('data', (data) => process.stdout.write(data)) + child.stderr.on('data', (data) => logStderr && process.stderr.write(data)) + + child.on('exit', (code) => { + exitCode = code + }) + // give app a second to start up + await new Promise((resolve) => setTimeout(() => resolve(), 1500)) + + if (exitCode !== null) { + throw new Error( + `Failed to run \`${statsConfig.appStartCommand}\` process exited with code ${exitCode}` + ) + } + + if (hasPagesToFetch) { + const fetchedPagesDir = path.join(curDir, 'fetched-pages') + await fs.mkdirp(fetchedPagesDir) + + for (let url of runConfig.pagesToFetch) { + url = url.replace('$PORT', port) + const { pathname } = urlParse(url) + try { + const res = await fetch(url) + if (!res.ok) { + throw new Error(`Failed to fetch ${url} got status: ${res.status}`) + } + const responseText = (await res.text()).trim() + + let fileName = pathname === '/' ? '/index' : pathname + if (fileName.endsWith('/')) + fileName = fileName.substr(0, fileName.length - 1) + logger( + `Writing file to ${path.join(fetchedPagesDir, `${fileName}.html`)}` + ) + + await fs.writeFile( + path.join(fetchedPagesDir, `${fileName}.html`), + responseText, + 'utf8' + ) + } catch (err) { + logger.error(err) + } + } + } + + if (hasPagesToBench) { + // disable stderr so we don't clobber logs while benchmarking + // any pages that create logs + logStderr = false + + for (let url of runConfig.pagesToBench) { + url = url.replace('$PORT', port) + logger(`Benchmarking ${url}`) + + const results = await benchmarkUrl(url, runConfig.benchOptions) + logger(`Finished benchmarking ${url}`) + + const { pathname: key } = urlParse(url) + stats[benchTitle][`${key} failed reqs`] = results.failedRequests + stats[benchTitle][`${key} total time (seconds)`] = results.totalTime + + stats[benchTitle][`${key} avg req/sec`] = results.avgReqPerSec + } + } + child.kill() + } + + for (const fileGroup of runConfig.filesToTrack) { + const { name, globs } = fileGroup + const groupStats = {} + const curFiles = new Set() + + for (const pattern of globs) { + const results = await glob(pattern, { cwd: curDir, nodir: true }) + results.forEach((result) => curFiles.add(result)) + } + + for (const file of curFiles) { + const fileKey = path.basename(file) + const absPath = path.join(curDir, file) + try { + const fileInfo = await fs.stat(absPath) + groupStats[fileKey] = fileInfo.size + groupStats[`${fileKey} gzip`] = await gzipSize.file(absPath) + } catch (err) { + logger.error('Failed to get file stats', err) + } + } + stats[name] = groupStats + } + + for (const fileGroup of runConfig.filesToTrack) { + const { name } = fileGroup + orderedStats[name] = stats[name] + } + + if (stats[benchTitle]) { + orderedStats[benchTitle] = stats[benchTitle] + } + return orderedStats +} diff --git a/.github/actions/next-stats-action/src/run/get-dir-size.js b/.github/actions/next-stats-action/src/run/get-dir-size.js new file mode 100644 index 0000000000000..291f09fe72900 --- /dev/null +++ b/.github/actions/next-stats-action/src/run/get-dir-size.js @@ -0,0 +1,21 @@ +const path = require('path') +const fs = require('fs-extra') + +// getDirSize recursively gets size of all files in a directory +async function getDirSize(dir, ctx = { size: 0 }) { + let subDirs = await fs.readdir(dir) + subDirs = subDirs.map((d) => path.join(dir, d)) + + await Promise.all( + subDirs.map(async (curDir) => { + const fileStat = await fs.stat(curDir) + if (fileStat.isDirectory()) { + return getDirSize(curDir, ctx) + } + ctx.size += fileStat.size + }) + ) + return ctx.size +} + +module.exports = getDirSize diff --git a/.github/actions/next-stats-action/src/run/index.js b/.github/actions/next-stats-action/src/run/index.js new file mode 100644 index 0000000000000..e2159e9a89bf2 --- /dev/null +++ b/.github/actions/next-stats-action/src/run/index.js @@ -0,0 +1,199 @@ +const path = require('path') +const fs = require('fs-extra') +const glob = require('../util/glob') +const exec = require('../util/exec') +const logger = require('../util/logger') +const getDirSize = require('./get-dir-size') +const collectStats = require('./collect-stats') +const collectDiffs = require('./collect-diffs') +const { statsAppDir, diffRepoDir, yarnEnvValues } = require('../constants') + +async function runConfigs( + configs = [], + { statsConfig, relativeStatsAppDir, mainRepoPkgPaths, diffRepoPkgPaths }, + diffing = false +) { + const results = [] + + for (const config of configs) { + logger(`Running config: ${config.title}${diffing ? ' (diff)' : ''}`) + + let mainRepoStats + let diffRepoStats + let diffs + + for (const pkgPaths of [mainRepoPkgPaths, diffRepoPkgPaths]) { + let curStats = { + General: { + buildDuration: null, + buildDurationCached: null, + nodeModulesSize: null, + }, + } + + // if stats-config is in root of project we're analyzing + // the whole project so copy from each repo + const curStatsAppPath = path.join(diffRepoDir, relativeStatsAppDir) + + // clean statsAppDir + await fs.remove(statsAppDir) + await fs.copy(curStatsAppPath, statsAppDir) + + logger(`Copying ${curStatsAppPath} ${statsAppDir}`) + + // apply config files + for (const configFile of config.configFiles || []) { + const filePath = path.join(statsAppDir, configFile.path) + await fs.writeFile(filePath, configFile.content, 'utf8') + } + + // links local builds of the packages and installs dependencies + await linkPkgs(statsAppDir, pkgPaths) + + if (!diffing) { + curStats.General.nodeModulesSize = await getDirSize( + path.join(statsAppDir, 'node_modules') + ) + } + + const buildStart = Date.now() + await exec(`cd ${statsAppDir} && ${statsConfig.appBuildCommand}`, false, { + env: yarnEnvValues, + }) + 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 }) + for (const result of results) { + let dest = rename.removeHash + ? result.replace(/(\.|-)[0-9a-f]{20}(\.|-)/g, '$1HASH$2') + : rename.dest + if (result === dest) continue + await fs.move( + path.join(statsAppDir, result), + path.join(statsAppDir, dest) + ) + } + } + + const collectedStats = await collectStats(config, statsConfig) + curStats = { + ...curStats, + ...collectedStats, + } + + 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) => { + 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'] + } + }) + } + } + } + + if (mainRepoStats) { + diffRepoStats = curStats + + if (!diffing && config.diff !== false) { + for (const groupKey of Object.keys(curStats)) { + if (groupKey === 'General') continue + let changeDetected = config.diff === 'always' + + const curDiffs = await collectDiffs(config.filesToTrack) + changeDetected = changeDetected || Object.keys(curDiffs).length > 0 + + applyRenames(curDiffs._renames, diffRepoStats) + delete curDiffs._renames + + if (changeDetected) { + logger('Detected change, running diff') + diffs = await runConfigs( + [ + { + ...config, + configFiles: config.diffConfigFiles, + }, + ], + { + statsConfig, + mainRepoPkgPaths, + diffRepoPkgPaths, + relativeStatsAppDir, + }, + true + ) + delete diffs._renames + break + } + } + } + + if (diffing) { + // copy new files and get diff results + return collectDiffs(config.filesToTrack) + } + } else { + // set up diffing folder and copy initial files + await collectDiffs(config.filesToTrack, true) + + /* 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}`) + + results.push({ + title: config.title, + mainRepoStats, + diffRepoStats, + diffs, + }) + } + + return results +} + +async function linkPkgs(pkgDir = '', pkgPaths) { + await fs.remove(path.join(pkgDir, 'node_modules')) + + const pkgJsonPath = path.join(pkgDir, 'package.json') + const pkgData = require(pkgJsonPath) + + if (!pkgData.dependencies && !pkgData.devDependencies) return + + for (const pkg of pkgPaths.keys()) { + const pkgPath = pkgPaths.get(pkg) + + if (pkgData.dependencies && pkgData.dependencies[pkg]) { + pkgData.dependencies[pkg] = pkgPath + } else if (pkgData.devDependencies && pkgData.devDependencies[pkg]) { + pkgData.devDependencies[pkg] = pkgPath + } + } + await fs.writeFile(pkgJsonPath, JSON.stringify(pkgData, null, 2), 'utf8') + + await fs.remove(yarnEnvValues.YARN_CACHE_FOLDER) + await exec(`cd ${pkgDir} && yarn install`, false, { + env: yarnEnvValues, + }) +} + +module.exports = runConfigs diff --git a/.github/actions/next-stats-action/src/util/exec.js b/.github/actions/next-stats-action/src/util/exec.js new file mode 100644 index 0000000000000..689bd5b2952aa --- /dev/null +++ b/.github/actions/next-stats-action/src/util/exec.js @@ -0,0 +1,38 @@ +const logger = require('./logger') +const { promisify } = require('util') +const { exec: execOrig, spawn: spawnOrig } = require('child_process') + +const execP = promisify(execOrig) +const env = { + ...process.env, + GITHUB_TOKEN: '', + PR_STATS_COMMENT_TOKEN: '', +} + +function exec(command, noLog = false, opts = {}) { + if (!noLog) logger(`exec: ${command}`) + return execP(command, { + timeout: 180 * 1000, + ...opts, + env: { ...env, ...opts.env }, + }) +} + +exec.spawn = function spawn(command = '', opts = {}) { + logger(`spawn: ${command}`) + const child = spawnOrig('/bin/bash', ['-c', command], { + ...opts, + env: { + ...env, + ...opts.env, + }, + stdio: opts.stdio || 'inherit', + }) + + child.on('exit', (code, signal) => { + logger(`spawn exit (${code}, ${signal}): ${command}`) + }) + return child +} + +module.exports = exec diff --git a/.github/actions/next-stats-action/src/util/glob.js b/.github/actions/next-stats-action/src/util/glob.js new file mode 100644 index 0000000000000..297e429897bc0 --- /dev/null +++ b/.github/actions/next-stats-action/src/util/glob.js @@ -0,0 +1,3 @@ +const globOrig = require('glob') +const { promisify } = require('util') +module.exports = promisify(globOrig) diff --git a/.github/actions/next-stats-action/src/util/logger.js b/.github/actions/next-stats-action/src/util/logger.js new file mode 100644 index 0000000000000..695d1ec68d428 --- /dev/null +++ b/.github/actions/next-stats-action/src/util/logger.js @@ -0,0 +1,17 @@ +function logger(...args) { + console.log(...args) +} + +logger.json = (obj) => { + logger('\n', JSON.stringify(obj, null, 2), '\n') +} + +logger.error = (...args) => { + console.error(...args) +} + +logger.warn = (...args) => { + console.warn(...args) +} + +module.exports = logger diff --git a/.github/labeler.json b/.github/labeler.json new file mode 100644 index 0000000000000..1499d280fb296 --- /dev/null +++ b/.github/labeler.json @@ -0,0 +1,32 @@ +{ + "labels": { + "type: example": ["examples/**"], + "type: documentation": ["docs/**", "errors/**"], + "type: create-next-app": ["packages/create-next-app/**"], + "type: next": [ + "packages/next/**", + "packages/react-dev-overlay/**", + "packages/react-refresh-utils/**", + "packages/next-codemod/**" + ], + "created-by: Chrome Aurora": [ + { "type": "user", "pattern": "spanicker" }, + { "type": "user", "pattern": "housseindjirdeh" }, + { "type": "user", "pattern": "devknoll" }, + { "type": "user", "pattern": "janicklas-ralph" }, + { "type": "user", "pattern": "atcastle" }, + { "type": "user", "pattern": "kyliau" }, + { "type": "user", "pattern": "kara" } + ], + "created-by: Next.js team": [ + { "type": "user", "pattern": "ijjk" }, + { "type": "user", "pattern": "padmaia" }, + { "type": "user", "pattern": "huozhi" }, + { "type": "user", "pattern": "shuding" }, + { "type": "user", "pattern": "sokra" }, + { "type": "user", "pattern": "styfle" }, + { "type": "user", "pattern": "leerob" }, + { "type": "user", "pattern": "timneutkens" } + ] + } +} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000000..9e9b995c92dd8 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,24 @@ + + +## Bug + +- [ ] Related issues linked using `fixes #number` +- [ ] Integration tests added +- [ ] Errors have helpful link attached, see `contributing.md` + +## Feature + +- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. +- [ ] Related issues linked using `fixes #number` +- [ ] Integration tests added +- [ ] Documentation added +- [ ] Telemetry added. In case of a feature if it's used or not. +- [ ] Errors have helpful link attached, see `contributing.md` + +## Documentation / Examples + +- [ ] Make sure the linting passes diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml new file mode 100644 index 0000000000000..e542dd2a2645e --- /dev/null +++ b/.github/workflows/build_native.yml @@ -0,0 +1,154 @@ +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize] + paths: + - 'packages/next/build/swc/**' + +name: Build next-swc native binaries + +jobs: + build: + strategy: + matrix: + os: [ubuntu-18.04, macos-latest, windows-latest] + + name: stable - ${{ matrix.os }} - node@14 + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + check-latest: true + + - name: Install + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + + - 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 NPM dependencies + uses: actions/cache@v1 + with: + path: node_modules + key: npm-cache-${{ matrix.os }}-node@14-${{ hashFiles('yarn.lock') }} + + - name: 'Install dependencies' + run: yarn install --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + + - name: 'Build' + run: yarn --cwd packages/next build-native + env: + MACOSX_DEPLOYMENT_TARGET: '10.13' + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache + + build-apple-silicon: + name: stable - aarch64-apple-darwin - node@14 + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: 14 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: true + toolchain: nightly-2021-03-25 + target: aarch64-apple-darwin + + - name: Install dependencies + run: yarn install --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000 + + - name: Cross build aarch64 + run: yarn --cwd packages/next build-native --target aarch64-apple-darwin + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache + + commit: + needs: [build, build-apple-silicon] + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + if: ${{ github.event_name == 'workflow_dispatch' }} + - uses: actions/download-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native + if: ${{ github.event_name == 'workflow_dispatch' }} + - uses: EndBug/add-and-commit@v7 + with: + add: 'packages/next/native --force' + message: 'Build next-swc binaries' + if: ${{ github.event_name == 'workflow_dispatch' }} + + check: + needs: [build, build-apple-silicon] + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + if: ${{ github.event_name == 'pull_request' }} + - uses: actions/download-artifact@v2 + with: + name: next-swc-binaries + path: packages/next/native + if: ${{ github.event_name == 'pull_request' }} + - run: git diff --exit-code + if: ${{ github.event_name == 'pull_request' }} + + test: + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + if: ${{ github.event_name == 'pull_request' }} + - name: Install + if: ${{ github.event_name == 'pull_request' }} + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly-2021-03-25 + profile: minimal + - run: cd packages/next/build/swc && cargo test + if: ${{ github.event_name == 'pull_request' }} diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 08b00fe3607ce..0ee8034b1b484 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -11,25 +11,36 @@ jobs: runs-on: ubuntu-latest env: NEXT_TELEMETRY_DISABLED: 1 + outputs: + docsChange: ${{ steps.docs-change.outputs.DOCS_CHANGE }} steps: - uses: actions/checkout@v2 + with: + fetch-depth: 25 + - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - run: yarn install --frozen-lockfile --check-files - - uses: actions/cache@v1 + - run: node run-tests.js --timings --write-timings -g 1/1 + - name: Check docs only change + 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}} + - uses: actions/cache@v2 id: cache-build with: - path: '.' + path: ./* key: ${{ github.sha }} lint: runs-on: ubuntu-latest needs: build steps: - - uses: actions/cache@v1 + - uses: actions/cache@v2 id: restore-build with: - path: '.' + path: ./* key: ${{ github.sha }} + - run: ./scripts/check-manifests.js - run: yarn lint checkPrecompiled: @@ -39,15 +50,36 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 steps: - - uses: actions/cache@v1 + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + - run: ./scripts/check-pre-compiled.sh + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + testUnit: + name: Test Unit + runs-on: ubuntu-latest + needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + HEADLESS: true + steps: + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: - path: '.' + path: ./* key: ${{ github.sha }} - - run: ./check-pre-compiled.sh - testAll: - name: Test All + - run: node run-tests.js --timings --type unit -g 1/1 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + testIntegration: + name: Test Integration runs-on: ubuntu-latest needs: build env: @@ -59,38 +91,110 @@ jobs: matrix: group: [1, 2, 3, 4, 5, 6] steps: - - uses: actions/cache@v1 + - run: echo ${{needs.build.outputs.docsChange}} + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: - path: '.' + path: ./* key: ${{ github.sha }} # TODO: remove after we fix watchpack watching too much - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + if: ${{needs.build.outputs.docsChange != 'docs only change'}} - - run: node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3 + - run: xvfb-run node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + testElectron: + name: Test Electron + runs-on: ubuntu-latest + needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + HEADLESS: true + TEST_ELECTRON: 1 + steps: + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + + # TODO: remove after we fix watchpack watching too much + - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - run: cd test/integration/with-electron/app && yarn + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + - run: xvfb-run node run-tests.js test/integration/with-electron/test/index.test.js + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + + testYarnPnP: + runs-on: ubuntu-latest + needs: build + env: + NODE_OPTIONS: '--unhandled-rejections=strict' + YARN_COMPRESSION_LEVEL: '0' + steps: + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + + - run: bash ./scripts/test-pnp.sh + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testsPass: name: thank you, next runs-on: ubuntu-latest - needs: [lint, checkPrecompiled, testAll] + needs: [lint, checkPrecompiled, testIntegration, testUnit, testYarnPnP] steps: - run: exit 0 + testLegacyWebpack: + name: Webpack 4 (Basic, Production, Acceptance) + runs-on: ubuntu-latest + needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + HEADLESS: true + NEXT_PRIVATE_TEST_WEBPACK4_MODE: 1 + + steps: + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + + - run: xvfb-run node run-tests.js test/integration/{basic,fallback-modules,link-ref,production,async-modules,font-optimization,ssr-ctx}/test/index.test.js test/acceptance/*.test.js + if: ${{needs.build.outputs.docsChange != 'docs only change'}} + testFirefox: name: Test Firefox (production) runs-on: ubuntu-latest needs: build env: - NEXT_TELEMETRY_DISABLED: 1 HEADLESS: true + BROWSER_NAME: 'firefox' + NEXT_TELEMETRY_DISABLED: 1 steps: - - uses: actions/cache@v1 + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: - path: '.' + path: ./* key: ${{ github.sha }} - - run: yarn testfirefox --forceExit test/integration/production/ + - run: node run-tests.js -c 1 test/integration/production/test/index.test.js + if: ${{needs.build.outputs.docsChange != 'docs only change'}} testSafari: name: Test Safari (production) @@ -98,17 +202,20 @@ jobs: needs: build env: BROWSERSTACK: true + 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: - - uses: actions/cache@v1 + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: - path: '.' + path: ./* key: ${{ github.sha }} - - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || yarn testsafari --forceExit test/integration/production/' + - 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) @@ -117,29 +224,47 @@ jobs: env: BROWSERSTACK: true LEGACY_SAFARI: true + 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: - - uses: actions/cache@v1 + - uses: actions/cache@v2 + if: ${{needs.build.outputs.docsChange != 'docs only change'}} id: restore-build with: - path: '.' + path: ./* key: ${{ github.sha }} - - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || yarn testsafari --forceExit test/integration/production-nav/' + - run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js -c 1 test/integration/production-nav/test/index.test.js' + if: ${{needs.build.outputs.docsChange != 'docs only change'}} publishRelease: name: Potentially publish release runs-on: ubuntu-latest - needs: [testsPass] + needs: build env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} steps: - - uses: actions/cache@v1 + - uses: actions/cache@v2 id: restore-build with: - path: '.' + path: ./* key: ${{ github.sha }} - - run: ./publish-release.sh + - run: ./scripts/publish-release.sh + + releaseStats: + name: Release Stats + runs-on: ubuntu-latest + needs: [publishRelease] + steps: + - uses: actions/cache@v2 + id: restore-build + with: + path: ./* + key: ${{ github.sha }} + - run: ./scripts/release-stats.sh + - uses: ./.github/actions/next-stats-action + env: + PR_STATS_COMMENT_TOKEN: ${{ secrets.PR_STATS_COMMENT_TOKEN }} diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml new file mode 100644 index 0000000000000..ce58d11cab7c9 --- /dev/null +++ b/.github/workflows/cancel.yml @@ -0,0 +1,17 @@ +name: Cancel +on: + pull_request_target: + types: + - edited + - synchronize + +jobs: + cancel: + name: 'Cancel Previous Runs' + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - uses: styfle/cancel-workflow-action@0.5.0 + 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 d348c31fd9bc7..4c4874cdb9f48 100644 --- a/.github/workflows/pull_request_stats.yml +++ b/.github/workflows/pull_request_stats.yml @@ -9,4 +9,11 @@ jobs: name: PR Stats runs-on: ubuntu-latest steps: - - uses: zeit/next-stats-action@master + - 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 + - uses: ./.github/actions/next-stats-action + if: ${{ steps.docs-change.outputs.DOCS_CHANGE != 'docs only change' }} diff --git a/.github/workflows/release_stats.yml b/.github/workflows/release_stats.yml deleted file mode 100644 index 20cc819388033..0000000000000 --- a/.github/workflows/release_stats.yml +++ /dev/null @@ -1,12 +0,0 @@ -on: release - -name: Generate Release Stats - -jobs: - prStats: - name: Release Stats - runs-on: ubuntu-latest - steps: - - uses: zeit/next-stats-action@master - env: - PR_STATS_COMMENT_TOKEN: ${{ secrets.PR_STATS_COMMENT_TOKEN }} diff --git a/.github/workflows/test_macos.yml b/.github/workflows/test_macos.yml new file mode 100644 index 0000000000000..573425c04aaea --- /dev/null +++ b/.github/workflows/test_macos.yml @@ -0,0 +1,27 @@ +on: + push: + branches: [canary] + paths-ignore: + - 'bench/**' + - 'docs/**' + - 'errors/**' + - 'examples/**' + +name: Test macOS + +jobs: + testMacOS: + name: macOS (Basic, Production, Acceptance) + runs-on: macos-latest + env: + NEXT_TELEMETRY_DISABLED: 1 + NEXT_TEST_JOB: 1 + HEADLESS: true + + steps: + - uses: actions/checkout@v2 + - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - run: yarn install --frozen-lockfile --check-files || yarn install --frozen-lockfile --check-files + - run: node run-tests.js test/integration/production/test/index.test.js + - run: node run-tests.js test/integration/basic/test/index.test.js + - run: node run-tests.js test/acceptance/* diff --git a/.github/workflows/test_react_experimental.yml b/.github/workflows/test_react_experimental.yml new file mode 100644 index 0000000000000..bbfb890544d67 --- /dev/null +++ b/.github/workflows/test_react_experimental.yml @@ -0,0 +1,55 @@ +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@next react-dom@next -W --dev + + # - uses: actions/cache@v2 + # id: cache-build + # with: + # path: ./* + # key: ${{ github.sha }} + + testAll: + name: Test All + runs-on: ubuntu-latest + # needs: build + env: + NEXT_TELEMETRY_DISABLED: 1 + HEADLESS: true + NEXT_PRIVATE_SKIP_SIZE_TESTS: true + NEXT_PRIVATE_REACT_ROOT: 1 + 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@experimental react-dom@experimental -W --dev + + # TODO: remove after we fix watchpack watching too much + - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p + + - run: node run-tests.js --timings -g ${{ matrix.group }}/6 -c 3 diff --git a/.github/workflows/test_react_next.yml b/.github/workflows/test_react_next.yml index 443b0e25af518..4845ca86c9a5a 100644 --- a/.github/workflows/test_react_next.yml +++ b/.github/workflows/test_react_next.yml @@ -17,10 +17,10 @@ jobs: # - run: yarn upgrade react@next react-dom@next -W --dev - # - uses: actions/cache@v1 + # - uses: actions/cache@v2 # id: cache-build # with: - # path: '.' + # path: ./* # key: ${{ github.sha }} testAll: @@ -30,15 +30,17 @@ jobs: env: NEXT_TELEMETRY_DISABLED: 1 HEADLESS: true + NEXT_PRIVATE_SKIP_SIZE_TESTS: true + NEXT_PRIVATE_REACT_ROOT: 1 strategy: fail-fast: false matrix: group: [1, 2, 3, 4, 5, 6] steps: - # - uses: actions/cache@v1 + # - uses: actions/cache@v2 # id: restore-build # with: - # path: '.' + # path: ./* # key: ${{ github.sha }} - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index 774d9ab5fa0a6..011b7fffcbbde 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # build output dist .next +target # dependencies node_modules @@ -21,10 +22,19 @@ coverage test/**/out* test/**/next-env.d.ts .DS_Store +/e2e-tests # Editors **/.idea +**/.#* -# example output +# examples examples/**/out +examples/**/.env*.local +pr-stats.md +test-timings.json + +# Vercel +.vercel +.now diff --git a/.prettierignore b/.prettierignore index 47d0e812cbdb7..101632425ad1d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,18 @@ node_modules **/.next/** **/_next/** **/dist/** -packages/next/compiled/** \ No newline at end of file +packages/next/bundles/webpack/packages/*.runtime.js +packages/next/compiled/** +packages/react-refresh-utils/**/*.js +packages/react-refresh-utils/**/*.d.ts +packages/react-dev-overlay/lib/** +**/__tmp__/** +lerna.json +.github/actions/next-stats-action/.work +packages/next-codemod/transforms/__testfixtures__/**/* +packages/next-codemod/transforms/__tests__/**/* +packages/next-codemod/**/*.js +packages/next-codemod/**/*.d.ts +packages/next-env/**/*.d.ts +test-timings.json +test/**/out/** diff --git a/.prettierignore_staged b/.prettierignore_staged index 5475415d75706..e888b26919139 100644 --- a/.prettierignore_staged +++ b/.prettierignore_staged @@ -2,3 +2,7 @@ **/_next/** **/dist/** packages/next/compiled/**/* +packages/next/bundles/webpack/packages/*.runtime.js +lerna.json +packages/next-codemod/transforms/__testfixtures__/**/* +packages/next-codemod/transforms/__tests__/**/* diff --git a/.prettierrc.json b/.prettierrc.json index 0b4951088a670..fd496a820ea94 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,5 +1,4 @@ { "singleQuote": true, - "semi": false, - "trailingComma": "es5" + "semi": false } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000000..088ae3cf90a1a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,69 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch app development", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "yarn", + "runtimeArgs": ["run", "debug", "dev", "test/integration/basic"], + "skipFiles": ["/**"], + "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"], + "port": 9229 + }, + { + "name": "Launch app build", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "yarn", + "runtimeArgs": ["run", "debug", "build", "test/integration/basic"], + "skipFiles": ["/**"], + "port": 9229, + "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"] + }, + { + "name": "Launch app build trace", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "yarn", + "runtimeArgs": ["run", "trace-debug", "build", "test/integration/basic"], + "skipFiles": ["/**"], + "port": 9229, + "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"] + }, + { + "name": "Launch app production", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "yarn", + "runtimeArgs": ["run", "debug", "start", "test/integration/basic"], + "skipFiles": ["/**"], + "port": 9229 + }, + { + "type": "node", + "request": "attach", + "name": "Attach to existing debugger", + "port": 9229, + "skipFiles": ["/**"], + "outFiles": ["${workspaceFolder}/packages/next/dist/**/*"] + }, + { + "name": "Launch this example", + "type": "node", + "request": "launch", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "yarn", + "runtimeArgs": ["run", "debug", "dev", "${fileDirname}"], + "skipFiles": ["/**"], + "port": 9229 + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index e521849f2cdba..9b9d414d6ec53 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ "javascriptreact", { "language": "typescript", "autoFix": true }, { "language": "typescriptreact", "autoFix": true } - ] + ], + "debug.javascript.unmapMissingSources": true } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index f264ef949da55..5b8d1a5be1b2b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,7 @@ further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at [abuse@zeit.co](mailto:abuse@zeit.co). All +reported by contacting the project team at [coc@vercel.com](mailto:coc@vercel.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000..294bff136eef4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1 @@ +Visit https://vercel.com/security to view the disclosure policy. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 40e8b3f12e1e4..37fd3bc6bd482 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,66 +1,149 @@ +trigger: + # Only run latest commit for branches: + batch: true + # Do not run Azure CI for docs-only/example-only changes: + paths: + include: + - '*' + exclude: + - bench + - docs + - errors + - examples + # Do not run Azure on `canary`, `master`, or release tags. This unnecessarily + # increases the backlog, and the change was already tested on the PR. + branches: + include: + - '*' + exclude: + - canary + - master + - refs/tags/* + +pr: + # Do not run Azure CI for docs-only/example-only changes: + paths: + include: + - '*' + exclude: + - bench + - docs + - errors + - examples + variables: YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn NEXT_TELEMETRY_DISABLED: '1' - node_version: ^10.10.0 - -jobs: - - job: test_ie11 - pool: - vmImage: 'windows-2019' - steps: - - task: NodeTool@0 - inputs: - versionSpec: $(node_version) - displayName: 'Install Node.js' - - - task: CacheBeta@0 - inputs: - key: yarn | $(Agent.OS) | yarn.lock - path: $(YARN_CACHE_FOLDER) - displayName: Cache Yarn packages - - - script: | - yarn install --frozen-lockfile --check-files - displayName: 'Install dependencies' - - - script: | - yarn testie --forceExit test/integration/production/ - displayName: 'Run tests' - - - job: test_chrome - pool: - vmImage: 'windows-2019' - strategy: - maxParallel: 10 - 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: - - 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' + node_version: ^12.0.0 - - task: CacheBeta@0 - inputs: - key: yarn | $(Agent.OS) | yarn.lock - path: $(YARN_CACHE_FOLDER) - displayName: Cache Yarn packages +stages: + - stage: Build + jobs: + - job: build + pool: + vmImage: 'windows-2019' + steps: + - script: echo $(Agent.BuildDirectory) + - script: dir + - script: dir $(System.DefaultWorkingDirectory) + - script: echo $(Build.SourceVersion) + - powershell: Get-MpComputerStatus + - 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: | + yarn install --frozen-lockfile --check-files + displayName: 'Install dependencies' + - script: | + node run-tests.js --timings --write-timings --azure -g 1/1 + displayName: 'Fetch test timing data' - - script: | - yarn install --frozen-lockfile --check-files - displayName: 'Install dependencies' + - stage: Test + dependsOn: Build + jobs: + - job: test_ie11 + pool: + vmImage: 'windows-2019' + variables: + BROWSER_NAME: internet explorer + steps: + - checkout: none + - 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 -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' - - script: | - node run-tests.js -g $(group) --timings - 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 + - script: | + node run-tests.js -g 1/1 --timings --azure --type unit + displayName: 'Run tests' + # TODO: investigate re-enabling when stability matches running in + # tests in ubuntu environment + # - job: test_chrome_integration + # pool: + # vmImage: 'windows-2019' + # strategy: + # matrix: + # nodejs-1: + # group: 1/4 + # nodejs-2: + # group: 2/4 + # nodejs-3: + # group: 3/4 + # nodejs-4: + # group: 4/4 + # steps: + # - checkout: none + # - script: | + # wmic datafile where name="C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" get Version /value + # displayName: 'List Chrome version' + # - task: NodeTool@0 + # inputs: + # versionSpec: $(node_version) + # displayName: 'Install Node.js' + # - task: Cache@2 + # inputs: + # # use deterministic cache key that is specific + # # to this test run + # key: $(Build.SourceVersion) + # path: $(System.DefaultWorkingDirectory) + # displayName: Cache Build + # - script: | + # node run-tests.js -g $(group) --timings --azure + # displayName: 'Run tests' diff --git a/bench/capture-trace.js b/bench/capture-trace.js new file mode 100644 index 0000000000000..d7419745c3a0e --- /dev/null +++ b/bench/capture-trace.js @@ -0,0 +1,66 @@ +import { createServer } from 'http' +import { writeFileSync } from 'fs' + +const PORT = 9411 +const HOST = '0.0.0.0' + +const traces = [] + +const onReady = () => console.log(`Listening on http://${HOST}:${PORT}`) +const onRequest = async (req, res) => { + if ( + req.method !== 'POST' || + req.url !== '/api/v2/spans' || + (req.headers && req.headers['content-type']) !== 'application/json' + ) { + res.writeHead(200) + return res.end() + } + + try { + const body = JSON.parse(await getBody(req)) + for (const traceEvent of body) { + traces.push(traceEvent) + } + res.writeHead(200) + } catch (err) { + console.warn(err) + res.writeHead(500) + } + + res.end() +} + +const getBody = (req) => + new Promise((resolve, reject) => { + let data = '' + req.on('data', (chunk) => { + data += chunk + }) + req.on('end', () => { + if (!req.complete) { + return reject('Connection terminated before body was received.') + } + resolve(data) + }) + req.on('aborted', () => reject('Connection aborted.')) + req.on('error', () => reject('Connection error.')) + }) + +const main = () => { + const args = process.argv.slice(2) + const outFile = args[0] || `./trace-${Date.now()}.json` + + process.on('SIGINT', () => { + console.log(`\nSaving to ${outFile}...`) + writeFileSync(outFile, JSON.stringify(traces, null, 2)) + process.exit() + }) + + const server = createServer(onRequest) + server.listen(PORT, HOST, onReady) +} + +if (require.main === module) { + main() +} diff --git a/bench/package.json b/bench/package.json index eb3c593c98f8c..d311332afdefa 100644 --- a/bench/package.json +++ b/bench/package.json @@ -8,7 +8,7 @@ "bench:recursive-copy": "node recursive-copy/run" }, "dependencies": { - "fs-extra": "7.0.1", - "recursive-copy": "2.0.10" + "fs-extra": "10.0.0", + "recursive-copy": "2.0.11" } } diff --git a/bench/readdir/glob.js b/bench/readdir/glob.js index 1c409ad384ba5..170f4fb050eb5 100644 --- a/bench/readdir/glob.js +++ b/bench/readdir/glob.js @@ -1,6 +1,7 @@ -const { join } = require('path') -const { promisify } = require('util') -const globMod = require('glob') +import { join } from 'path' +import { promisify } from 'util' +import globMod from 'glob' + const glob = promisify(globMod) const resolveDataDir = join(__dirname, 'fixtures', '**/*') diff --git a/bench/readdir/recursive-readdir.js b/bench/readdir/recursive-readdir.js index 871256707ddb6..2167f30336553 100644 --- a/bench/readdir/recursive-readdir.js +++ b/bench/readdir/recursive-readdir.js @@ -1,5 +1,5 @@ -const { join } = require('path') -const { recursiveReadDir } = require('next/dist/lib/recursive-readdir') +import { join } from 'path' +import { recursiveReadDir } from 'next/dist/lib/recursive-readdir' const resolveDataDir = join(__dirname, 'fixtures') async function test() { diff --git a/bench/recursive-copy/run.js b/bench/recursive-copy/run.js index adff8be65aa42..ab12258004724 100644 --- a/bench/recursive-copy/run.js +++ b/bench/recursive-copy/run.js @@ -1,18 +1,14 @@ -const { join } = require('path') -const fs = require('fs-extra') - -const recursiveCopyNpm = require('recursive-copy') - -const { - recursiveCopy: recursiveCopyCustom, -} = require('next/dist/lib/recursive-copy') +import { join } from 'path' +import { ensureDir, outputFile, remove } from 'fs-extra' +import recursiveCopyNpm from 'recursive-copy' +import { recursiveCopy as recursiveCopyCustom } from 'next/dist/lib/recursive-copy' const fixturesDir = join(__dirname, 'fixtures') const srcDir = join(fixturesDir, 'src') const destDir = join(fixturesDir, 'dest') const createSrcFolder = async () => { - await fs.ensureDir(srcDir) + await ensureDir(srcDir) const files = new Array(100) .fill(undefined) @@ -20,7 +16,7 @@ const createSrcFolder = async () => { join(srcDir, `folder${i % 5}`, `folder${i + (1 % 5)}`, `file${i}`) ) - await Promise.all(files.map(file => fs.outputFile(file, 'hello'))) + await Promise.all(files.map((file) => outputFile(file, 'hello'))) } async function run(fn) { @@ -38,7 +34,7 @@ async function run(fn) { for (let i = 0; i < 10; i++) { const t = await test() - await fs.remove(destDir) + await remove(destDir) ts.push(t) } @@ -57,7 +53,7 @@ async function main() { console.log('test recursive-copy custom implementation') await run(recursiveCopyCustom) - await fs.remove(fixturesDir) + await remove(fixturesDir) } main() diff --git a/bench/recursive-delete/recursive-delete.js b/bench/recursive-delete/recursive-delete.js index 8989739c9039f..e23ed3e6f26a6 100644 --- a/bench/recursive-delete/recursive-delete.js +++ b/bench/recursive-delete/recursive-delete.js @@ -1,5 +1,5 @@ -const { join } = require('path') -const { recursiveDelete } = require('next/dist/lib/recursive-delete') +import { join } from 'path' +import { recursiveDelete } from 'next/dist/lib/recursive-delete' const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`) async function test() { diff --git a/bench/recursive-delete/rimraf.js b/bench/recursive-delete/rimraf.js index 2b5d50457a13c..827cdaae77484 100644 --- a/bench/recursive-delete/rimraf.js +++ b/bench/recursive-delete/rimraf.js @@ -1,8 +1,9 @@ -const { join } = require('path') -const { promisify } = require('util') -const rimrafMod = require('rimraf') -const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`, '**/*') +import { join } from 'path' +import { promisify } from 'util' +import rimrafMod from 'rimraf' + const rimraf = promisify(rimrafMod) +const resolveDataDir = join(__dirname, `fixtures-${process.argv[2]}`, '**/*') async function test() { const time = process.hrtime() diff --git a/check-pre-compiled.sh b/check-pre-compiled.sh deleted file mode 100755 index 431be334834d9..0000000000000 --- a/check-pre-compiled.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -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 65722bd46d162..bd3bbfc5ca46b 100644 --- a/contributing.md +++ b/contributing.md @@ -1,9 +1,9 @@ # Contributing to Next.js -Our Commitment to Open Source can be found [here](https://zeit.co/blog/oss) +Read about our [Commitment to Open Source](https://vercel.com/oss). 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` +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 @@ -12,16 +12,45 @@ Our Commitment to Open Source can be found [here](https://zeit.co/blog/oss) > You may need to run `yarn types` again if your types get outdated. -To contribute to [our examples](examples), take a look at the [“Adding examples” section](#adding-examples). +To contribute to [our examples](examples), take a look at the [“Adding examples” +section](#adding-examples). + +## Building + +You can build the project, including all type definitions, with: + +```bash +yarn build +# - or - +yarn prepublish +``` + +If you need to clean the project for any reason, use `yarn clean`. + +## 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: + +- Create a new markdown file under the `errors` directory based on `errors/template.md`: `cp errors/template.md errors/.md` +- Add the newly added file to `errors/manifest.json` +- 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` ## To run tests Make sure you have `chromedriver` installed for your Chrome version. You can install it with -- `brew cask install chromedriver` on Mac OS X +- `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` +You may also have to [install Rust](https://www.rust-lang.org/tools/install) and build our native packages to see all tests pass locally. We check in binaries for the most common targets and those required for CI so that most people don't have to, but if you do not see a binary for your target in `packages/next/native`, you can build it by running `yarn --cwd packages/next build-native`. If you are working on the Rust code and you need to build the binaries for ci, you can manually trigger [the workflow](https://github.com/vercel/next.js/actions/workflows/build_native.yml) to build and commit with the "Run workflow" button. + Running all tests: ```sh @@ -46,7 +75,7 @@ Running a specific test suite inside of the `test/integration` directory: yarn testonly --testPathPattern "production" ``` -Running just one test in the `production` test suite: +Running one test in the `production` test suite: ```sh yarn testonly --testPathPattern "production" -t "should allow etag header support" @@ -79,6 +108,14 @@ EXAMPLE=./test/integration/basic ## Running your own app with locally compiled version of Next.js +1. Move your app inside of the Next.js monorepo. + +2. Run with `yarn next-with-deps ./app-path-in-monorepo` + +This will use the version of `next` built inside of the Next.js monorepo and the main `yarn dev` monorepo command can be running to make changes to the local Next.js version at the same time (some changes might require re-running `yarn next-with-deps` to take affect). + +or + 1. In your app's `package.json`, replace: ```json @@ -123,24 +160,29 @@ When you add an example to the [examples](examples) directory, don’t forget to - Fill in `Example Name` and `Description`. - 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 ZEIT Now. +- 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 [ZEIT Now](https://zeit.co/now): +Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): -[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/DIRECTORY_NAME) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/DIRECTORY_NAME&project-name=DIRECTORY_NAME&repository-name=DIRECTORY_NAME) ## How to use -### Using `create-next-app` - -Execute [`create-next-app`](https://github.com/zeit/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: +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 DIRECTORY_NAME DIRECTORY_NAME-app @@ -148,24 +190,9 @@ npx create-next-app --example DIRECTORY_NAME DIRECTORY_NAME-app yarn create next-app --example DIRECTORY_NAME DIRECTORY_NAME-app ``` -### Download manually - -Download the example: - -```bash -curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/DIRECTORY_NAME -cd DIRECTORY_NAME -``` +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)). +```` -Install it and run: +## Publishing -```bash -npm install -npm run dev -# or -yarn -yarn dev -``` - -Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). -```` +Repository maintainers can use `yarn publish-canary` to publish a new version of all packages to npm. diff --git a/examples/with-firebase-authentication/sessions/.gitkeep b/data.sqlite similarity index 100% rename from examples/with-firebase-authentication/sessions/.gitkeep rename to data.sqlite diff --git a/docs/advanced-features/amp-support/amp-in-static-html-export.md b/docs/advanced-features/amp-support/amp-in-static-html-export.md index dfa3afbc41f30..67351a27d9bf5 100644 --- a/docs/advanced-features/amp-support/amp-in-static-html-export.md +++ b/docs/advanced-features/amp-support/amp-in-static-html-export.md @@ -27,7 +27,7 @@ And the AMP version of your page will include a link to the HTML page: ``` -When [`exportTrailingSlash`](/docs/api-reference/next.config.js/exportPathMap.md#0cf7d6666b394c5d8d08a16a933e86ea) is enabled the exported pages for `pages/about.js` would be: +When [`trailingSlash`](/docs/api-reference/next.config.js/trailing-slash.md) is enabled the exported pages for `pages/about.js` would be: - `out/about/index.html` - HTML page - `out/about.amp/index.html` - AMP page diff --git a/docs/advanced-features/amp-support/amp-validation.md b/docs/advanced-features/amp-support/amp-validation.md index 8590243307123..5d7f7efe52b92 100644 --- a/docs/advanced-features/amp-support/amp-validation.md +++ b/docs/advanced-features/amp-support/amp-validation.md @@ -7,3 +7,27 @@ description: AMP pages are automatically validated by Next.js during development AMP pages are automatically validated with [amphtml-validator](https://www.npmjs.com/package/amphtml-validator) during development. Errors and warnings will appear in the terminal where you started Next.js. Pages are also validated during [Static HTML export](/docs/advanced-features/static-html-export.md) and any warnings / errors will be printed to the terminal. Any AMP errors will cause the export to exit with status code `1` because the export is not valid AMP. + +### Custom Validators + +You can set up custom AMP validator in `next.config.js` as shown below: + +```jsx +module.exports = { + amp: { + validator: './custom_validator.js', + }, +} +``` + +### Skip AMP Validation + +To turn off AMP validation add the following code to `next.config.js` + +```jsx +experimental: { + amp: { + skipValidation: true + } +} +``` diff --git a/docs/advanced-features/amp-support/introduction.md b/docs/advanced-features/amp-support/introduction.md index 89ca03b4b3c00..ed660ee31ce9e 100644 --- a/docs/advanced-features/amp-support/introduction.md +++ b/docs/advanced-features/amp-support/introduction.md @@ -7,7 +7,7 @@ description: With minimal config, and without leaving React, you can start addin
Examples
@@ -21,7 +21,7 @@ To enable AMP support for a page, and to learn more about the different AMP conf ## Caveats -- Only CSS-in-JS is supported. [CSS Modules](/docs/basic-features/built-in-css-support.md) aren't supported by AMP pages at the moment. You can [contribute CSS Modules support to Next.js](https://github.com/zeit/next.js/issues/10549). +- Only CSS-in-JS is supported. [CSS Modules](/docs/basic-features/built-in-css-support.md) aren't supported by AMP pages at the moment. You can [contribute CSS Modules support to Next.js](https://github.com/vercel/next.js/issues/10549). ## Related diff --git a/docs/advanced-features/amp-support/typescript.md b/docs/advanced-features/amp-support/typescript.md index 273c943d94920..2f284c26d8035 100644 --- a/docs/advanced-features/amp-support/typescript.md +++ b/docs/advanced-features/amp-support/typescript.md @@ -6,4 +6,4 @@ description: Using AMP with TypeScript? Extend your typings to allow AMP compone AMP currently doesn't have built-in types for TypeScript, but it's in their roadmap ([#13791](https://github.com/ampproject/amphtml/issues/13791)). -As a workaround you can manually create a file called `amp.d.ts` inside your project and add the custom types described [here](https://stackoverflow.com/a/50601125). +As a workaround you can manually create a file called `amp.d.ts` inside your project and add these [custom types](https://stackoverflow.com/a/50601125). diff --git a/docs/advanced-features/automatic-static-optimization.md b/docs/advanced-features/automatic-static-optimization.md index 59315c2bc7eb6..17a3ea787c3b9 100644 --- a/docs/advanced-features/automatic-static-optimization.md +++ b/docs/advanced-features/automatic-static-optimization.md @@ -14,29 +14,27 @@ One of the main benefits of this feature is that optimized pages require no serv ## How it works -If `getServerSideProps` or `getInitialProps` is present in a page, Next.js will use its default behavior and render the page on-demand, per-request (meaning [Server-Side Rendering](/docs/basic-features/pages.md#server-side-rendering)). +If `getServerSideProps` or `getInitialProps` is present in a page, Next.js will switch to render the page on-demand, per-request (meaning [Server-Side Rendering](/docs/basic-features/pages.md#server-side-rendering)). If the above is not the case, Next.js will **statically optimize** your page automatically by prerendering the page to static HTML. -During prerendering, the router's `query` object will be empty since we do not have `query` information to provide during this phase. Any `query` values will be populated client-side after hydration. +During prerendering, the router's `query` object will be empty since we do not have `query` information to provide during this phase. After hydration, Next.js will trigger an update to your application to provide the route parameters in the `query` object. > **Note:** Parameters added with [dynamic routes](/docs/routing/dynamic-routes.md) to a page that's using [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) will always be available inside the `query` object. `next build` will emit `.html` files for statically optimized pages. For example, the result for the page `pages/about.js` would be: ```bash -.next/server/static/${BUILD_ID}/about.html +.next/server/pages/about.html ``` And if you add `getServerSideProps` to the page, it will then be JavaScript, like so: ```bash -.next/server/static/${BUILD_ID}/about.js +.next/server/pages/about.js ``` -In development you'll know if `pages/about.js` is optimized or not thanks to the included [static optimization indicator](/docs/api-reference/next.config.js/static-optimization-indicator.md). - ## Caveats -- If you have a [custom `App`](/docs/advanced-features/custom-app.md) with `getInitialProps` then this optimization will be disabled for all pages. +- If you have a [custom `App`](/docs/advanced-features/custom-app.md) with `getInitialProps` then this optimization will be turned off in pages without [Static Generation](/docs/basic-features/data-fetching.md#getstaticprops-static-generation). - If you have a [custom `Document`](/docs/advanced-features/custom-document.md) with `getInitialProps` be sure you check if `ctx.req` is defined before assuming the page is server-side rendered. `ctx.req` will be `undefined` for pages that are prerendered. diff --git a/docs/advanced-features/codemods.md b/docs/advanced-features/codemods.md new file mode 100644 index 0000000000000..f897c89f206ea --- /dev/null +++ b/docs/advanced-features/codemods.md @@ -0,0 +1,186 @@ +--- +description: Use codemods to update your codebase when upgrading Next.js to the latest version +--- + +# Next.js Codemods + +Next.js provides Codemod transformations to help upgrade your Next.js codebase when a feature is deprecated. + +Codemods are transformations that run on your codebase programmatically. This allows for a large amount of changes to be applied without having to manually go through every file. + +## Usage + +`npx @next/codemod ` + +- `transform` - name of transform, see available transforms below. +- `path` - files or directory to transform +- `--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` + +Transforms files that do not import `React` to include the import in order for the new [React JSX transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) to work. + +For example: + +```jsx +// my-component.js +export default class Home extends React.Component { + render() { + return
Hello World
+ } +} +``` + +Transforms into: + +```jsx +// my-component.js +import React from 'react' +export default class Home extends React.Component { + render() { + return
Hello World
+ } +} +``` + +## Next.js 9 + +### `name-default-component` + +Transforms anonymous components into named components to make sure they work with [Fast Refresh](https://nextjs.org/blog/next-9-4#fast-refresh). + +For example: + +```jsx +// my-component.js +export default function () { + return
Hello World
+} +``` + +Transforms into: + +```jsx +// my-component.js +export default function MyComponent() { + return
Hello World
+} +``` + +The component will have a camel cased name based on the name of the file, and it also works with arrow functions. + +#### Usage + +Go to your project + +``` +cd path-to-your-project/ +``` + +Run the codemod: + +``` +npx @next/codemod name-default-component +``` + +### `withamp-to-config` + +Transforms the `withAmp` HOC into Next.js 9 page configuration. + +For example: + +```js +// Before +import { withAmp } from 'next/amp' + +function Home() { + return

My AMP Page

+} + +export default withAmp(Home) +``` + +```js +// After +export default function Home() { + return

My AMP Page

+} + +export const config = { + amp: true, +} +``` + +#### Usage + +Go to your project + +``` +cd path-to-your-project/ +``` + +Run the codemod: + +``` +npx @next/codemod withamp-to-config +``` + +## Next.js 6 + +### `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: [https://nextjs.org/docs/messages/url-deprecated](https://nextjs.org/docs/messages/url-deprecated) + +For example: + +```js +// From +import React from 'react' +export default class extends React.Component { + render() { + const { pathname } = this.props.url + return
Current pathname: {pathname}
+ } +} +``` + +```js +// To +import React from 'react' +import { withRouter } from 'next/router' +export default withRouter( + class extends React.Component { + render() { + const { pathname } = this.props.router + return
Current pathname: {pathname}
+ } + } +) +``` + +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 + +Go to your project + +``` +cd path-to-your-project/ +``` + +Run the codemod: + +``` +npx @next/codemod url-to-withrouter +``` diff --git a/docs/advanced-features/custom-app.md b/docs/advanced-features/custom-app.md index 1eba8e6fec1c7..71dd20af32577 100644 --- a/docs/advanced-features/custom-app.md +++ b/docs/advanced-features/custom-app.md @@ -10,7 +10,7 @@ Next.js uses the `App` component to initialize pages. You can override it and co - Keeping state when navigating pages - Custom error handling using `componentDidCatch` - Inject additional data into pages -- [Add global CSS](/docs/basic-features/built-in-css-support#adding-a-global-stylesheet) +- [Add global CSS](/docs/basic-features/built-in-css-support.md#adding-a-global-stylesheet) To override the default `App`, create the file `./pages/_app.js` as shown below: @@ -38,13 +38,18 @@ export default MyApp The `Component` prop is the active `page`, so whenever you navigate between routes, `Component` will change to the new `page`. Therefore, any props you send to `Component` will be received by the `page`. -`pageProps` is an object with the initial props that were preloaded for your page, it's an empty object if the page is not using [`getInitialProps`](/docs/api-reference/data-fetching/getInitialProps.md). +`pageProps` is an object with the initial props that were preloaded for your page by one of our [data fetching methods](/docs/basic-features/data-fetching.md), otherwise it's an empty object. -> Adding a custom `getInitialProps` in your `App` will disable [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md). +### Caveats + +- If your app is running and you added a custom `App`, you'll need to restart the development server. Only required if `pages/_app.js` didn't exist before. +- Adding a custom `getInitialProps` in your `App` will disable [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) in pages without [Static Generation](/docs/basic-features/data-fetching.md#getstaticprops-static-generation). +- When you add `getInitialProps` in your custom app, you must `import App from "next/app"`, call `App.getInitialProps(appContext)` inside `getInitialProps` and merge the returned object into the return value. +- `App` currently does not support Next.js [Data Fetching methods](/docs/basic-features/data-fetching.md) like [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) or [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering). ### TypeScript -If you’re using TypeScript, take a look at [our TypeScript documentation](/docs/basic-features/typescript#custom-app). +If you’re using TypeScript, take a look at [our TypeScript documentation](/docs/basic-features/typescript.md#custom-app). ## Related diff --git a/docs/advanced-features/custom-document.md b/docs/advanced-features/custom-document.md index f1f8aa3348abc..2c75cca86b376 100644 --- a/docs/advanced-features/custom-document.md +++ b/docs/advanced-features/custom-document.md @@ -6,8 +6,6 @@ description: Extend the default document markup added by Next.js. A custom `Document` is commonly used to augment your application's `` and `` tags. This is necessary because Next.js pages skip the definition of the surrounding document's markup. -A custom `Document` can also include `getInitialProps` for expressing asynchronous server-rendering data requirements. - To override the default `Document`, create the file `./pages/_document.js` and extend the `Document` class as shown below: ```jsx @@ -35,6 +33,8 @@ class MyDocument extends Document { export default MyDocument ``` +> The code above is the default `Document` added by Next.js. Feel free to remove the `getInitialProps` or `render` function from `MyDocument` if you don't need to change them. + ``, ``, `
` and `` are required for the page to be properly rendered. Custom attributes are allowed as props, like `lang`: @@ -43,17 +43,18 @@ Custom attributes are allowed as props, like `lang`: ``` +The `` component used here is not the same one from [`next/head`](/docs/api-reference/next/head.md). The `` component used here should only be used for any `` code that is common for all pages. For all other cases, such as `` tags, we recommend using [`next/head`](/docs/api-reference/next/head.md) in your pages or components. + The `ctx` object is equivalent to the one received in [`getInitialProps`](/docs/api-reference/data-fetching/getInitialProps.md#context-object), with one addition: -- `renderPage`: `Function` - a callback that executes the actual React rendering logic (synchronously). It's useful to decorate this function in order to support server-rendering wrappers like Aphrodite's [`renderStatic`](https://github.com/Khan/aphrodite#server-side-rendering) +- `renderPage`: `Function` - a callback that runs the actual React rendering logic (synchronously). It's useful to decorate this function in order to support server-rendering wrappers like Aphrodite's [`renderStatic`](https://github.com/Khan/aphrodite#server-side-rendering) ## Caveats -- `Document` is only rendered in the server, event handlers like `onClick` won't work -- React components outside of `<Main />` will not be initialized by the browser. Do _not_ add application logic here. If you need shared components in all your pages (like a menu or a toolbar), take a look at the [`App`](/docs/advanced-features/custom-app.md) component instead -- `Document`'s `getInitialProps` function is not called during client-side transitions, nor when a page is [statically optimized](/docs/advanced-features/automatic-static-optimization.md) -- Make sure to check if `ctx.req` / `ctx.res` are defined in `getInitialProps`. Those variables will be `undefined` when a page is being statically exported by [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) or by [`next export`](/docs/advanced-features/static-html-export.md) -- Common errors include adding a `<title>` in the `<Head />` tag or using `styled-jsx`. These should be avoided in `pages/_document.js` as they lead to unexpected behavior +- `Document` is only rendered in the server, event handlers like `onClick` won't work. +- React components outside of `<Main />` will not be initialized by the browser. Do _not_ add application logic here or custom CSS (like `styled-jsx`). If you need shared components in all your pages (like a menu or a toolbar), take a look at the [`App`](/docs/advanced-features/custom-app.md) component instead. +- `Document`'s `getInitialProps` function is not called during client-side transitions, nor when a page is [statically optimized](/docs/advanced-features/automatic-static-optimization.md). +- `Document` 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). ## Customizing `renderPage` @@ -71,9 +72,9 @@ class MyDocument extends Document { ctx.renderPage = () => originalRenderPage({ // useful for wrapping the whole react tree - enhanceApp: App => App, + enhanceApp: (App) => App, // useful for wrapping in a per-page basis - enhanceComponent: Component => Component, + enhanceComponent: (Component) => Component, }) // Run the parent `getInitialProps`, it now includes the custom `renderPage` @@ -85,3 +86,21 @@ class MyDocument extends Document { export default MyDocument ``` + +## TypeScript + +You can use the built-in `DocumentContext` type and change the file name to `./pages/_document.tsx` like so: + +```tsx +import Document, { DocumentContext } from 'next/document' + +class MyDocument extends Document { + static async getInitialProps(ctx: DocumentContext) { + const initialProps = await Document.getInitialProps(ctx) + + return initialProps + } +} + +export default MyDocument +``` diff --git a/docs/advanced-features/custom-error-page.md b/docs/advanced-features/custom-error-page.md index 64199d2668159..6dfa999892be1 100644 --- a/docs/advanced-features/custom-error-page.md +++ b/docs/advanced-features/custom-error-page.md @@ -2,6 +2,8 @@ description: Override and extend the built-in Error page to handle custom errors. --- +# Custom Error Page + ## 404 Page A 404 page may be accessed very often. Server-rendering an error page for every visit increases the load of the Next.js server. This can result in increased costs and slow experiences. @@ -19,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 <h1>500 - Server-side error occurred</h1> +} +``` -### Customizing The Error Page +> **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. + +### 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: @@ -54,10 +71,9 @@ If you want to render the built-in error page you can by importing the `Error` c ```jsx import Error from 'next/error' -import fetch from 'node-fetch' export async function getServerSideProps() { - const res = await fetch('https://api.github.com/repos/zeit/next.js') + const res = await fetch('https://api.github.com/repos/vercel/next.js') const errorCode = res.ok ? false : res.statusCode const json = await res.json() @@ -76,3 +92,5 @@ export default function Page({ errorCode, stars }) { ``` The `Error` component also takes `title` as a property if you want to pass in a text message along with a `statusCode`. + +If you have a custom `Error` component be sure to import that one instead. `next/error` exports the default component used by Next.js. diff --git a/docs/advanced-features/custom-server.md b/docs/advanced-features/custom-server.md index b7ee881c8e3fa..f6d95c59e7b2d 100644 --- a/docs/advanced-features/custom-server.md +++ b/docs/advanced-features/custom-server.md @@ -7,17 +7,19 @@ description: Start a Next.js app programmatically using a custom server. <details> <summary><b>Examples</b></summary> <ul> - <li><a href="/zeit/next.js/tree/canary/examples/custom-server">Basic custom server</a></li> - <li><a href="/zeit/next.js/tree/canary/examples/custom-server-express">Express integration</a></li> - <li><a href="/zeit/next.js/tree/canary/examples/custom-server-hapi">Hapi integration</a></li> - <li><a href="/zeit/next.js/tree/canary/examples/custom-server-koa">Koa integration</a></li> - <li><a href="/zeit/next.js/tree/canary/examples/ssr-caching">SSR Caching</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/custom-server">Basic custom server</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/custom-server-express">Express integration</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/custom-server-hapi">Hapi integration</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/custom-server-koa">Koa integration</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/ssr-caching">SSR Caching</a></li> </ul> </details> -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. -> 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).** +> **Note:** A custom server **can not** be deployed on [Vercel](https://vercel.com/solutions/nextjs). + +> Before deciding to use a custom server, please keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements. A custom server will remove important performance optimizations, like **serverless functions** and **[Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md).** Take a look at the following example of a custom server: @@ -39,13 +41,13 @@ app.prepare().then(() => { const { pathname, query } = parsedUrl if (pathname === '/a') { - app.render(req, res, '/b', query) - } else if (pathname === '/b') { app.render(req, res, '/a', query) + } else if (pathname === '/b') { + app.render(req, res, '/b', query) } else { handle(req, res, parsedUrl) } - }).listen(3000, err => { + }).listen(3000, (err) => { if (err) throw err console.log('> Ready on http://localhost:3000') }) @@ -94,6 +96,6 @@ module.exports = { } ``` -> Note that `useFileSystemPublicRoutes` simply disables filename routes from SSR; client-side routing may still access those paths. When using this option, you should guard against navigation to routes you do not want programmatically. +> Note that `useFileSystemPublicRoutes` disables filename routes from SSR; client-side routing may still access those paths. When using this option, you should guard against navigation to routes you do not want programmatically. -> You may also wish to configure the client-side Router to disallow client-side redirects to filename routes; for that refer to [`Router.beforePopState`](/docs/api-reference/next/router.md#router.beforePopState). +> You may also wish to configure the client-side router to disallow client-side redirects to filename routes; for that refer to [`router.beforePopState`](/docs/api-reference/next/router.md#router.beforePopState). diff --git a/docs/advanced-features/customizing-babel-config.md b/docs/advanced-features/customizing-babel-config.md index faea0981c88ac..aee52289f1589 100644 --- a/docs/advanced-features/customizing-babel-config.md +++ b/docs/advanced-features/customizing-babel-config.md @@ -7,13 +7,13 @@ description: Extend the babel preset added by Next.js with your own configs. <details> <summary><b>Examples</b></summary> <ul> - <li><a href="/zeit/next.js/tree/canary/examples/with-custom-babel-config">Customizing babel configuration</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/with-custom-babel-config">Customizing babel configuration</a></li> </ul> </details> -Next.js includes the `next/babel` preset to your app, it 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. +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 file is found, we're going to consider it the _source of truth_, 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: @@ -24,17 +24,18 @@ Here's an example `.babelrc` file: } ``` -The `next/babel` presets includes: +You can [take a look at this file](https://github.com/vercel/next.js/blob/canary/packages/next/build/babel/preset.ts) to learn about the presets included by `next/babel`. -- preset-env -- preset-react -- preset-typescript -- plugin-proposal-class-properties -- plugin-proposal-object-rest-spread -- plugin-transform-runtime -- styled-jsx +To add presets/plugins **without configuring them**, you can do it this way: -To configure these presets/plugins, **do not** add them to `presets` or `plugins` in your custom `.babelrc`. Instead, configure them on the `next/babel` preset, like so: +```json +{ + "presets": ["next/babel"], + "plugins": ["@babel/plugin-proposal-do-expressions"] +} +``` + +To add presets/plugins **with custom configuration**, do it on the `next/babel` preset like so: ```json { @@ -57,4 +58,4 @@ To learn more about the available options for each config, visit their documenta > Next.js uses the **current** Node.js version for server-side compilations. -> The `modules` option on `"preset-env"` should be kept to `false`, otherwise webpack code splitting is disabled. +> The `modules` option on `"preset-env"` should be kept to `false`, otherwise webpack code splitting is turned off. diff --git a/docs/advanced-features/customizing-postcss-config.md b/docs/advanced-features/customizing-postcss-config.md index 0b1cb7bbb2780..b89b4817f11c2 100644 --- a/docs/advanced-features/customizing-postcss-config.md +++ b/docs/advanced-features/customizing-postcss-config.md @@ -7,13 +7,13 @@ description: Extend the PostCSS config and plugins added by Next.js with your ow <details open> <summary><b>Examples</b></summary> <ul> - <li><a href="/zeit/next.js/tree/canary/examples/with-tailwindcss">Tailwind CSS Example</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/with-tailwindcss">Tailwind CSS Example</a></li> </ul> </details> ## Default Behavior -Next.js compiles CSS for its [built-in CSS support](/docs/basic-features/built-in-css-support) using PostCSS. +Next.js compiles CSS for its [built-in CSS support](/docs/basic-features/built-in-css-support.md) using PostCSS. Out of the box, with no configuration, Next.js compiles CSS with the following transformations: @@ -24,10 +24,46 @@ Out of the box, with no configuration, Next.js compiles CSS with the following t - [Break Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/break-after) - [`font-variant` Property](https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant) - [Gap Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/gap) - - [Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/grid) - [Media Query Ranges](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#Syntax_improvements_in_Level_4) -By default, [Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/var) (CSS variables) are **not compiled** for IE11 support. +By default, [CSS Grid](https://www.w3.org/TR/css-grid-1/) and [Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/var) (CSS variables) are **not compiled** for IE11 support. + +To compile [CSS Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/grid) for IE11, you can place the following comment at the top of your CSS file: + +```css +/* autoprefixer grid: autoplace */ +``` + +You can also enable IE11 support for [CSS Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/grid) +in your entire project by configuring autoprefixer with the configuration shown below (collapsed). +See ["Customizing Plugins"](#customizing-plugins) below for more information. + +<details> +<summary><strong>Click to view the configuration to enable CSS Grid Layout</strong></summary> + +```json +{ + "plugins": [ + "postcss-flexbugs-fixes", + [ + "postcss-preset-env", + { + "autoprefixer": { + "flexbox": "no-2009", + "grid": "autoplace" + }, + "stage": 3, + "features": { + "custom-properties": false + } + } + ] + ] +} +``` + +</details> +<br/> CSS variables are not compiled because it is [not possible to safely do so](https://github.com/MadLittleMods/postcss-css-variables#caveats). If you must use variables, consider using something like [Sass variables](https://sass-lang.com/documentation/variables) which are compiled away by [Sass](https://sass-lang.com/). @@ -50,12 +86,13 @@ You can use the [browserl.ist](https://browserl.ist/?q=%3E0.3%25%2C+not+ie+11%2C No configuration is needed to support CSS Modules. To enable CSS Modules for a file, rename the file to have the extension `.module.css`. -You can learn more about [Next.js' CSS Module support here](/docs/basic-features/built-in-css-support). +You can learn more about [Next.js' CSS Module support here](/docs/basic-features/built-in-css-support.md). ## Customizing Plugins > **Warning**: When you define a custom PostCSS configuration file, Next.js **completely disables** the [default behavior](#default-behavior). > Be sure to manually configure all the features you need compiled, including [Autoprefixer](https://github.com/postcss/autoprefixer). +> You also need to install any plugins included in your custom configuration manually, i.e. `npm install postcss-flexbugs-fixes postcss-preset-env`. To customize the PostCSS configuration, create a `postcss.config.json` file in the root of your project. diff --git a/docs/advanced-features/debugging.md b/docs/advanced-features/debugging.md new file mode 100644 index 0000000000000..d400d334367db --- /dev/null +++ b/docs/advanced-features/debugging.md @@ -0,0 +1,77 @@ +--- +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). + +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. + +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/). + +## Step 1: Start Next.js in debug mode + +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. + +First, start Next.js with the inspect flag: + +```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`: + +```json +"dev": "NODE_OPTIONS='--inspect' next dev" +``` + +The result of launching Next.js with the inspect flag looks 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 +``` + +> Be aware that using `NODE_OPTIONS='--inspect' npm run dev` or `NODE_OPTIONS='--inspect' yarn dev` won't work. This would try to start multiple debuggers on the same port: one for the npm/yarn process and one for Next.js. You would then get an error like `Starting inspector on 127.0.0.1:9229 failed: address already in use` in your console. + +## Step 2: Connect to the debugger + +### Using Chrome DevTools + +Once you open a new tab in Google Chrome and go to `chrome://inspect`, you should see your Next.js application inside the "Remote Target" section. Now click "inspect" to open a screen that will be your debugging environment from now on. + +### Using the Debugger in Visual Studio Code + +We will be using the [attach mode](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_setting-up-an-attach-configuration) of VS Code to attach the VS Code inspector to our running debugger started in step 1. + +Create a file named `.vscode/launch.json` at the root of your project with this content: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "attach", + "name": "Attach to application", + "skipFiles": ["<node_internals>/**"], + "port": 9229 + } + ] +} +``` + +Now hit <kdb>F5</kbd> 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. + +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 appear. + +To learn more on 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) diff --git a/docs/advanced-features/dynamic-import.md b/docs/advanced-features/dynamic-import.md index 547137cbb4187..9bd60726971b5 100644 --- a/docs/advanced-features/dynamic-import.md +++ b/docs/advanced-features/dynamic-import.md @@ -4,17 +4,49 @@ description: Dynamically import JavaScript modules and React Components and spli # Dynamic Import -<details> +<details open> <summary><b>Examples</b></summary> <ul> - <li><a href="/zeit/next.js/tree/canary/examples/with-dynamic-import">Dynamic Import</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/with-dynamic-import">Dynamic Import</a></li> </ul> </details> -Next.js supports ES2020 [dynamic `import()`](https://github.com/tc39/proposal-dynamic-import) for JavaScript. With it you can import JavaScript modules (inc. React Components) dynamically and work with them. They also work with SSR. +Next.js supports ES2020 [dynamic `import()`](https://github.com/tc39/proposal-dynamic-import) for JavaScript. With it you can import JavaScript modules dynamically and work with them. They also work with SSR. + +In the following example, we implement fuzzy search using `fuse.js` and only load the module dynamically in the browser after the user types in the search input: + +```jsx +import { useState } from 'react' + +const names = ['Tim', 'Joe', 'Bel', 'Max', 'Lee'] + +export default function Page() { + const [results, setResults] = useState() + + return ( + <div> + <input + type="text" + placeholder="Search" + onChange={async (e) => { + const { value } = e.currentTarget + // Dynamically load fuse.js + const Fuse = (await import('fuse.js')).default + const fuse = new Fuse(names) + + setResults(fuse.search(value)) + }} + /> + <pre>Results: {JSON.stringify(results, null, 2)}</pre> + </div> + ) +} +``` 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 like any other React Component. Check out the sections below for more details on how it works. + ## Basic usage In the following example, the module `../components/hello` will be dynamically loaded by the page: @@ -39,6 +71,8 @@ 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**: 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 If the dynamic component is not the default export, you can use a named export too. Consider the module `../components/hello.js` which has a named export `Hello`: @@ -55,7 +89,7 @@ To dynamically import the `Hello` component, you can return it from the [Promise import dynamic from 'next/dynamic' const DynamicComponent = dynamic(() => - import('../components/hello').then(mod => mod.Hello) + import('../components/hello').then((mod) => mod.Hello) ) function Home() { @@ -98,7 +132,7 @@ export default Home ## With no SSR -You may not always want to include a module on server-side, For example, when the module includes a library that only works in the browser. +You may not always want to include a module on server-side. For example, when the module includes a library that only works in the browser. Take a look at the following example: diff --git a/docs/advanced-features/i18n-routing.md b/docs/advanced-features/i18n-routing.md new file mode 100644 index 0000000000000..5932f80d77461 --- /dev/null +++ b/docs/advanced-features/i18n-routing.md @@ -0,0 +1,288 @@ +--- +description: Next.js has built-in support for internationalized routing and language detection. Learn more here. +--- + +# Internationalized Routing + +<details> + <summary><b>Examples</b></summary> + <ul> + <li><a href="/vercel/next.js/tree/canary/examples/i18n-routing">i18n routing</a></li> + </ul> +</details> + +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`](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 + +To get started, add the `i18n` config to your `next.config.js` file. + +Locales are [UTS Locale Identifiers](https://www.unicode.org/reports/tr35/tr35-59/tr35.html#Identifiers), a standardized format for defining locales. + +Generally a Locale Identifier is made up of a language, region, and script separated by a dash: `language-region-script`. The region and script are optional. An example: + +- `en-US` - English as spoken in the United States +- `nl-NL` - Dutch as spoken in the Netherlands +- `nl` - Dutch, no specific region + +```js +// next.config.js +module.exports = { + i18n: { + // These are all the locales you want to support in + // your application + locales: ['en-US', 'fr', 'nl-NL'], + // This is the default locale you want to be used when visiting + // a non-locale prefixed path e.g. `/hello` + defaultLocale: 'en-US', + // This is a list of locale domains and the default locale they + // should handle (these are only required when setting up domain routing) + // Note: subdomains must be included in the domain value to be matched e.g. "fr.example.com". + domains: [ + { + domain: 'example.com', + defaultLocale: 'en-US', + }, + { + domain: 'example.nl', + defaultLocale: 'nl-NL', + }, + { + 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, + }, + ], + }, +} +``` + +## Locale Strategies + +There are two locale handling strategies: Sub-path Routing and Domain Routing. + +### Sub-path Routing + +Sub-path Routing puts the locale in the url path. + +```js +// next.config.js +module.exports = { + i18n: { + locales: ['en-US', 'fr', 'nl-NL'], + defaultLocale: 'en-US', + }, +} +``` + +With the above configuration `en-US`, `fr`, and `nl-NL` will be available to be routed to, and `en-US` is the default locale. If you have a `pages/blog.js` the following urls would be available: + +- `/blog` +- `/fr/blog` +- `/nl-nl/blog` + +The default locale does not have a prefix. + +### Domain Routing + +By using domain routing you can configure locales to be served from different domains: + +```js +// next.config.js +module.exports = { + i18n: { + locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'], + defaultLocale: 'en-US', + + domains: [ + { + domain: 'example.com', + defaultLocale: 'en-US', + }, + { + domain: 'example.fr', + defaultLocale: 'fr', + }, + { + domain: 'example.nl', + defaultLocale: 'nl-NL', + // specify other locales that should be redirected + // to this domain + locales: ['nl-BE'], + }, + ], + }, +} +``` + +For example if you have `pages/blog.js` the following urls will be available: + +- `example.com/blog` +- `example.fr/blog` +- `example.nl/blog` +- `example.nl/nl-BE/blog` + +## Automatic Locale Detection + +When a user visits the application root (generally `/`), Next.js will try to automatically detect which locale the user prefers based on the [`Accept-Language`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language) header and the current domain. + +If a locale other than the default locale is detected, the user will be redirected to either: + +- **When using Sub-path Routing:** The locale prefixed path +- **When using Domain Routing:** The domain with that locale specified as the default + +When using Domain Routing, if a user with the `Accept-Language` header `fr;q=0.9` visits `example.com`, they will be redirected to `example.fr` since that domain handles the `fr` locale by default. + +When using Sub-path Routing, the user would be redirected to `/fr`. + +### Disabling Automatic Locale Detection + +The automatic locale detection can be disabled with: + +```js +// next.config.js +module.exports = { + i18n: { + localeDetection: false, + }, +} +``` + +When `localeDetection` is set to `false` Next.js will no longer automatically redirect based on the user's preferred locale and will only provide locale information detected from either the locale based domain or locale path as described above. + +## Accessing the locale information + +You can access the locale information via the Next.js router. For example, using the [`useRouter()`](/docs/api-reference/next/router.md#userouter) hook the following properties are available: + +- `locale` contains the currently active locale. +- `locales` contains all configured locales. +- `defaultLocale` contains the configured default locale. + +When [pre-rendering](/docs/basic-features/pages.md#static-generation-recommended) pages with `getStaticProps` or `getServerSideProps`, the locale information is provided in [the context](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) provided to the function. + +When leveraging `getStaticPaths`, the configured locales are provided in the context parameter of the function under `locales` and the configured defaultLocale under `defaultLocale`. + +## Transition between locales + +You can use `next/link` or `next/router` to transition between locales. + +For `next/link`, a `locale` prop can be provided to transition to a different locale from the currently active one. If no `locale` prop is provided, the currently active `locale` is used during client-transitions. For example: + +```jsx +import Link from 'next/link' + +export default function IndexPage(props) { + return ( + <Link href="/another" locale="fr"> + <a>To /fr/another</a> + </Link> + ) +} +``` + +When using the `next/router` methods directly, you can specify the `locale` that should be used via the transition options. For example: + +```jsx +import { useRouter } from 'next/router' + +export default function IndexPage(props) { + const router = useRouter() + + return ( + <div + onClick={() => { + router.push('/another', '/another', { locale: 'fr' }) + }} + > + to /fr/another + </div> + ) +} +``` + +If you have a `href` that already includes the locale you can opt-out of automatically handling the locale prefixing: + +```jsx +import Link from 'next/link' + +export default function IndexPage(props) { + return ( + <Link href="/fr/another" locale={false}> + <a>To /fr/another</a> + </Link> + ) +} +``` + +## 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 when redirecting from `/` to the correct locale location. + +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 + +Since Next.js knows what language the user is visiting it will automatically add the `lang` attribute to the `<html>` tag. + +Next.js doesn't know about variants of a page so it's up to you to add the `hreflang` meta tags using [`next/head`](/docs/api-reference/next/head.md). You can learn more about `hreflang` in the [Google Webmasters documentation](https://support.google.com/webmasters/answer/189077). + +## How does this work with Static Generation? + +> 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. + +### 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. + +### Non-dynamic getStaticProps Pages + +For non-dynamic `getStaticProps` pages, a version is generated for each locale like above. `getStaticProps` is called with each `locale` that is being rendered. If you would like to opt-out of a certain locale from being pre-rendered, you can return `notFound: true` from `getStaticProps` and this variant of the page will not be generated. + +```js +export async function getStaticProps({ locale }) { + // Call an external API endpoint to get posts. + // You can use any data fetching library + const res = await fetch(`https://.../posts?locale=${locale}`) + const posts = await res.json() + + if (posts.length === 0) { + return { + notFound: true, + } + } + + // By returning { props: posts }, the Blog component + // will receive `posts` as a prop at build time + return { + props: { + posts, + }, + } +} +``` + +### Dynamic getStaticProps Pages + +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: + +```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, + } +} +``` + +## Limits for the i18n config + +- `locales`: 100 total locales +- `domains`: 100 total locale domain items diff --git a/docs/advanced-features/measuring-performance.md b/docs/advanced-features/measuring-performance.md new file mode 100644 index 0000000000000..789d94bb8759c --- /dev/null +++ b/docs/advanced-features/measuring-performance.md @@ -0,0 +1,198 @@ +--- +description: Measure and track page performance using Next.js Analytics +--- + +# Measuring performance + +[Next.js Analytics](https://nextjs.org/analytics) allows you to analyze and measure the performance of +pages using different metrics. + +You can start collecting your [Real Experience Score](https://vercel.com/docs/analytics#metrics) with zero-configuration on [Vercel deployments](https://vercel.com/docs/analytics). There's also support for Analytics if you're [self-hosting](https://vercel.com/docs/analytics#self-hosted). + +The rest of this documentation describes the built-in relayer Next.js Analytics uses. + +## Build Your Own + +First, you will need to create a [custom App](/docs/advanced-features/custom-app.md) component and define a `reportWebVitals` function: + +```js +// pages/_app.js +export function reportWebVitals(metric) { + console.log(metric) +} + +function MyApp({ Component, pageProps }) { + return <Component {...pageProps} /> +} + +export default MyApp +``` + +This function is fired when the final values for any of the metrics have finished calculating on +the page. You can use to log any of the results to the console or send to a particular endpoint. + +The `metric` object returned to the function consists of a number of properties: + +- `id`: Unique identifier for the metric in the context of the current page load +- `name`: Metric name +- `startTime`: First recorded timestamp of the performance entry in [milliseconds](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp) (if applicable) +- `value`: Value, or duration in [milliseconds](https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp), of the performance entry +- `label`: Type of metric (`web-vital` or `custom`) + +There are two types of metrics that are tracked: + +- Web Vitals +- Custom metrics + +## Web Vitals + +[Web Vitals](https://web.dev/vitals/) are a set of useful metrics that aim to capture the user +experience of a web page. The following web vitals are all included: + +- [Time to First Byte](https://developer.mozilla.org/en-US/docs/Glossary/Time_to_first_byte) (TTFB) +- [First Contentful Paint](https://developer.mozilla.org/en-US/docs/Glossary/First_contentful_paint) (FCP) +- [Largest Contentful Paint](https://web.dev/lcp/) (LCP) +- [First Input Delay](https://web.dev/fid/) (FID) +- [Cumulative Layout Shift](https://web.dev/cls/) (CLS) + +You can handle all the results of these metrics using the `web-vital` label: + +```js +export function reportWebVitals(metric) { + if (metric.label === 'web-vital') { + console.log(metric) // The metric object ({ id, name, startTime, value, label }) is logged to the console + } +} +``` + +There's also the option of handling each of the metrics separately: + +```js +export function reportWebVitals(metric) { + switch (metric.name) { + case 'FCP': + // handle FCP results + break + case 'LCP': + // handle LCP results + break + case 'CLS': + // handle CLS results + break + case 'FID': + // handle FID results + break + case 'TTFB': + // handle TTFB results + break + default: + break + } +} +``` + +A third-party library, [web-vitals](https://github.com/GoogleChrome/web-vitals), is used to measure +these metrics. Browser compatibility depends on the particular metric, so refer to the [Browser +Support](https://github.com/GoogleChrome/web-vitals#browser-support) section to find out which +browsers are supported. + +## Custom metrics + +In addition to the core metrics listed above, there are some additional custom metrics that +measure the time it takes for the page to hydrate and render: + +- `Next.js-hydration`: Length of time it takes for the page to start and finish hydrating (in ms) +- `Next.js-route-change-to-render`: Length of time it takes for a page to start rendering after a + route change (in ms) +- `Next.js-render`: Length of time it takes for a page to finish render after a route change (in ms) + +You can handle all the results of these metrics using the `custom` label: + +```js +export function reportWebVitals(metric) { + if (metric.label === 'custom') { + console.log(metric) // The metric object ({ id, name, startTime, value, label }) is logged to the console + } +} +``` + +There's also the option of handling each of the metrics separately: + +```js +export function reportWebVitals(metric) { + switch (metric.name) { + case 'Next.js-hydration': + // handle hydration results + break + case 'Next.js-route-change-to-render': + // handle route-change to render results + break + case 'Next.js-render': + // handle render results + break + default: + break + } +} +``` + +These metrics work in all browsers that support the [User Timing API](https://caniuse.com/#feat=user-timing). + +## Sending results to analytics + +With the relay function, you can send any of results to an analytics endpoint to measure and track +real user performance on your site. For example: + +```js +export function reportWebVitals(metric) { + const body = JSON.stringify(metric) + const url = 'https://example.com/analytics' + + // Use `navigator.sendBeacon()` if available, falling back to `fetch()`. + if (navigator.sendBeacon) { + navigator.sendBeacon(url, body) + } else { + fetch(url, { body, method: 'POST', keepalive: true }) + } +} +``` + +> **Note**: If you use [Google Analytics](https://analytics.google.com/analytics/web/), using the +> `id` value can allow you to construct metric distributions manually (to calculate percentiles, +> etc...). +> +> ```js +> export function reportWebVitals({ id, name, label, value }) { +> // Use `window.gtag` if you initialized Google Analytics as this example: +> // https://github.com/vercel/next.js/blob/canary/examples/with-google-analytics/pages/_document.js +> window.gtag('event', name, { +> event_category: +> label === 'web-vital' ? 'Web Vitals' : 'Next.js custom metric', +> value: Math.round(name === 'CLS' ? value * 1000 : value), // values must be integers +> event_label: id, // id unique to current page load +> non_interaction: true, // avoids affecting bounce rate. +> }) +> } +> ``` +> +> Read more about [sending results to Google Analytics](https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics). + +## TypeScript + +If you are using TypeScript, you can use the built-in type `NextWebVitalsMetric`: + +```ts +// pages/_app.tsx + +import type { AppProps, NextWebVitalsMetric } from 'next/app' + +function MyApp({ Component, pageProps }: AppProps) { + return <Component {...pageProps} /> +} + +export function reportWebVitals(metric: NextWebVitalsMetric) { + console.log(metric) +} + +export default MyApp +``` diff --git a/docs/advanced-features/module-path-aliases.md b/docs/advanced-features/module-path-aliases.md new file mode 100644 index 0000000000000..8015f82667cd0 --- /dev/null +++ b/docs/advanced-features/module-path-aliases.md @@ -0,0 +1,95 @@ +--- +description: Configure module path aliases that allow you to remap certain import paths. +--- + +# Absolute Imports and Module path aliases + +<details> + <summary><b>Examples</b></summary> + <ul> + <li><a href="/vercel/next.js/tree/canary/examples/with-absolute-imports">Absolute Imports</a></li> + </ul> +</details> + +Next.js automatically supports the `tsconfig.json` and `jsconfig.json` `"paths"` and `"baseUrl"` options since [Next.js 9.4](https://nextjs.org/blog/next-9-4). + +> 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. + +The `baseUrl` configuration option allows you to import directly from the root of the project. + +An example of this configuration: + +```json +// tsconfig.json or jsconfig.json +{ + "compilerOptions": { + "baseUrl": "." + } +} +``` + +```jsx +// components/button.js +export default function Button() { + return <button>Click me</button> +} +``` + +```jsx +// pages/index.js +import Button from 'components/button' + +export default function HomePage() { + return ( + <> + <h1>Hello World</h1> + <Button /> + </> + ) +} +``` + +While `baseUrl` is useful you might want to add other aliases that don't match 1 on 1. For this TypeScript has the `"paths"` option. + +Using `"paths"` allows you to configure module aliases. For example `@/components/*` to `components/*`. + +An example of this configuration: + +```json +// tsconfig.json or jsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/components/*": ["components/*"] + } + } +} +``` + +```jsx +// components/button.js +export default function Button() { + return <button>Click me</button> +} +``` + +```jsx +// pages/index.js +import Button from '@/components/button' + +export default function HomePage() { + return ( + <> + <h1>Hello World</h1> + <Button /> + </> + ) +} +``` diff --git a/docs/advanced-features/multi-zones.md b/docs/advanced-features/multi-zones.md index 2a9883a2541d5..7d288941b0cb9 100644 --- a/docs/advanced-features/multi-zones.md +++ b/docs/advanced-features/multi-zones.md @@ -1,9 +1,9 @@ # Multi Zones -<details> +<details open> <summary><b>Examples</b></summary> <ul> - <li><a href="/examples/with-zones">With Zones</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/with-zones">With Zones</a></li> </ul> </details> @@ -18,30 +18,13 @@ 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 add an [assetPrefix](/docs/api-reference/next.config.js/cdn-support-with-asset-prefix.md) to avoid conflicts with static files. +- Make sure to configure a [basePath](/docs/api-reference/next.config.js/basepath.md) to avoid conflicts with pages and static files. ## How to merge zones -You can merge zones using any HTTP proxy. - -For [ZEIT Now](https://zeit.co/now), you can use a single `now.json` to deploy both apps. It allows you to easily define routing routes for multiple apps like below: - -```json -{ - "version": 2, - "builds": [ - { "src": "blog/package.json", "use": "@now/next" }, - { "src": "home/package.json", "use": "@now/next" } - ], - "routes": [ - { "src": "/blog/_next(.*)", "dest": "blog/_next$1" }, - { "src": "/blog(.*)", "dest": "blog/blog$1" }, - { "src": "(.*)", "dest": "home$1" } - ] -} -``` - -You can also configure a proxy server to route using a set of routes like the ones above, e.g deploy the blog app to `https://blog.example.com` and the home app to `https://home.example.com` and then add a proxy server for both apps in `https://example.com`. +You can merge zones using [Rewrites](/docs/api-reference/next.config.js/rewrites.md) in one of the apps or any HTTP proxy. + +For [Vercel](https://vercel.com/), you can use a [monorepo](https://vercel.com/blog/monorepos) to deploy both apps. Check the [Monorepos blog post](https://vercel.com/blog/monorepos) for more details on how it works and our [`with-zones` example](https://github.com/vercel/next.js/tree/canary/examples/with-zones) for a detailed guide using multiple Next.js applications. diff --git a/docs/advanced-features/preview-mode.md b/docs/advanced-features/preview-mode.md index d5cc52607c57d..98d1ae6698cb8 100644 --- a/docs/advanced-features/preview-mode.md +++ b/docs/advanced-features/preview-mode.md @@ -6,11 +6,31 @@ description: Next.js has the preview mode for statically generated pages. You ca > This document is for Next.js versions 9.3 and up. If you’re using older versions of Next.js, refer to our [previous documentation](https://nextjs.org/docs/tag/v9.2.2/basic-features/pages). +<details open> + <summary><b>Examples</b></summary> + <ul> + <li><a href="/vercel/next.js/tree/canary/examples/cms-wordpress">WordPress Example</a> (<a href="https://next-blog-wordpress.vercel.app">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-datocms">DatoCMS Example</a> (<a href="https://next-blog-datocms.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-takeshape">TakeShape Example</a> (<a href="https://next-blog-takeshape.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-sanity">Sanity Example</a> (<a href="https://next-blog-sanity.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-prismic">Prismic Example</a> (<a href="https://next-blog-prismic.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-contentful">Contentful Example</a> (<a href="https://next-blog-contentful.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-strapi">Strapi Example</a> (<a href="https://next-blog-strapi.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-prepr">Prepr Example</a> (<a href="https://next-blog-prepr.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-agilitycms">Agility CMS Example</a> (<a href="https://next-blog-agilitycms.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-cosmic">Cosmic Example</a> (<a href="https://next-blog-cosmic.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-buttercms">ButterCMS Example</a> (<a href="https://next-blog-buttercms.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-storyblok">Storyblok Example</a> (<a href="https://next-blog-storyblok.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-graphcms">GraphCMS Example</a> (<a href="https://next-blog-graphcms.vercel.app/">Demo</a>)</li> + <li><a href="/vercel/next.js/tree/canary/examples/cms-kontent">Kontent Example</a> (<a href="https://next-blog-kontent.vercel.app//">Demo</a>)</li> + </ul> +</details> + In the [Pages documentation](/docs/basic-features/pages.md) and the [Data Fetching documentation](/docs/basic-features/data-fetching.md), we talked about how to pre-render a page at build time (**Static Generation**) using `getStaticProps` and `getStaticPaths`. -Static Generation is useful when your pages fetch data from a headless CMS. However, it’s not ideal when you’re writing a draft on your headless CMS and want to **preview** the draft immediately on your page. You’d want to Next.js to render these pages at **request time** instead of build time and fetch the draft content instead of the published content. You’d want Next.js to bypass Static Generation only for this specific case. +Static Generation is useful when your pages fetch data from a headless CMS. However, it’s not ideal when you’re writing a draft on your headless CMS and want to **preview** the draft immediately on your page. You’d want Next.js to render these pages at **request time** instead of build time and fetch the draft content instead of the published content. You’d want Next.js to bypass Static Generation only for this specific case. -Next.js has the feature called **Preview Mode** which solves this problem. Here’s an instruction on how to use it. +Next.js has a feature called **Preview Mode** which solves this problem. Here are instructions on how to use it. ## Step 1. Create and access a preview API route @@ -18,10 +38,10 @@ Next.js has the feature called **Preview Mode** which solves this problem. Here First, create a **preview API route**. It can have any name - e.g. `pages/api/preview.js` (or `.ts` if using TypeScript). -In this API route, you need to call `setPreviewData` on the response object. The argument for `setPreviewData` should be an object, and this can be used by `getStaticProps` (more on this later). For now, we’ll just use `{}`. +In this API route, you need to call `setPreviewData` on the response object. The argument for `setPreviewData` should be an object, and this can be used by `getStaticProps` (more on this later). For now, we’ll use `{}`. ```js -export default (req, res) => { +export default function handler(req, res) { // ... res.setPreviewData({}) // ... @@ -36,7 +56,7 @@ You can test this manually by creating an API route like below and accessing it // A simple example for testing it manually from your browser. // If this is located at pages/api/preview.js, then // open /api/preview from your browser. -export default (req, res) => { +export default function handler(req, res) { res.setPreviewData({}) res.end('Preview mode enabled') } @@ -93,8 +113,7 @@ export default async (req, res) => { // Redirect to the path from the fetched post // We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities - res.writeHead(307, { Location: post.slug }) - res.end() + res.redirect(post.slug) } ``` @@ -149,15 +168,6 @@ That’s it! If you access the preview API route (with `secret` and `slug`) from https://<your-site>/api/preview?secret=<token>&slug=<path> ``` -## More Examples - -Take a look at the following examples to learn more: - -- [DatoCMS Example](https://github.com/zeit/next.js/tree/canary/examples/cms-datocms) ([Demo](https://next-blog-datocms.now.sh/)) -- [TakeShape Example](https://github.com/zeit/next.js/tree/canary/examples/cms-takeshape) ([Demo](https://next-blog-takeshape.now.sh/)) -- [Sanity Example](https://github.com/zeit/next.js/tree/canary/examples/cms-sanity) ([Demo](https://next-blog-sanity.now.sh/)) -- [Prismic Example](https://github.com/zeit/next.js/tree/canary/examples/cms-prismic) ([Demo](https://next-blog-prismic.now.sh/)) - ## More Details ### Clear the preview mode cookies @@ -167,7 +177,7 @@ By default, no expiration date is set for the preview mode cookies, so the previ To clear the preview cookies manually, you can create an API route which calls `clearPreviewData` and then access this API route. ```js -export default (req, res) => { +export default function handler(req, res) { // Clears the preview mode cookies. // This function accepts no arguments. res.clearPreviewData() @@ -195,9 +205,24 @@ You can pass an object to `setPreviewData` and have it be available in `getStati The preview mode works on `getServerSideProps` as well. It will also be available on the `context` object containing `preview` and `previewData`. +### Works with API Routes + +API Routes will have access to `preview` and `previewData` under the request object. For example: + +```js +export default function myApiRoute(req, res) { + const isPreview = req.preview + const previewData = req.previewData + // ... +} +``` + ### Unique per `next build` -The bypass cookie value and private key for encrypting the `previewData` changes when a `next build` is ran, this ensures that the bypass cookie can’t be guessed. +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 diff --git a/docs/advanced-features/security-headers.md b/docs/advanced-features/security-headers.md new file mode 100644 index 0000000000000..c51718bdcb08e --- /dev/null +++ b/docs/advanced-features/security-headers.md @@ -0,0 +1,139 @@ +--- +description: Improve the security of your Next.js application by adding HTTP response headers. +--- + +# Security Headers + +To improve the security of your application, you can use [`headers`](/docs/api-reference/next.config.js/headers.md) in `next.config.js` to apply HTTP response headers to all routes in your application. + +```jsx +// next.config.js + +// You can choose which headers to add to the list +// after learning more below. +const securityHeaders = [] + +module.exports = { + async headers() { + return [ + { + // Apply these headers to all routes in your application. + source: '/(.*)', + headers: securityHeaders, + }, + ] + }, +} +``` + +## Options + +### [X-DNS-Prefetch-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control) + +This header controls DNS prefetching, allowing browsers to proactively perform domain name resolution on external links, images, CSS, JavaScript, and more. This prefetching is performed in the background, so the [DNS](https://developer.mozilla.org/en-US/docs/Glossary/DNS) is more likely to be resolved by the time the referenced items are needed. This reduces latency when the user clicks a link. + +```jsx +{ + key: 'X-DNS-Prefetch-Control', + value: 'on' +} +``` + +### [Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) + +This header informs browsers it should only be accessed using HTTPS, instead of using HTTP. Using the configuration below, all present and future subdomains will use HTTPS for a `max-age` of 2 years. This blocks access to pages or subdomains that can only be served over HTTP. + +If you're deploying to [Vercel](https://vercel.com/docs/edge-network/headers#strict-transport-security), this header is not necessary as it's automatically added to all deployments. + +```jsx +{ + key: 'Strict-Transport-Security', + value: 'max-age=63072000; includeSubDomains; preload' +} +``` + +### [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) + +This header stops pages from loading when they detect reflected cross-site scripting (XSS) attacks. Although this protection is not necessary when sites implement a strong [`Content-Security-Policy`](#content-security-policy) disabling the use of inline JavaScript (`'unsafe-inline'`), it can still provide protection for older web browsers that don't support CSP. + +```jsx +{ + key: 'X-XSS-Protection', + value: '1; mode=block' +} +``` + +### [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) + +This header indicates whether the site should be allowed to be displayed within an `iframe`. This can prevent against clickjacking attacks. This header has been superseded by CSP's `frame-ancestors` option, which has better support in modern browsers. + +```jsx +{ + key: 'X-Frame-Options', + value: 'SAMEORIGIN' +} +``` + +### [Permissions-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy) + +This header allows you to control which features and APIs can be used in the browser. It was previously named `Feature-Policy`. You can view the full list of permission options [here](https://www.w3.org/TR/permissions-policy-1/). + +```jsx +{ + key: 'Permissions-Policy', + value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()' +} +``` + +### [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) + +This header prevents the browser from attempting to guess the type of content if the `Content-Type` header is not explicitly set. This can prevent XSS exploits for websites that allow users to upload and share files. For example, a user trying to download an image, but having it treated as a different `Content-Type` like an executable, which could be malicious. This header also applies to downloading browser extensions. The only valid value for this header is `nosniff`. + +```jsx +{ + key: 'X-Content-Type-Options', + value: 'nosniff' +} +``` + +### [Referrer-Policy](https://scotthelme.co.uk/a-new-security-header-referrer-policy/) + +This header controls how much information the browser includes when navigating from the current website (origin) to another. You can read about the different options [here](https://scotthelme.co.uk/a-new-security-header-referrer-policy/). + +```jsx +{ + key: 'Referrer-Policy', + value: 'origin-when-cross-origin' +} +``` + +### [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) + +This header helps prevent cross-site scripting (XSS), clickjacking and other code injection attacks. Content Security Policy (CSP) can specify allowed origins for content including scripts, stylesheets, images, fonts, objects, media (audio, video), iframes, and more. + +You can read about the many different CSP options [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). + +```jsx +{ + key: 'Content-Security-Policy', + value: // Your CSP Policy +} +``` + +### References + +- [MDN](https://developer.mozilla.org) +- [Varun Naik](https://blog.vnaik.com/posts/web-attacks.html) +- [Scott Helme](https://scotthelme.co.uk) +- [Mozilla Observatory](https://observatory.mozilla.org/) + +## Related + +For more information, we recommend the following sections: + +<div class="card"> + <a href="/docs/api-reference/next.config.js/headers.md"> + <b>Headers:</b> + <small>Add custom HTTP headers to your Next.js app.</small> + </a> +</div> diff --git a/docs/advanced-features/source-maps.md b/docs/advanced-features/source-maps.md new file mode 100644 index 0000000000000..aa07ea4cf5982 --- /dev/null +++ b/docs/advanced-features/source-maps.md @@ -0,0 +1,23 @@ +--- +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 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: + +```js +// next.config.js +module.exports = { + productionBrowserSourceMaps: true, +} +``` + +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 + +- 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 4b2f09da7c1cf..c217b55d952ac 100644 --- a/docs/advanced-features/static-html-export.md +++ b/docs/advanced-features/static-html-export.md @@ -7,7 +7,7 @@ description: Export your Next.js app to static HTML, and run it standalone witho <details> <summary><b>Examples</b></summary> <ul> - <li><a href="/zeit/next.js/tree/canary/examples/with-static-export">Static Export</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/with-static-export">Static Export</a></li> </ul> </details> @@ -15,13 +15,17 @@ description: Export your Next.js app to static HTML, and run it standalone witho The exported app supports almost every feature of Next.js, including dynamic routes, prefetching, preloading and dynamic imports. -The way `next export` works is by prerendering all pages to HTML; it does so based on a mapping called [`exportPathMap`](/docs/api-reference/next.config.js/exportPathMap.md) which offers a way to pre-define paths you will render as html. +`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. -> If your pages don't have `getInitialProps` you may not need `next export` at all; `next build` is already enough thanks to [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md). +> `next export` is intended for scenarios where **none** of your pages have server-side or incremental data requirements (though statically-rendered pages can still [fetch data on the client side](/docs/basic-features/data-fetching.md#fetching-data-on-the-client-side)). +> +> If you're looking to make a hybrid site where only _some_ pages are prerendered to static HTML, Next.js already does that automatically for you! Read up on [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) for details. +> +> `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 -Simply develop your app as you normally do with Next.js. Then run: +Develop your app as you normally do with Next.js. Then run: ```bash next build && next export @@ -35,7 +39,7 @@ For that you may want to update the scripts in your `package.json` like this: } ``` -And run it at once with: +And run it with: ```bash npm run build @@ -43,16 +47,29 @@ npm run build Then you'll have a static version of your app in the `out` directory. -By default `next export` doesn't require any configuration. It will generate a default `exportPathMap` with routes for the pages inside the `pages` directory. - -> To learn more about `exportPathMap` please visit the [documentation for the `exportPathMap` API](/docs/api-reference/next.config.js/exportPathMap.md). +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 -You can read about deploying your Next.js application in the [deployment section](/docs/deployment.md). +By default, `next export` will generate an `out` directory, which can be served by any static hosting service or CDN. + +> 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. ## Caveats -- With `next export`, we build an HTML version of your app. At export time we will run the [`getInitialProps`](/docs/api-reference/data-fetching/getInitialProps.md) in your pages. The `req` and `res` fields of the [`context`](/docs/api-reference/data-fetching/getInitialProps.md#context-object) object will be empty objects during export as there is no server running. -- You won't be able to render HTML dynamically when static exporting, as we pre-build the HTML files. Your application can be a hybrid of [Static Generation](/docs/basic-features/pages.md#static-generation) and [Server-Side Rendering](/docs/basic-features/pages.md#server-side-rendering) when you don't use `next export`. You can learn more about it in the [pages section](/docs/basic-features/pages.md). +- 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` 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 recommended to use and migrate towards `getStaticProps` over `getInitialProps` whenever possible. + +- 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. diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md index 5b4de22e6301d..b79edb5f0bbce 100644 --- a/docs/api-reference/cli.md +++ b/docs/api-reference/cli.md @@ -21,7 +21,7 @@ Usage $ next <command> Available commands - build, start, export, dev, telemetry + build, start, export, dev, lint, telemetry Options --version, -v Version number @@ -38,3 +38,90 @@ NODE_OPTIONS='--throw-deprecation' next NODE_OPTIONS='-r esm' next NODE_OPTIONS='--inspect' next ``` + +## Build + +`next build` creates an optimized production build of your application. The output displays information about each route. + +- **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 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): + +```bash +next build --profile +``` + +After that, you can use the profiler in the same way as you would in development. + +You can enable more verbose build output with the `--debug` flag in `next build`. This requires Next.js 9.5.3: + +```bash +next build --debug +``` + +With this flag enabled additional build output like rewrites, redirects, and headers will be shown. + +## Development + +`next dev` starts the application in development mode with hot-code reloading, error reporting, and more: + +The application will start at `http://localhost:3000` by default. The default port can be changed with `-p`, like so: + +```bash +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. + +The application will start at `http://localhost:3000` by default. The default port can be changed with `-p`, like so: + +```bash +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. +Participation in this anonymous program is optional, and you may opt-out if you'd not like to share any information. + +To learn more about Telemetry, [please read this document](https://nextjs.org/telemetry/). diff --git a/docs/api-reference/create-next-app.md b/docs/api-reference/create-next-app.md new file mode 100644 index 0000000000000..1192865d09c00 --- /dev/null +++ b/docs/api-reference/create-next-app.md @@ -0,0 +1,65 @@ +--- +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 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 +# 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 --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. 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. +- **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`). +- **Tested**: The package is part of the Next.js monorepo and tested using the same integration test suite as Next.js itself, ensuring it works as expected with every release. + +## Related + +For more information on what to do next, we recommend the following sections: + +<div class="card"> + <a href="/docs/basic-features/pages.md"> + <b>Pages:</b> + <small>Learn more about what pages are in Next.js.</small> + </a> +</div> + +<div class="card"> + <a href="/docs/basic-features/built-in-css-support.md"> + <b>CSS Support:</b> + <small>Use the built-in CSS support to add custom styles to your app.</small> + </a> +</div> + +<div class="card"> + <a href="/docs/api-reference/cli.md"> + <b>CLI:</b> + <small>Learn more about the Next.js CLI.</small> + </a> +</div> diff --git a/docs/api-reference/data-fetching/getInitialProps.md b/docs/api-reference/data-fetching/getInitialProps.md index b20a2ab9f4f20..f46b34615f563 100644 --- a/docs/api-reference/data-fetching/getInitialProps.md +++ b/docs/api-reference/data-fetching/getInitialProps.md @@ -4,35 +4,25 @@ description: Enable Server-Side Rendering in a page and do initial data populati # getInitialProps -> **Recommended: [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) or [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering)** +> **Recommended: [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) or [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering)**. > > If you're using Next.js 9.3 or newer, we recommend that you use `getStaticProps` or `getServerSideProps` instead of `getInitialProps`. > -> These new data fetching methods allow you to have a granular choice between static generation and server-side rendering. -> Learn more on the documentation for [Pages](/docs/basic-features/pages.md) and [Data fetching](/docs/basic-features/data-fetching.md): - -<details> - <summary><b>Examples</b></summary> - <ul> - <li><a href="/zeit/next.js/tree/canary/examples/data-fetch">Data fetch</a></li> - </ul> -</details> +> These new data fetching methods allow you to have a granular choice between static generation and server-side rendering. Learn more on the documentation for [Pages](/docs/basic-features/pages.md) and [Data Fetching](/docs/basic-features/data-fetching.md). `getInitialProps` enables [server-side rendering](/docs/basic-features/pages.md#server-side-rendering) in a page and allows you to do **initial data population**, it means sending the [page](/docs/basic-features/pages.md) with the data already populated from the server. This is especially useful for [SEO](https://en.wikipedia.org/wiki/Search_engine_optimization). > `getInitialProps` will disable [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md). -`getInitialProps` is an [`async`](https://zeit.co/blog/async-and-await) function that can be added to any page as a [`static method`](https://javascript.info/static-properties-methods). Take a look at the following example: +`getInitialProps` is an [`async`](https://vercel.com/blog/async-and-await) function that can be added to any page as a [`static method`](https://javascript.info/static-properties-methods). Take a look at the following example: ```jsx -import fetch from 'isomorphic-unfetch' - function Page({ stars }) { return <div>Next stars: {stars}</div> } -Page.getInitialProps = async ctx => { - const res = await fetch('https://api.github.com/repos/zeit/next.js') +Page.getInitialProps = async (ctx) => { + const res = await fetch('https://api.github.com/repos/vercel/next.js') const json = await res.json() return { stars: json.stargazers_count } } @@ -44,11 +34,10 @@ Or using a class component: ```jsx import React from 'react' -import fetch from 'isomorphic-unfetch' class Page extends React.Component { static async getInitialProps(ctx) { - const res = await fetch('https://api.github.com/repos/zeit/next.js') + const res = await fetch('https://api.github.com/repos/vercel/next.js') const json = await res.json() return { stars: json.stargazers_count } } @@ -65,7 +54,7 @@ export default Page Data returned from `getInitialProps` is serialized when server rendering, similar to what [`JSON.stringify`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) does. Make sure the returned object from `getInitialProps` is a plain `Object` and not using `Date`, `Map` or `Set`. -For the initial page load, `getInitialProps` will execute on the server only. `getInitialProps` will only be executed on the client when navigating to a different route via the [`next/link`](/docs/api-reference/next/link.md) component or by using [`next/router`](/docs/api-reference/next/router.md). +For the initial page load, `getInitialProps` will run on the server only. `getInitialProps` will then run on the client when navigating to a different route via the [`next/link`](/docs/api-reference/next/link.md) component or by using [`next/router`](/docs/api-reference/next/router.md). However, if `getInitialProps` is used in a custom `_app.js`, and the page being navigated to implements `getServerSideProps`, then `getInitialProps` will run on the server. ## Context Object @@ -74,8 +63,8 @@ For the initial page load, `getInitialProps` will execute on the server only. `g - `pathname` - Current route. That is the path of the page in `/pages` - `query` - Query string section of URL parsed as an object - `asPath` - `String` of the actual path (including the query) shown in the browser -- `req` - HTTP request object (server only) -- `res` - HTTP response object (server only) +- `req` - [HTTP request object](https://nodejs.org/api/http.html#http_class_http_incomingmessage 'Class: http.IncomingMessage HTTP | Node.js v14.8.0 Documentation') (server only) +- `res` - [HTTP response object](https://nodejs.org/api/http.html#http_class_http_serverresponse 'Class: http.ServerResponse HTTP | Node.js v14.8.0 Documentation') (server only) - `err` - Error object if any error is encountered during the rendering ## Caveats diff --git a/docs/api-reference/next.config.js/basepath.md b/docs/api-reference/next.config.js/basepath.md new file mode 100644 index 0000000000000..0df919b35a057 --- /dev/null +++ b/docs/api-reference/next.config.js/basepath.md @@ -0,0 +1,79 @@ +--- +description: Learn more about setting a base path in Next.js +--- + +# Base Path + +<details> + <summary><b>Version History</b></summary> + +| Version | Changes | +| -------- | ---------------- | +| `v9.5.0` | Base Path added. | + +</details> + +To deploy a Next.js application under a sub-path of a domain you can use the `basePath` config option. + +`basePath` allows you to set a path prefix for the application. For example, to use `/docs` instead of `/` (the default), open `next.config.js` and add the `basePath` config: + +```js +module.exports = { + basePath: '/docs', +} +``` + +Note: this value must be set at build time and can not be changed without re-building as the value is inlined in the client-side bundles. + +## Links + +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`. + +```js +export default function HomePage() { + return ( + <> + <Link href="/about"> + <a>About Page</a> + </Link> + </> + ) +} +``` + +Output html: + +```html +<a href="/docs/about">About Page</a> +``` + +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 ( + <> + <h1>My Homepage</h1> + <Image + src="/docs/me.png" + alt="Picture of the author" + width={500} + height={500} + /> + <p>Welcome to my homepage!</p> + </> + ) +} + +export default Home +``` diff --git a/docs/api-reference/next.config.js/build-target.md b/docs/api-reference/next.config.js/build-target.md deleted file mode 100644 index fffd743340e9d..0000000000000 --- a/docs/api-reference/next.config.js/build-target.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -description: Learn more about the build targets used by Next.js, which decide the way your application is built and run. ---- - -# Build Target - -Next.js supports various build targets, each changing the way your application is built and run. We'll explain each of the targets below. - -## `server` target - -> This is the default target, however, we highly recommend the [`serverless` target](#serverless-target). The `serverless` target enforces [additional constraints](https://rauchg.com/2020/2019-in-review#serverless-upgrades-itself) to keep you in the [Pit of Success](https://blog.codinghorror.com/falling-into-the-pit-of-success/). - -This target is compatible with both `next start` and [custom server](/docs/advanced-features/custom-server.md) setups (it's mandatory for a custom server). - -Your application will be built and deployed as a monolith. This is the default target and no action is required on your part to opt-in. - -## `serverless` target - -> Deployments to [ZEIT Now](https://zeit.co) will automatically enable this target. You should not opt-into it yourself. - -This target will output independent pages that don't require a monolithic server. - -It's only compatible with `next start` or Serverless deployment platforms (like [ZEIT Now](https://zeit.co)) — you cannot use the custom server API. - -To opt-into this target, set the following configuration in your `next.config.js`: - -```js -module.exports = { - target: 'serverless', -} -``` - -## Related - -<div class="card"> - <a href="/docs/api-reference/next.config.js/introduction.md"> - <b>Introduction to next.config.js:</b> - <small>Learn more about the configuration file used by Next.js.</small> - </a> -</div> - -<div class="card"> - <a href="/docs/deployment.md"> - <b>Deployment:</b> - <small>Compile and deploy your Next.js app to production.</small> - </a> -</div> 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 7b6435c1eba30..20056521d6e78 100644 --- a/docs/api-reference/next.config.js/cdn-support-with-asset-prefix.md +++ b/docs/api-reference/next.config.js/cdn-support-with-asset-prefix.md @@ -4,6 +4,13 @@ description: A custom asset prefix allows you serve static assets from a CDN. Le # CDN Support with Asset Prefix +> **Attention**: [Deploying to Vercel](/docs/deployment.md) automatically configures a global CDN for your Next.js project. +> You do not need to manually setup an Asset Prefix. + +> **Note**: Next.js 9.5+ added support for a customizable [Base Path](/docs/api-reference/next.config.js/basepath.md), which is better +> suited for hosting your application on a sub-path like `/docs`. +> We do not suggest you use a custom Asset Prefix for this use case. + To set up a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network), you can set up an asset prefix and configure your CDN's origin to resolve to the domain that Next.js is hosted on. Open `next.config.js` and add the `assetPrefix` config: @@ -17,7 +24,25 @@ module.exports = { } ``` -Next.js will automatically use your prefix in the scripts it loads, but this has no effect whatsoever on 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. One way of introducing a prefix that works inside your components and varies by environment is documented [in this example](https://github.com/zeit/next.js/tree/canary/examples/with-universal-configuration-build-time). +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: + +``` +/_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. +- `/_next/data/` requests for `getStaticProps` pages. These requests will always be made against the main domain to support [Incremental Static Generation](/docs/basic-features/data-fetching.md#incremental-static-regeneration), even if you're not using it (for consistency). ## Related diff --git a/docs/api-reference/next.config.js/compression.md b/docs/api-reference/next.config.js/compression.md index c20ebe798e1c7..6947040d2ec01 100644 --- a/docs/api-reference/next.config.js/compression.md +++ b/docs/api-reference/next.config.js/compression.md @@ -4,7 +4,7 @@ description: Next.js provides gzip compression to compress rendered content and # Compression -Next.js provides [**gzip**](https://tools.ietf.org/html/rfc6713#section-3) compression to compress rendered content and static files. Compression only works with the [`server` target](/docs/api-reference/next.config.js/build-target.md#server-target). In general you will want to enable compression on a HTTP proxy like [nginx](https://www.nginx.com/), to offload load from the `Node.js` process. +Next.js provides [**gzip**](https://tools.ietf.org/html/rfc6713#section-3) compression to compress rendered content and static files. In general you will want to enable compression on a HTTP proxy like [nginx](https://www.nginx.com/), to offload load from the `Node.js` process. To disable **compression**, open `next.config.js` and disable the `compress` config: 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 54fc78f6f9ba3..793228d67fc24 100644 --- a/docs/api-reference/next.config.js/custom-page-extensions.md +++ b/docs/api-reference/next.config.js/custom-page-extensions.md @@ -4,16 +4,18 @@ description: Extend the default page extensions used by Next.js when resolving p # Custom Page Extensions -Aimed at modules like [@next/mdx](https://github.com/zeit/next.js/tree/canary/packages/next-mdx), which adds support for pages ending with `.mdx`. You can configure the extensions looked for in the `pages` directory when resolving pages. +Aimed at modules like [@next/mdx](https://github.com/vercel/next.js/tree/canary/packages/next-mdx), which adds support for pages ending with `.mdx`. You can configure the extensions looked for in the `pages` directory when resolving pages. 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. + ## Related <div class="card"> 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 5e0f74a565632..6b4535c7e80f5 100644 --- a/docs/api-reference/next.config.js/custom-webpack-config.md +++ b/docs/api-reference/next.config.js/custom-webpack-config.md @@ -4,28 +4,25 @@ description: Extend the default webpack config added by Next.js. # Custom Webpack Config +Before continuing to add custom webpack configuration to your application make sure Next.js doesn't already support your use-case: + +- [CSS imports](/docs/basic-features/built-in-css-support.md#adding-a-global-stylesheet) +- [CSS modules](/docs/basic-features/built-in-css-support.md#adding-component-level-css) +- [Sass/SCSS imports](/docs/basic-features/built-in-css-support.md#sass-support) +- [Sass/SCSS modules](/docs/basic-features/built-in-css-support.md#sass-support) +- [preact](https://github.com/vercel/next.js/tree/canary/examples/using-preact) +- [Customizing babel configuration](/docs/advanced-features/customizing-babel-config.md) + Some commonly asked for features are available as plugins: -- [@zeit/next-sass](https://github.com/zeit/next-plugins/tree/master/packages/next-sass) -- [@zeit/next-less](https://github.com/zeit/next-plugins/tree/master/packages/next-less) -- [@zeit/next-stylus](https://github.com/zeit/next-plugins/tree/master/packages/next-stylus) -- [@zeit/next-preact](https://github.com/zeit/next-plugins/tree/master/packages/next-preact) -- [@next/mdx](https://github.com/zeit/next.js/tree/canary/packages/next-mdx) -- [@next/bundle-analyzer](https://github.com/zeit/next.js/tree/canary/packages/next-bundle-analyzer) +- [@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) In order to extend our usage of `webpack`, you can define a function that extends its config inside `next.config.js`, like so: ```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 - // Important: return the modified config - config.plugins.push(new webpack.IgnorePlugin(/\/__tests__\//)) - return config - }, - webpackDevMiddleware: config => { - // Perform customizations to webpack dev middleware config // Important: return the modified config return config }, @@ -47,7 +44,7 @@ Example usage of `defaultLoaders.babel`: ```js // Example config for adding a loader that depends on babel-loader // This source was taken from the @next/mdx plugin source: -// https://github.com/zeit/next.js/tree/canary/packages/next-mdx +// https://github.com/vercel/next.js/tree/canary/packages/next-mdx module.exports = { webpack: (config, options) => { config.module.rules.push({ diff --git a/docs/api-reference/next.config.js/disabling-http-keep-alive.md b/docs/api-reference/next.config.js/disabling-http-keep-alive.md new file mode 100644 index 0000000000000..bfa79ad3d8b6d --- /dev/null +++ b/docs/api-reference/next.config.js/disabling-http-keep-alive.md @@ -0,0 +1,36 @@ +--- +description: Next.js will automatically use HTTP Keep-Alive by default. Learn more about how to disable HTTP Keep-Alive here. +--- + +# Disabling HTTP Keep-Alive + +Next.js automatically polyfills [node-fetch](/docs/basic-features/supported-browsers-features#polyfills) and enables [HTTP Keep-Alive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive) by default. You may want to disable HTTP Keep-Alive for certain `fetch()` calls or globally. + +For a single `fetch()` call, you can add the agent option: + +```js +import { Agent } from 'https' + +const url = 'https://example.com' +const agent = new Agent({ keepAlive: false }) +fetch(url, { agent }) +``` + +To override all `fetch()` calls globally, you can use `next.config.js`: + +```js +module.exports = { + httpAgentOptions: { + keepAlive: false, + }, +} +``` + +## Related + +<div class="card"> + <a href="/docs/api-reference/next.config.js/introduction.md"> + <b>Introduction to next.config.js:</b> + <small>Learn more about the configuration file used by Next.js.</small> + </a> +</div> diff --git a/docs/api-reference/next.config.js/disabling-x-powered-by.md b/docs/api-reference/next.config.js/disabling-x-powered-by.md index 1595d8e300116..29a0810f08eed 100644 --- a/docs/api-reference/next.config.js/disabling-x-powered-by.md +++ b/docs/api-reference/next.config.js/disabling-x-powered-by.md @@ -1,10 +1,10 @@ --- -description: Next.js will add `x-powered-by` to the request headers by default. Learn to opt-out of it here. +description: Next.js will add the `x-powered-by` header by default. Learn to opt-out of it here. --- # Disabling x-powered-by -By default Next.js will add `x-powered-by` to the request headers. To opt-out of it, open `next.config.js` and disable the `poweredByHeader` config: +By default Next.js will add the `x-powered-by` header. To opt-out of it, open `next.config.js` and disable the `poweredByHeader` config: ```js module.exports = { diff --git a/docs/api-reference/next.config.js/environment-variables.md b/docs/api-reference/next.config.js/environment-variables.md index 068d366f491d0..582f1449b60df 100644 --- a/docs/api-reference/next.config.js/environment-variables.md +++ b/docs/api-reference/next.config.js/environment-variables.md @@ -4,11 +4,12 @@ description: Learn to add and access environment variables in your Next.js appli # Environment Variables +> Since the release of [Next.js 9.4](https://nextjs.org/blog/next-9-4) we now have a more intuitive and ergonomic experience for [adding environment variables](/docs/basic-features/environment-variables.md). Give it a try! + <details> <summary><b>Examples</b></summary> <ul> - <li><a href="/zeit/next.js/tree/canary/examples/with-env-from-next-config-js">With env</a></li> - <li><a href="/zeit/next.js/tree/canary/examples/with-now-env">With Now env</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/with-env-from-next-config-js">With env</a></li> </ul> </details> @@ -54,3 +55,10 @@ return <h1>The value of customKey is: {'my-value'}</h1> <small>Learn more about the configuration file used by Next.js.</small> </a> </div> + +<div class="card"> + <a href="/docs/basic-features/environment-variables.md"> + <b>Built-in support for Environment Variables:</b> + <small>Learn more about the new support for environment variables.</small> + </a> +</div> diff --git a/docs/api-reference/next.config.js/exportPathMap.md b/docs/api-reference/next.config.js/exportPathMap.md index 449f00e038aae..6a5efe93f305c 100644 --- a/docs/api-reference/next.config.js/exportPathMap.md +++ b/docs/api-reference/next.config.js/exportPathMap.md @@ -9,10 +9,12 @@ description: Customize the pages that will be exported as HTML files when using <details> <summary><b>Examples</b></summary> <ul> - <li><a href="/zeit/next.js/tree/canary/examples/with-static-export">Static Export</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/with-static-export">Static Export</a></li> </ul> </details> +`exportPathMap` allows you to specify a mapping of request paths to page destinations, to be used during export. Paths defined in `exportPathMap` will also be available when using [`next dev`](/docs/api-reference/cli.md#development). + Let's start with an example, to create a custom `exportPathMap` for an app with the following pages: - `pages/index.js` @@ -23,7 +25,7 @@ Open `next.config.js` and add the following `exportPathMap` config: ```js module.exports = { - exportPathMap: async function( + exportPathMap: async function ( defaultPathMap, { dev, dir, outDir, distDir, buildId } ) { @@ -38,13 +40,15 @@ module.exports = { } ``` +Note: the `query` field in `exportPathMap` can not 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 can not 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: - `dev` - `true` when `exportPathMap` is being called in development. `false` when running `next export`. In development `exportPathMap` is used to define routes. - `dir` - Absolute path to the project directory -- `outDir` - Absolute path to the `out/` directory (configurable with `-o`). When `dev` is `true` the value of `outDir` will be `null`. +- `outDir` - Absolute path to the `out/` directory ([configurable with `-o`](#customizing-the-output-directory)). When `dev` is `true` the value of `outDir` will be `null`. - `distDir` - Absolute path to the `.next/` directory (configurable with the [`distDir`](/docs/api-reference/next.config.js/setting-a-custom-build-directory.md) config) - `buildId` - The generated build id @@ -59,14 +63,22 @@ The returned object is a map of pages where the `key` is the `pathname` and the It is possible to configure Next.js to export pages as `index.html` files and require trailing slashes, `/about` becomes `/about/index.html` and is routable via `/about/`. This was the default behavior prior to Next.js 9. -To switch back and add a trailing slash, open `next.config.js` and enable the `exportTrailingSlash` config: +To switch back and add a trailing slash, open `next.config.js` and enable the `trailingSlash` config: ```js module.exports = { - exportTrailingSlash: true, + trailingSlash: true, } ``` +## Customizing the output directory + +[`next export`](/docs/advanced-features/static-html-export.md#how-to-use-it) will use `out` as the default output directory, you can customize this using the `-o` argument, like so: + +```bash +next export -o outdir +``` + ## Related <div class="card"> diff --git a/docs/api-reference/next.config.js/headers.md b/docs/api-reference/next.config.js/headers.md new file mode 100644 index 0000000000000..d64c01c676b79 --- /dev/null +++ b/docs/api-reference/next.config.js/headers.md @@ -0,0 +1,390 @@ +--- +description: Add custom HTTP headers to your Next.js app. +--- + +# Headers + +<details open> + <summary><b>Examples</b></summary> + <ul> + <li><a href="/vercel/next.js/tree/canary/examples/headers">Headers</a></li> + </ul> +</details> + +<details> + <summary><b>Version History</b></summary> + +| Version | Changes | +| --------- | -------------- | +| `v10.2.0` | `has` added. | +| `v9.5.0` | Headers added. | + +</details> + +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`: + +```js +module.exports = { + async headers() { + return [ + { + source: '/about', + headers: [ + { + key: 'x-custom-header', + value: 'my custom header value', + }, + { + key: 'x-another-custom-header', + value: 'my other custom header value', + }, + ], + }, + ] + }, +} +``` + +`headers` is an async function that expects an array to be returned holding objects with `source` and `headers` properties: + +- `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 + +If two headers match the same path and set the same header key, the last header key will override the first. Using the below headers, the path `/hello` will result in the header `x-hello` being `world` due to the last header value set being `world`. + +```js +module.exports = { + async headers() { + return [ + { + source: '/:path*', + headers: [ + { + key: 'x-hello', + value: 'there', + }, + ], + }, + { + source: '/hello', + headers: [ + { + key: 'x-hello', + value: 'world', + }, + ], + }, + ], + }, +} +``` + +## Path Matching + +Path matches are allowed, for example `/blog/:slug` will match `/blog/hello-world` (no nested paths): + +```js +module.exports = { + async headers() { + return [ + { + source: '/blog/:slug', + headers: [ + { + key: 'x-slug', + value: ':slug', // Matched parameters can be used in the value + }, + { + key: 'x-slug-:slug', // Matched parameters can be used in the key + value: 'my other custom header value', + }, + ], + }, + ], + }, +} +``` + +### Wildcard Path Matching + +To match a wildcard path you can use `*` after a parameter, for example `/blog/:slug*` will match `/blog/a/b/c/d/hello-world`: + +```js +module.exports = { + async headers() { + return [ + { + source: '/blog/:slug*', + headers: [ + { + key: 'x-slug', + value: ':slug*', // Matched parameters can be used in the value + }, + { + key: 'x-slug-:slug*', // Matched parameters can be used in the key + value: 'my other custom header value', + }, + ], + }, + ], + }, +} +``` + +### Regex Path Matching + +To match a regex path you can wrap the regex in parenthesis after a parameter, for example `/blog/:slug(\\d{1,})` will match `/blog/123` but not `/blog/abc`: + +```js +module.exports = { + async headers() { + return [ + { + source: '/blog/:post(\\d{1,})', + headers: [ + { + key: 'x-post', + value: ':post', + }, + ], + }, + ], + }, +} +``` + +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-(?<paramName>.*)` 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. (?<page>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: '(?<authorized>yes|true)', + }, + ], + headers: [ + { + key: 'x-another-header', + value: ':authorized', + }, + ], + }, + // if the host is `example.com`, + // this header will be applied + { + source: '/:path*', + has: [ + { + type: 'host', + value: 'example.com', + }, + ], + headers: [ + { + key: 'x-another-header', + value: ':authorized', + }, + ], + }, + ] + }, +} +``` + +### Headers with basePath support + +When leveraging [`basePath` support](/docs/api-reference/next.config.js/basepath.md) with headers each `source` is automatically prefixed with the `basePath` unless you add `basePath: false` to the header: + +```js +module.exports = { + basePath: '/docs', + + async headers() { + return [ + { + source: '/with-basePath', // becomes /docs/with-basePath + headers: [ + { + key: 'x-hello', + value: 'world', + }, + ], + }, + { + source: '/without-basePath', // is not modified since basePath: false is set + headers: [ + { + key: 'x-hello', + value: 'world', + }, + ], + basePath: false, + }, + ] + }, +} +``` + +### Headers with i18n support + +When leveraging [`i18n` support](/docs/advanced-features/i18n-routing.md) with headers each `source` is automatically prefixed to handle the configured `locales` unless you add `locale: false` to the header. If `locale: false` is used you must prefix the `source` with a locale for it to be matched correctly. + +```js +module.exports = { + i18n: { + locales: ['en', 'fr', 'de'], + defaultLocale: 'en', + }, + + async headers() { + return [ + { + source: '/with-locale', // automatically handles all locales + headers: [ + { + key: 'x-hello', + value: 'world', + }, + ], + }, + { + // does not handle locales automatically since locale: false is set + source: '/nl/with-locale-manual', + locale: false, + headers: [ + { + key: 'x-hello', + value: 'world', + }, + ], + }, + { + // this matches '/' since `en` is the defaultLocale + source: '/en', + locale: false, + headers: [ + { + key: 'x-hello', + value: 'world', + }, + ], + }, + { + // this gets converted to /(en|fr|de)/(.*) so will not match the top-level + // `/` or `/fr` routes like /:path* would + source: '/(.*)', + headers: [ + { + key: 'x-hello', + value: 'worlld', + }, + ], + }, + ] + }, +} +``` + +### 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: + +<div class="card"> + <a href="/docs/advanced-features/security-headers.md"> + <b>Security Headers:</b> + <small>Improve the security of your Next.js application by add HTTP response headers.</small> + </a> +</div> diff --git a/docs/api-reference/next.config.js/ignoring-eslint.md b/docs/api-reference/next.config.js/ignoring-eslint.md new file mode 100644 index 0000000000000..650c678bfd072 --- /dev/null +++ b/docs/api-reference/next.config.js/ignoring-eslint.md @@ -0,0 +1,37 @@ +--- +description: Next.js reports ESLint errors and warnings during builds by default. Learn how to opt-out of this behavior here. +--- + +# Ignoring ESLint + +When ESLint is detected in your project, Next.js fails your **production build** (`next build`) when errors are present. + +If you'd like Next.js to produce production code even when your application has ESLint errors, you can disable the built-in linting step completely. This is not recommended unless you already have ESLint configured to run in a separate part of your workflow (for example, in CI or a pre-commit hook). + +Open `next.config.js` and enable the `ignoreDuringBuilds` option in the `eslint` config: + +```js +module.exports = { + eslint: { + // Warning: This allows production builds to successfully complete even if + // your project has ESLint errors. + ignoreDuringBuilds: true, + }, +} +``` + +## Related + +<div class="card"> + <a href="/docs/api-reference/next.config.js/introduction.md"> + <b>Introduction to next.config.js:</b> + <small>Learn more about the configuration file used by Next.js.</small> + </a> +</div> + +<div class="card"> + <a href="/docs/basic-features/eslint.md"> + <b>ESLint:</b> + <small>Get started with ESLint in Next.js.</small> + </a> +</div> diff --git a/docs/api-reference/next.config.js/ignoring-typescript-errors.md b/docs/api-reference/next.config.js/ignoring-typescript-errors.md index f9f5b6ba9e4f4..57335b30cf8ce 100644 --- a/docs/api-reference/next.config.js/ignoring-typescript-errors.md +++ b/docs/api-reference/next.config.js/ignoring-typescript-errors.md @@ -4,23 +4,9 @@ description: Next.js reports TypeScript errors by default. Learn to opt-out of t # Ignoring TypeScript Errors -Next.js reports TypeScript errors by default. If you don't want to leverage this behavior and prefer something else instead, like your editor's integration, you may want to disable it. +Next.js fails your **production build** (`next build`) when TypeScript errors are present in your project. -Open `next.config.js` and enable the `ignoreDevErrors` option in the `typescript` config: - -```js -module.exports = { - typescript: { - ignoreDevErrors: true, - }, -} -``` - -Next.js will still fail your **production build** (`next build`) when TypeScript errors are present in your project. - ---- - -If you'd like Next.js to dangerously produce production code even when your application has errors, you can also disable error reports for builds. +If you'd like Next.js to dangerously produce production code even when your application has errors, you can disable the built-in type checking step. > Be sure you are running type checks as part of your build or deploy process, otherwise this can be very dangerous. @@ -32,9 +18,6 @@ module.exports = { // !! WARN !! // Dangerously allow production builds to successfully complete even if // your project has type errors. - // - // This option is rarely needed, and should be reserved for advanced - // setups. You may be looking for `ignoreDevErrors` instead. // !! WARN !! ignoreBuildErrors: true, }, diff --git a/docs/api-reference/next.config.js/introduction.md b/docs/api-reference/next.config.js/introduction.md index e9fd3f681694f..02bf1c5904bbf 100644 --- a/docs/api-reference/next.config.js/introduction.md +++ b/docs/api-reference/next.config.js/introduction.md @@ -11,22 +11,31 @@ For custom advanced behavior of Next.js, you can create a `next.config.js` in th Take a look at the following `next.config.js` example: ```js -module.exports = { +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { /* config options here */ } + +module.exports = nextConfig ``` You can also use a function: ```js module.exports = (phase, { defaultConfig }) => { - return { + /** + * @type {import('next').NextConfig} + */ + const nextConfig = { /* config options here */ } + return nextConfig } ``` -`phase` is the current context in which the configuration is loaded. You can see the available phases [here](https://github.com/zeit/next.js/blob/canary/packages/next/next-server/lib/constants.ts#L1-L4). Phases can be imported from `next/constants`: +`phase` is the current context in which the configuration is loaded. You can see the [available phases](https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/constants.ts#L1-L4). Phases can be imported from `next/constants`: ```js const { PHASE_DEVELOPMENT_SERVER } = require('next/constants') @@ -44,8 +53,8 @@ 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/zeit/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. +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. > Avoid using new JavaScript features not available in your target Node.js version. `next.config.js` will not be parsed by Webpack, Babel or TypeScript. diff --git a/docs/api-reference/next.config.js/react-strict-mode.md b/docs/api-reference/next.config.js/react-strict-mode.md new file mode 100644 index 0000000000000..d442b81ac1ad0 --- /dev/null +++ b/docs/api-reference/next.config.js/react-strict-mode.md @@ -0,0 +1,29 @@ +--- +description: The complete Next.js runtime is now Strict Mode-compliant, learn how to opt-in +--- + +# React Strict Mode + +> **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`: + +```js +// next.config.js +module.exports = { + reactStrictMode: true, +} +``` + +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 `<React.StrictMode>`](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. + +## Related + +<div class="card"> + <a href="/docs/api-reference/next.config.js/introduction.md"> + <b>Introduction to next.config.js:</b> + <small>Learn more about the configuration file used by Next.js.</small> + </a> +</div> diff --git a/docs/api-reference/next.config.js/redirects.md b/docs/api-reference/next.config.js/redirects.md new file mode 100644 index 0000000000000..9d35b97c34506 --- /dev/null +++ b/docs/api-reference/next.config.js/redirects.md @@ -0,0 +1,294 @@ +--- +description: Add redirects to your Next.js app. +--- + +# Redirects + +<details open> + <summary><b>Examples</b></summary> + <ul> + <li><a href="/vercel/next.js/tree/canary/examples/redirects">Redirects</a></li> + </ul> +</details> + +<details> + <summary><b>Version History</b></summary> + +| Version | Changes | +| --------- | ---------------- | +| `v10.2.0` | `has` added. | +| `v9.5.0` | Redirects added. | + +</details> + +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. + +To use Redirects you can use the `redirects` key in `next.config.js`: + +```js +module.exports = { + async redirects() { + return [ + { + source: '/about', + destination: '/', + permanent: true, + }, + ] + }, +} +``` + +`redirects` is an async function that expects an array to be returned holding objects with `source`, `destination`, and `permanent` properties: + +- `source` is the incoming request path pattern. +- `destination` is the path you want to route to. +- `permanent` if the redirect is permanent or not. +- `basePath`: `false` or `undefined` - if false the basePath won't be included when matching, can be used for external rewrites only. +- `locale`: `false` or `undefined` - whether the locale should not be included when matching. +- `has` is an array of [has objects](#header-cookie-and-query-matching) with the `type`, `key` and `value` properties. + +Redirects are checked before the filesystem which includes pages and `/public` files. + +When a redirect is applied, any query values provided in the request will be passed through to the redirect destination. For example, see the following redirect configuration: + +```js +{ + source: '/old-blog/:path*', + destination: '/blog/:path*', + permanent: false +} +``` + +When `/old-blog/post-1?hello=world` is requested, the client will be redirected to `/blog/post-1?hello=world`. + +## Path Matching + +Path matches are allowed, for example `/old-blog/:slug` will match `/old-blog/hello-world` (no nested paths): + +```js +module.exports = { + async redirects() { + return [ + { + source: '/old-blog/:slug', + destination: '/news/:slug', // Matched parameters can be used in the destination + permanent: true, + }, + ] + }, +} +``` + +### Wildcard Path Matching + +To match a wildcard path you can use `*` after a parameter, for example `/blog/:slug*` will match `/blog/a/b/c/d/hello-world`: + +```js +module.exports = { + async redirects() { + return [ + { + source: '/blog/:slug*', + destination: '/news/:slug*', // Matched parameters can be used in the destination + permanent: true, + }, + ] + }, +} +``` + +### Regex Path Matching + +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 = { + async redirects() { + return [ + { + source: '/post/:slug(\\d{1,})', + destination: '/news/:slug', // Matched parameters can be used in the destination + permanent: false, + }, + ] + }, +} +``` + +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-(?<paramName>.*)` 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. (?<page>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: '(?<authorized>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', + }, + ], + 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: + +```js +module.exports = { + basePath: '/docs', + + async redirects() { + return [ + { + source: '/with-basePath', // automatically becomes /docs/with-basePath + destination: '/another', // automatically becomes /docs/another + permanent: false, + }, + { + // does not add /docs since basePath: false is set + source: '/without-basePath', + destination: '/another', + basePath: false, + permanent: false, + }, + ] + }, +} +``` + +### Redirects with i18n support + +When leveraging [`i18n` support](/docs/advanced-features/i18n-routing.md) with redirects each `source` and `destination` is automatically prefixed to handle the configured `locales` unless you add `locale: false` to the redirect. If `locale: false` is used you must prefix the `source` and `destination` with a locale for it to be matched correctly. + +```js +module.exports = { + i18n: { + locales: ['en', 'fr', 'de'], + defaultLocale: 'en', + }, + + async redirects() { + return [ + { + source: '/with-locale', // automatically handles all locales + destination: '/another', // automatically passes the locale on + permanent: false, + }, + { + // does not handle locales automatically since locale: false is set + source: '/nl/with-locale-manual', + destination: '/nl/another', + locale: false, + permanent: false, + }, + { + // this matches '/' since `en` is the defaultLocale + source: '/en', + destination: '/en/another', + 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 new file mode 100644 index 0000000000000..46c58eb6035dd --- /dev/null +++ b/docs/api-reference/next.config.js/rewrites.md @@ -0,0 +1,406 @@ +--- +description: Add rewrites to your Next.js app. +--- + +# Rewrites + +<details open> + <summary><b>Examples</b></summary> + <ul> + <li><a href="/vercel/next.js/tree/canary/examples/rewrites">Rewrites</a></li> + </ul> +</details> + +<details> + <summary><b>Version History</b></summary> + +| Version | Changes | +| --------- | --------------- | +| `v10.2.0` | `has` added. | +| `v9.5.0` | Rewrites added. | + +</details> + +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`: + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/about', + destination: '/', + }, + ] + }, +} +``` + +Rewrites are applied to client-side routing, a `<Link href="/about">` 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`: `String` - is the incoming request path pattern. +- `destination`: `String` is the path you want to route to. +- `basePath`: `false` or `undefined` - if false the basePath won't be included when matching, can be used for external rewrites only. +- `locale`: `false` or `undefined` - whether the locale should not be included when matching. +- `has` is an array of [has objects](#header-cookie-and-query-matching) with the `type`, `key` and `value` properties. + +Rewrites are applied after checking the filesystem (pages and `/public` files) and before dynamic routes by default. This behavior can be changed by instead returning an object instead of an array from the `rewrites` function since `v10.1` of Next.js: + +```js +module.exports = { + async rewrites() { + return { + beforeFiles: [ + // These rewrites are checked after headers/redirects + // and before all files including _next/public files which + // allows overriding page files + { + source: '/some-page', + destination: '/somewhere-else', + has: [{ type: 'query', key: 'overrideMe' }], + }, + ], + afterFiles: [ + // These rewrites are checked after pages/public files + // are checked but before dynamic routes + { + source: '/non-existent', + destination: '/somewhere-else', + }, + ], + fallback: [ + // These rewrites are checked after both pages/public files + // and dynamic routes are checked + { + source: '/:path*', + destination: `https://my-old-site.com/:path*`, + }, + ], + } + }, +} +``` + +Note: rewrites in `beforeFiles` do not check the filesystem/dynamic routes immediately after matching a source, they continue until all `beforeFiles` have been checked. + +The order Next.js routes are checked is: + +1. [headers](/docs/api-reference/next.config.js/headers) are checked/applied +2. [redirects](/docs/api-reference/next.config.js/redirects) are checked/applied +3. `beforeFiles` rewrites are checked/applied +4. static files from the [public directory](/docs/basic-features/static-file-serving), `_next/static` files, and non-dynamic pages are checked/served +5. `afterFiles` rewrites are checked/applied, if one of these rewrites is matched we check dynamic routes/static files after each match +6. `fallback` rewrites are checked/applied, these are applied before rendering the 404 page and after dynamic routes/all static assets have been checked. + +## Rewrite parameters + +When using parameters in a rewrite the parameters will be passed in the query by default when none of the parameters are used in the `destination`. + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/old-about/:path*', + destination: '/about', // The :path parameter isn't used here so will be automatically passed in the query + }, + ] + }, +} +``` + +If a parameter is used in the destination none of the parameters will be automatically passed in the query. + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/docs/:path*', + destination: '/:path*', // The :path parameter is used here so will not be automatically passed in the query + }, + ] + }, +} +``` + +You can still pass the parameters manually in the query if one is already used in the destination by specifying the query in the `destination`. + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/:first/:second', + destination: '/:first?second=:second', + // Since the :first parameter is used in the destination the :second parameter + // will not automatically be added in the query although we can manually add it + // as shown above + }, + ] + }, +} +``` + +## Path Matching + +Path matches are allowed, for example `/blog/:slug` will match `/blog/hello-world` (no nested paths): + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/blog/:slug', + destination: '/news/:slug', // Matched parameters can be used in the destination + }, + ] + }, +} +``` + +### Wildcard Path Matching + +To match a wildcard path you can use `*` after a parameter, for example `/blog/:slug*` will match `/blog/a/b/c/d/hello-world`: + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/blog/:slug*', + destination: '/news/:slug*', // Matched parameters can be used in the destination + }, + ] + }, +} +``` + +### Regex Path Matching + +To match a regex path you can wrap the regex in parenthesis after a parameter, for example `/blog/:slug(\\d{1,})` will match `/blog/123` but not `/blog/abc`: + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/old-blog/:post(\\d{1,})', + destination: '/blog/:post', // Matched parameters can be used in the destination + }, + ] + }, +} +``` + +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-(?<paramName>.*)` 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. (?<page>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: '(?<authorized>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 + +<details> + <summary><b>Examples</b></summary> + <ul> + <li><a href="/vercel/next.js/tree/canary/examples/custom-routes-proxying">Incremental adoption of Next.js</a></li> + </ul> +</details> + +Rewrites allow you to rewrite to an external url. This is especially useful for incrementally adopting Next.js. + +```js +module.exports = { + async rewrites() { + return [ + { + source: '/blog/:slug', + destination: 'https://example.com/blog/:slug', // Matched parameters can be used in the destination + }, + ] + }, +} +``` + +### Incremental adoption of Next.js + +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 { + 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: + +```js +module.exports = { + basePath: '/docs', + + async rewrites() { + return [ + { + source: '/with-basePath', // automatically becomes /docs/with-basePath + destination: '/another', // automatically becomes /docs/another + }, + { + // does not add /docs to /without-basePath since basePath: false is set + // Note: this can not be used for internal rewrites e.g. `destination: '/another'` + source: '/without-basePath', + destination: 'https://example.com', + basePath: false, + }, + ] + }, +} +``` + +### Rewrites with i18n support + +When leveraging [`i18n` support](/docs/advanced-features/i18n-routing.md) with rewrites each `source` and `destination` is automatically prefixed to handle the configured `locales` unless you add `locale: false` to the rewrite. If `locale: false` is used you must prefix the `source` and `destination` with a locale for it to be matched correctly. + +```js +module.exports = { + i18n: { + locales: ['en', 'fr', 'de'], + defaultLocale: 'en', + }, + + async rewrites() { + return [ + { + source: '/with-locale', // automatically handles all locales + destination: '/another', // automatically passes the locale on + }, + { + // does not handle locales automatically since locale: false is set + source: '/nl/with-locale-manual', + destination: '/nl/another', + locale: false, + }, + { + // this matches '/' since `en` is the defaultLocale + source: '/en', + 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/runtime-configuration.md b/docs/api-reference/next.config.js/runtime-configuration.md index 4a4a2a5ace6e1..27ed9919542b2 100644 --- a/docs/api-reference/next.config.js/runtime-configuration.md +++ b/docs/api-reference/next.config.js/runtime-configuration.md @@ -4,9 +4,7 @@ description: Add client and server runtime configuration to your Next.js app. # Runtime Configuration -> Generally you'll want to use [build-time environment variables](/docs/api-reference/next.config.js/environment-variables.md) to provide your configuration. The reason for this is that runtime configuration adds rendering / initialization overhead and is incompatible with [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md). - -> Runtime configuration is not available when using the [`serverless` target](/docs/api-reference/next.config.js/build-target.md#serverless-target). +> Generally you'll want to use [build-time environment variables](/docs/basic-features/environment-variables.md) to provide your configuration. The reason for this is that runtime configuration adds rendering / initialization overhead and is incompatible with [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md). To add runtime configuration to your app open `next.config.js` and add the `publicRuntimeConfig` and `serverRuntimeConfig` configs: @@ -34,6 +32,7 @@ To get access to the runtime configs in your app use `next/config`, like so: ```jsx import getConfig from 'next/config' +import Image from 'next/image' // Only holds serverRuntimeConfig and publicRuntimeConfig const { serverRuntimeConfig, publicRuntimeConfig } = getConfig() @@ -45,7 +44,11 @@ console.log(publicRuntimeConfig.staticFolder) function MyImage() { return ( <div> - <img src={`${publicRuntimeConfig.staticFolder}/logo.png`} alt="logo" /> + <Image + src={`${publicRuntimeConfig.staticFolder}/logo.png`} + alt="logo" + layout="fill" + /> </div> ) } diff --git a/docs/api-reference/next.config.js/static-optimization-indicator.md b/docs/api-reference/next.config.js/static-optimization-indicator.md index 7ef296f9c36f9..f8c512d388a44 100644 --- a/docs/api-reference/next.config.js/static-optimization-indicator.md +++ b/docs/api-reference/next.config.js/static-optimization-indicator.md @@ -4,6 +4,8 @@ description: Optimized pages include an indicator to let you know if it's being # Static Optimization Indicator +> **Note:** This indicator was removed in Next.js version 10.0.1. We recommend upgrading to the latest version of Next.js. + When a page qualifies for [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) we show an indicator to let you know. This is helpful since automatic static optimization can be very beneficial and knowing immediately in development if the page qualifies can be useful. @@ -17,19 +19,3 @@ module.exports = { }, } ``` - -## Related - -<div class="card"> - <a href="/docs/api-reference/next.config.js/introduction.md"> - <b>Introduction to next.config.js:</b> - <small>Learn more about the configuration file used by Next.js.</small> - </a> -</div> - -<div class="card"> - <a href="/docs/advanced-features/automatic-static-optimization.md"> - <b>Automatic Static Optimization:</b> - <small>Next.js automatically optimizes your app to be static HTML whenever possible. Learn how it works here.</small> - </a> -</div> diff --git a/docs/api-reference/next.config.js/trailing-slash.md b/docs/api-reference/next.config.js/trailing-slash.md new file mode 100644 index 0000000000000..ee2757f872ccc --- /dev/null +++ b/docs/api-reference/next.config.js/trailing-slash.md @@ -0,0 +1,35 @@ +--- +description: Configure Next.js pages to resolve with or without a trailing slash. +--- + +# Trailing Slash + +<details> + <summary><b>Version History</b></summary> + +| Version | Changes | +| -------- | --------------------- | +| `v9.5.0` | Trailing Slash added. | + +</details> + +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. + +Open `next.config.js` and add the `trailingSlash` config: + +```js +module.exports = { + trailingSlash: true, +} +``` + +With this option set, urls like `/about` will redirect to `/about/`. + +## Related + +<div class="card"> + <a href="/docs/api-reference/next.config.js/introduction.md"> + <b>Introduction to next.config.js:</b> + <small>Learn more about the configuration file used by Next.js.</small> + </a> +</div> diff --git a/docs/api-reference/next/amp.md b/docs/api-reference/next/amp.md index a5fd080b2fd5d..6cc212791168d 100644 --- a/docs/api-reference/next/amp.md +++ b/docs/api-reference/next/amp.md @@ -7,11 +7,11 @@ description: Enable AMP in a page, and control the way Next.js adds AMP to the p <details> <summary><b>Examples</b></summary> <ul> - <li><a href="/zeit/next.js/tree/canary/examples/amp">AMP</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/amp">AMP</a></li> </ul> </details> -> 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: @@ -22,7 +22,7 @@ export const config = { amp: true } The `amp` config accepts the following values: - `true` - The page will be AMP-only -- `'hybrid'` - The page will two versions, one with AMP and another one with HTML +- `'hybrid'` - The page will have two versions, one with AMP and another one with HTML To learn more about the `amp` config, read the sections below. @@ -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 961f18e2b5e00..63010ff4e0643 100644 --- a/docs/api-reference/next/head.md +++ b/docs/api-reference/next/head.md @@ -7,8 +7,8 @@ description: Add custom elements to the `head` of your page with the built-in He <details> <summary><b>Examples</b></summary> <ul> - <li><a href="/zeit/next.js/tree/canary/examples/head-elements">Head Elements</a></li> - <li><a href="/zeit/next.js/tree/canary/examples/layout-component">Layout Component</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/head-elements">Head Elements</a></li> + <li><a href="/vercel/next.js/tree/canary/examples/layout-component">Layout Component</a></li> </ul> </details> @@ -42,18 +42,10 @@ function IndexPage() { <div> <Head> <title>My page title - + - +

Hello world!

@@ -63,9 +55,11 @@ function IndexPage() { export default IndexPage ``` -In this case only the second `` is rendered. +In this case only the second `` is rendered. `meta` tags with duplicate `name` 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 ``, otherwise the tags won't be correctly picked up on client-side navigations. +`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 + + +
Home Page
+ + ) +} + +export default Home +``` + +### Useful Links + +- [Efficiently load third-party JavaScript](https://web.dev/efficiently-load-third-party-javascript/) diff --git a/errors/no-title-in-document-head.md b/errors/no-title-in-document-head.md new file mode 100644 index 0000000000000..771e99674d5c4 --- /dev/null +++ b/errors/no-title-in-document-head.md @@ -0,0 +1,30 @@ +# No Title in Document Head + +### Why This Error Occurred + +A `` element was defined within the `Head` component imported from `next/document`, which should only be used for any `<head>` code that is common for all pages. Title tags should be defined at the page-level using `next/head`. + +### Possible Ways to Fix It + +Within a page or component, import and use `next/head` to define a page title: + +```jsx +import Head from 'next/head' + +export class Home { + render() { + return ( + <div> + <Head> + <title>My page title + + + ) + } +} +``` + +### Useful links + +- [next/head](https://nextjs.org/docs/api-reference/next/head) +- [Custom Document](https://nextjs.org/docs/advanced-features/custom-document) diff --git a/errors/no-unwanted-polyfillio.md b/errors/no-unwanted-polyfillio.md new file mode 100644 index 0000000000000..6f05aa9a7251a --- /dev/null +++ b/errors/no-unwanted-polyfillio.md @@ -0,0 +1,13 @@ +# Duplicate Polyfills from Polyfill.io + +#### Why This Error Occurred + +You are using Polyfill.io and including duplicate polyfills already shipped with Next.js. This increases page weight unnecessarily which can affect loading performance. + +#### Possible Ways to Fix It + +Remove all duplicate polyfills that are included with Polyfill.io. If you need to add polyfills but are not sure if Next.js already includes it, take a look at the list of [supported browsers and features](https://nextjs.org/docs/basic-features/supported-browsers-features) first. + +### Useful Links + +- [Supported Browsers and Features](https://nextjs.org/docs/basic-features/supported-browsers-features) diff --git a/errors/non-dynamic-getstaticpaths-usage.md b/errors/non-dynamic-getstaticpaths-usage.md new file mode 100644 index 0000000000000..2f94c774b0cab --- /dev/null +++ b/errors/non-dynamic-getstaticpaths-usage.md @@ -0,0 +1,14 @@ +# getStaticPaths Used on Non-Dynamic Page + +#### Why This Error Occurred + +On a non-dynamic SSG page `getStaticPaths` was incorrectly exported as this can only be used on dynamic pages to return the paths to prerender. + +#### Possible Ways to Fix It + +Remove the `getStaticPaths` export on the non-dynamic page or rename the page to be a dynamic page. + +### Useful Links + +- [Dynamic Routes Documentation](https://nextjs.org/docs/routing/dynamic-routes) +- [`getStaticPaths` Documentation](https://nextjs.org/docs/routing/dynamic-routes) diff --git a/errors/non-standard-node-env.md b/errors/non-standard-node-env.md new file mode 100644 index 0000000000000..238823e622dd1 --- /dev/null +++ b/errors/non-standard-node-env.md @@ -0,0 +1,29 @@ +# Non-Standard NODE_ENV + +#### Why This Error Occurred + +Your environment has a non-standard `NODE_ENV` value configured. + +This may be by accident, so if you're unaware where the value is coming from, check the following: + +- The `.env*` files in your project, if present +- Your `~/.bash_profile`, if present +- Your `~/.zshrc`, if present + +The greater React ecosystem treats `NODE_ENV` as a convention, only permitting three (3) values: + +- `production`: When your application is built with `next build` +- `development`: When your application is ran with `next dev` +- `test`: When your application is being tested (e.g. `jest`) + +Setting a non-standard `NODE_ENV` value may cause dependencies to behave unexpectedly, or worse, **break dead code elimination**. + +#### Possible Ways to Fix It + +To fix this error, identify the source of the erroneous `NODE_ENV` value and get rid of it: Next.js automatically sets the correct value for you. + +If you need the concept of different environments in your application, e.g. `staging`, you should use a different environment variable name like `APP_ENV`. + +### Useful Links + +- [Environment Variables](https://en.wikipedia.org/wiki/Environment_variable) diff --git a/errors/opt-out-auto-static-optimization.md b/errors/opt-out-auto-static-optimization.md index d4445d444bee3..4c2115e84b957 100644 --- a/errors/opt-out-auto-static-optimization.md +++ b/errors/opt-out-auto-static-optimization.md @@ -2,16 +2,18 @@ #### Why This Warning Occurred -You are using `getInitialProps` in your [Custom ``](https://nextjs.org/docs#custom-app). +You are using `getInitialProps` in your [Custom ``](https://nextjs.org/docs/advanced-features/custom-app). -This causes **all non-getStaticProps pages** to be executed on the server -- disabling [Automatic Static Optimization](https://nextjs.org/docs#automatic-static-optimization). +This causes **all non-getStaticProps pages** to be executed on the server -- disabling [Automatic Static Optimization](https://nextjs.org/docs/advanced-features/automatic-static-optimization). #### Possible Ways to Fix It Be sure you meant to use `getInitialProps` in `pages/_app`! There are some valid use cases for this, but it is often better to handle `getInitialProps` on a _per-page_ basis. -If you previously copied the [Custom ``](https://nextjs.org/docs#custom-app) example, you may be able to remove your `getInitialProps`. +Check for any [higher-order components](https://reactjs.org/docs/higher-order-components.html) that may have added `getInitialProps` to your [Custom ``](https://nextjs.org/docs/advanced-features/custom-app). + +If you previously copied the [Custom ``](https://nextjs.org/docs/advanced-features/custom-app) example, you may be able to remove your `getInitialProps`. The following `getInitialProps` does nothing and may be removed: diff --git a/errors/opt-out-automatic-prerendering.md b/errors/opt-out-automatic-prerendering.md index afe98e98df4ef..f3db86439cc7f 100644 --- a/errors/opt-out-automatic-prerendering.md +++ b/errors/opt-out-automatic-prerendering.md @@ -2,16 +2,16 @@ #### Why This Warning Occurred -You are using `getInitialProps` in your [Custom ``](https://nextjs.org/docs#custom-app). +You are using `getInitialProps` in your [Custom ``](https://nextjs.org/docs/advanced-features/custom-app). -This causes **all pages** to be executed on the server -- disabling [Automatic Static Optimization](https://nextjs.org/docs#automatic-static-optimization). +This causes **all pages** to be executed on the server -- disabling [Automatic Static Optimization](https://nextjs.org/docs/advanced-features/automatic-static-optimization). #### Possible Ways to Fix It Be sure you meant to use `getInitialProps` in `pages/_app`! There are some valid use cases for this, but it is often better to handle `getInitialProps` on a _per-page_ basis. -If you previously copied the [Custom ``](https://nextjs.org/docs#custom-app) example, you may be able to remove your `getInitialProps`. +If you previously copied the [Custom ``](https://nextjs.org/docs/advanced-features/custom-app) example, you may be able to remove your `getInitialProps`. The following `getInitialProps` does nothing and may be removed: diff --git a/errors/page-data-collection-timeout.md b/errors/page-data-collection-timeout.md new file mode 100644 index 0000000000000..12332bfb430be --- /dev/null +++ b/errors/page-data-collection-timeout.md @@ -0,0 +1,18 @@ +# Collecting page data timed out after multiple attempts + +#### Why This Error Occurred + +Next.js tries to restart the worker pool of the page data collection when no progress happens for a while, to avoid hanging builds. + +When restarted it will retry all uncompleted jobs, but if a job was unsuccessfully attempted multiple times, this will lead to an error. + +#### Possible Ways to Fix It + +- Make sure that there is no infinite loop during execution. +- Make sure all Promises in `getStaticPaths` `resolve` or `reject` correctly. +- Avoid very long timeouts for network requests. +- Increase the timeout by changing the `experimental.pageDataCollectionTimeout` configuration option (default `60` in seconds). + +### Useful Links + +- [`getStaticPaths`](https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation) diff --git a/errors/page-without-valid-component.md b/errors/page-without-valid-component.md index 9344b712c9dbb..09d94fee00c35 100644 --- a/errors/page-without-valid-component.md +++ b/errors/page-without-valid-component.md @@ -13,4 +13,4 @@ For each, you'll want to check if the file is meant to be a page. If the file is not meant to be a page, and instead, is a shared component or file, move the file to a different folder like `components` or `lib`. -If the file is meant to be a page, double check you have an `export default` with the React Component instead of an `export`. If you're already using `export default`, make sure the returned valid is a valid React Component. +If the file is meant to be a page, double check you have an `export default` with the React Component instead of an `export`. If you're already using `export default`, make sure the returned value is a valid React Component. diff --git a/errors/placeholder-blur-data-url.md b/errors/placeholder-blur-data-url.md new file mode 100644 index 0000000000000..2fc53099c9830 --- /dev/null +++ b/errors/placeholder-blur-data-url.md @@ -0,0 +1,15 @@ +# `placeholder=blur` without `blurDataURL` + +#### Why This Error Occurred + +You are attempting use the `next/image` component with `placeholder=blur` property but no `blurDataURL` property. + +The `blurDataURL` might be missing because you're using a string for `src` instead of a static import. + +Or `blurDataURL` might be missing because the static import is an unsupported image format. Only jpg, png, and webp are supported at this time. + +#### Possible Ways to Fix It + +- Add a [`blurDataURL`](https://nextjs.org/docs/api-reference/next/image#blurdataurl) property, the contents should be a small Data URL to represent the image +- Change the [`src`](https://nextjs.org/docs/api-reference/next/image#src) property to a static import with one of the supported file types: jpg, png, or webp +- Remove the [`placeholder`](https://nextjs.org/docs/api-reference/next/image#placeholder) property, effectively no blur effect diff --git a/errors/popstate-state-empty.md b/errors/popstate-state-empty.md index 433adc2287a5a..ccd98713a557d 100644 --- a/errors/popstate-state-empty.md +++ b/errors/popstate-state-empty.md @@ -2,13 +2,13 @@ #### Why This Error Occurred -When using the browser back button the popstate event is triggered. Next.js sets -`popstate` event triggered but `event.state` did not have `url` or `as`, causing a route change failure +When using the browser back button the popstate event is triggered. Next.js sees a +`popstate` event being triggered but `event.state` did not have `url` or `as`, causing a route change failure. #### Possible Ways to Fix It -The only known cause of this issue is manually manipulating `window.history` instead of using `next/router` +The only known cause of this issue is manually manipulating `window.history` instead of using `next/router`. Starting from version 9.5, Next.js will ignore `popstate` events that contain `event.state` not created by its own router. ### Useful Links -- [The issue this was reported in: #4994](https://github.com/zeit/next.js/issues/4994) +- [The issue this was reported in: #4994](https://github.com/vercel/next.js/issues/4994) diff --git a/errors/postcss-ignored-plugin.md b/errors/postcss-ignored-plugin.md index 658ad57f28575..e8c73f1c69965 100644 --- a/errors/postcss-ignored-plugin.md +++ b/errors/postcss-ignored-plugin.md @@ -17,4 +17,4 @@ Remove the plugin specified in the error message from your custom PostCSS config #### How do I configure CSS Modules? CSS Modules are supported in [Next.js' built-in CSS support](https://nextjs.org/docs/advanced-features/customizing-postcss-config). -You can [read more](https://nextjs.org/docs/advanced-features/customizing-postcss-config) about how to use them [here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). +You can [read more about how to use CSS Modules here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). diff --git a/errors/postcss-shape.md b/errors/postcss-shape.md index 889fe55bcbc68..f121c889d04b1 100644 --- a/errors/postcss-shape.md +++ b/errors/postcss-shape.md @@ -39,7 +39,7 @@ module.exports = { } ``` -You can [read more](https://nextjs.org/docs/advanced-features/customizing-postcss-config) about configuring PostCSS in Next.js [here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). +You can [read more about configuring PostCSS in Next.js here](https://nextjs.org/docs/advanced-features/customizing-postcss-config). #### Common Errors diff --git a/errors/prefetch-true-deprecated.md b/errors/prefetch-true-deprecated.md index dbc5a2ed346f4..77733fea94726 100644 --- a/errors/prefetch-true-deprecated.md +++ b/errors/prefetch-true-deprecated.md @@ -18,4 +18,4 @@ These requests have low-priority and yield to fetch() or XHR requests. Next.js w The prefetch attribute is no longer needed, when set to true, example: `prefetch={true}`, remove the property. -Prefetching can be disabled with `prefetch={false}`. +Prefetching can be turned off with `prefetch={false}`. diff --git a/errors/prerender-error.md b/errors/prerender-error.md index 207671f546490..87e301c459db6 100644 --- a/errors/prerender-error.md +++ b/errors/prerender-error.md @@ -7,5 +7,7 @@ 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 +- Check for any code that assumes a prop is available even when it might not be. e.g., have default data for all dynamic pages' props. - Check for any out of date modules that you might be relying on +- Make sure your component handles `fallback` if it is enabled in `getStaticPaths`. [Fallback docs](https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required) +- Make sure you are not trying to export (`next export`) pages that have server-side rendering enabled [(getServerSideProps)](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) diff --git a/errors/production-start-no-build-id.md b/errors/production-start-no-build-id.md new file mode 100644 index 0000000000000..c6c9bfdb93f57 --- /dev/null +++ b/errors/production-start-no-build-id.md @@ -0,0 +1,10 @@ +# Could not find a production build + +#### Why This Error Occurred + +When running `next start` or a custom server in production mode a production build is needed. + +#### Possible Ways to Fix It + +- Run `next build` to create a production build before booting up the production server. +- If your intention was to run the development server run `next dev` instead. diff --git a/errors/promise-in-next-config.md b/errors/promise-in-next-config.md index 7fb48b3b00419..f39ce7786fc78 100644 --- a/errors/promise-in-next-config.md +++ b/errors/promise-in-next-config.md @@ -6,7 +6,7 @@ The webpack function in `next.config.js` returned a promise which is not support ```js module.exports = { - webpack: async function(config) { + webpack: async function (config) { return config }, } diff --git a/errors/react-version.md b/errors/react-version.md new file mode 100644 index 0000000000000..713254102491b --- /dev/null +++ b/errors/react-version.md @@ -0,0 +1,51 @@ +# Minimum React Version + +#### Why This Error Occurred + +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.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. + +For example, the following features are not enabled with old React versions: + +- [Fast Refresh](https://nextjs.org/docs/basic-features/fast-refresh): instantly + view edits to your app without losing component state +- Component stack trace in development: see the component tree that lead up to + an error +- Hydration mismatch warnings: trace down discrepancies in your React tree that + cause performance problems + +This list is not exhaustive, but illustrative in the value of upgrading React! + +#### Possible Ways to Fix It + +**Via npm** + +```bash +npm upgrade react@latest react-dom@latest +``` + +**Via Yarn** + +```bash +yarn add react@latest react-dom@latest +``` + +**Manually** Open your `package.json` and upgrade `react` and `react-dom`: + +```json +{ + "dependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + } +} +``` + +### Useful Links + +- [Fast Refresh blog post](https://nextjs.org/blog/next-9-4#fast-refresh) +- [Fast Refresh docs](https://nextjs.org/docs/basic-features/fast-refresh) diff --git a/errors/rewrite-auto-export-fallback.md b/errors/rewrite-auto-export-fallback.md new file mode 100644 index 0000000000000..a16fa7125639d --- /dev/null +++ b/errors/rewrite-auto-export-fallback.md @@ -0,0 +1,18 @@ +# Rewriting to Auto Export or Fallback Dynamic Route + +#### Why This Error Occurred + +One of your rewrites in your `next.config.js` point to a [dynamic route](https://nextjs.org/docs/routing/dynamic-routes) that is automatically statically optimized or is a [fallback SSG page](https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required). + +Rewriting to these pages are not yet supported since rewrites are not available client-side and the dynamic route params are unable to be parsed. Support for this may be added in a future release. + +#### Possible Ways to Fix It + +For fallback SSG pages you can add the page to the list of [prerendered paths](https://nextjs.org/docs/basic-features/data-fetching#the-paths-key-required). + +For static dynamic routes, you will currently need to either rewrite to non-dynamic route or opt the page out of the static optimization with [`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) + +### Useful Links + +- [Dynamic Routes Documentation](https://nextjs.org/docs/routing/dynamic-routes) +- [Fallback Documentation](https://nextjs.org/docs/basic-features/data-fetching#the-fallback-key-required) diff --git a/errors/routes-must-be-array.md b/errors/routes-must-be-array.md index fd4a909dc111c..59b1875653ec6 100644 --- a/errors/routes-must-be-array.md +++ b/errors/routes-must-be-array.md @@ -13,13 +13,11 @@ Make sure to return an array that contains the routes. ```js // next.config.js module.exports = { - experimental: { - async rewrites() { - return { - source: '/feedback', - destination: '/feedback/general', - } - }, + async rewrites() { + return { + source: '/feedback', + destination: '/feedback/general', + } }, } ``` @@ -28,15 +26,13 @@ module.exports = { ```js module.exports = { - experimental: { - async rewrites() { - return [ - { - source: '/feedback', - destination: '/feedback/general', - }, - ] - }, + async rewrites() { + return [ + { + source: '/feedback', + destination: '/feedback/general', + }, + ] }, } ``` diff --git a/errors/sharp-missing-in-production.md b/errors/sharp-missing-in-production.md new file mode 100644 index 0000000000000..01fc602b4b4e3 --- /dev/null +++ b/errors/sharp-missing-in-production.md @@ -0,0 +1,13 @@ +# Sharp Missing In Production + +#### Why This Error Occurred + +The `next/image` component's default loader uses the ['squoosh'](https://www.npmjs.com/package/@squoosh/lib) library for image resizing and optimization. This library is quick to install and suitable for a dev server environment. For a production environment, it is strongly recommended that you install the optional [`sharp`](https://www.npmjs.com/package/sharp). This package was not detected when leveraging the Image Optimization in production mode (`next start`). + +#### Possible Ways to Fix It + +Install `sharp` by running `yarn add sharp` in your project directory. + +### Useful Links + +- [Image Optimization Documentation](https://nextjs.org/docs/basic-features/image-optimization) diff --git a/errors/ssg-fallback-true-export.md b/errors/ssg-fallback-true-export.md new file mode 100644 index 0000000000000..9696edc817d71 --- /dev/null +++ b/errors/ssg-fallback-true-export.md @@ -0,0 +1,14 @@ +# SSG `fallback: true` Export Error + +#### Why This Error Occurred + +You attempted to export a page with a `fallback: true` return value from `getStaticPaths` which is invalid. `fallback: true` is meant for building pages on-demand after a build has occurred, exporting disables this functionality + +#### Possible Ways to Fix It + +If you would like the `fallback: true` behavior, `next export` should not be used. Instead follow the [deployment documentation](https://nextjs.org/docs/deployment) to deploy your incrementally generated static site. + +### Useful Links + +- [deployment documentation](https://nextjs.org/docs/deployment#vercel-recommended) +- [`fallback: true` documentation](https://nextjs.org/docs/basic-features/data-fetching#fallback-true) diff --git a/errors/static-dir-deprecated.md b/errors/static-dir-deprecated.md index 303e7311c8b48..76e13986aed9a 100644 --- a/errors/static-dir-deprecated.md +++ b/errors/static-dir-deprecated.md @@ -8,11 +8,11 @@ The reason we want to support a `public` directory instead is to not require the #### Possible Ways to Fix It -You can move your `static` directory inside of the `public` directory and all URLs will remain the same as they were before. +You can move your `static` directory inside of the `public` directory and all URLs will stay the same as they were before. **Before** -```sh +``` static/ my-image.jpg pages/ @@ -23,7 +23,7 @@ components/ **After** -```sh +``` public/ static/ my-image.jpg @@ -35,4 +35,4 @@ components/ ### Useful Links -- [Static file serving docs](https://nextjs.org/docs#static-file-serving-eg-images) +- [Static file serving docs](https://nextjs.org/docs/basic-features/static-file-serving) diff --git a/errors/static-page-generation-timeout.md b/errors/static-page-generation-timeout.md new file mode 100644 index 0000000000000..604e0364870f5 --- /dev/null +++ b/errors/static-page-generation-timeout.md @@ -0,0 +1,18 @@ +# 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 `getStaticProps` `resolve` or `reject` correctly. +- Avoid very long timeouts for network requests. +- Increase the timeout by changing the `experimental.staticPageGenerationTimeout` configuration option (default `60` in seconds). + +### Useful Links + +- [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) diff --git a/errors/template.md b/errors/template.md new file mode 100644 index 0000000000000..711da0c52abf2 --- /dev/null +++ b/errors/template.md @@ -0,0 +1,13 @@ +# + +#### Why This Error Occurred + + + +#### Possible Ways to Fix It + + + +### Useful Links + + diff --git a/errors/undefined-webpack-config.md b/errors/undefined-webpack-config.md new file mode 100644 index 0000000000000..fb15fdb731ba9 --- /dev/null +++ b/errors/undefined-webpack-config.md @@ -0,0 +1,28 @@ +# Missing webpack config + +#### Why This Error Occurred + +The value returned from the custom `webpack` function in your `next.config.js` was undefined. This can occur from the initial config value not being returned. + +#### Possible Ways to Fix It + +Make sure to return the `webpack` config from your custom `webpack` function in your `next.config.js` + +```js +// next.config.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 + }, +} +``` + +### Useful Links + +- [Custom webpack config Documentation](https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config) diff --git a/errors/url-deprecated.md b/errors/url-deprecated.md index d583128789469..df352d8e07068 100644 --- a/errors/url-deprecated.md +++ b/errors/url-deprecated.md @@ -10,9 +10,9 @@ 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 decending components. +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`. Here's an example of using `withRouter`: @@ -33,4 +33,4 @@ export default withRouter(Page) We provide a codemod (a code to code transformation) to automatically change the `url` property usages to `withRouter`. -You can find this codemod and instructions on how to run it here: https://github.com/zeit/next-codemod#url-to-withrouter +You can find this codemod and instructions on how to run it here: https://nextjs.org/docs/advanced-features/codemods#url-to-withrouter diff --git a/errors/webpack5.md b/errors/webpack5.md new file mode 100644 index 0000000000000..b8c994c549e3a --- /dev/null +++ b/errors/webpack5.md @@ -0,0 +1,45 @@ +# 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. For example Next.js now comes with both webpack 4 and 5 allowing you to adopt webpack 5 by adding a flag to your `next.config.js`: + +```js +module.exports = { + // Webpack 5 is enabled by default + // You can still use webpack 4 while upgrading to the latest version of Next.js by adding the "webpack5: false" flag + webpack5: false, +} +``` + +Using webpack 5 in your application has many benefits, notably: + +- Improved Disk Caching: `next build` is significantly faster on subsequent builds +- Improved Fast Refresh: Fast Refresh work is prioritized +- Improved Long Term Caching of Assets: Deterministic code output that is less likely to change between builds +- Improved Tree Shaking +- Support for assets using `new URL("file.png", import.meta.url)` +- Support for web workers using `new Worker(new URL("worker.js", import.meta.url))` +- Support for `exports`/`imports` field in `package.json` + +In the past releases we have gradually rolled out webpack 5 to Next.js applications: + +- In Next.js 10.2 we automatically opted-in applications without custom webpack configuration in `next.config.js` +- In Next.js 10.2 we automatically opted-in applications that do not have a `next.config.js` +- In Next.js 11 webpack 5 was enabled by default for all applications. You can still opt-out and use webpack 4 to help with backwards compatibility using `webpack5: false` in `next.config.js` + +In the next major version webpack 4 support will be removed. + +#### Custom webpack configuration + +In case you do have custom webpack configuration, either through custom plugins or your own modifications you'll have to take a few steps to ensure your applications works with webpack 5. + +- When using `next-transpile-modules` make sure you use the latest version which includes [this patch](https://github.com/martpie/next-transpile-modules/pull/179) +- When using `@zeit/next-css` / `@zeit/next-sass` make sure you use the [built-in CSS/Sass support](https://nextjs.org/docs/basic-features/built-in-css-support) instead +- When using `@zeit/next-preact` use [this example](https://github.com/vercel/next-plugins/tree/master/packages/next-preact) instead +- When using `@zeit/next-source-maps` use the [built-in production Source Map support](https://nextjs.org/docs/advanced-features/source-maps) +- When using webpack plugins make sure they're upgraded to the latest version, in most cases the latest version will include webpack 5 support. In some cases these upgraded webpack plugins will only support webpack 5. + +### Useful Links + +In case you're running into issues you can connect with the community in [this help discussion](https://github.com/vercel/next.js/discussions/23498). diff --git a/examples/active-class-name/.gitignore b/examples/active-class-name/.gitignore new file mode 100644 index 0000000000000..1437c53f70bc2 --- /dev/null +++ b/examples/active-class-name/.gitignore @@ -0,0 +1,34 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel diff --git a/examples/active-class-name/README.md b/examples/active-class-name/README.md index 115a0ed889305..902d79e24b7fe 100644 --- a/examples/active-class-name/README.md +++ b/examples/active-class-name/README.md @@ -2,41 +2,26 @@ 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`. -## Deploy your own - -Deploy the example using [ZEIT Now](https://zeit.co/now): +## Preview -[![Deploy with ZEIT Now](https://zeit.co/button)](https://zeit.co/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/active-class-name) - -## How to use +Preview the example live on [StackBlitz](http://stackblitz.com/): -### Using `create-next-app` +[![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) -Execute [`create-next-app`](https://github.com/zeit/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 -npm init next-app --example active-class-name active-class-name-app -# or -yarn create next-app --example active-class-name active-class-name-app -``` +## Deploy your own -### Download manually +Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): -Download the 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/active-class-name&project-name=active-class-name&repository-name=active-class-name) -```bash -curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/active-class-name -cd active-class-name -``` +## How to use -Install it and run: +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 -npm install -npm run dev +npx create-next-app --example active-class-name active-class-name-app # or -yarn -yarn dev +yarn create next-app --example active-class-name active-class-name-app ``` -Deploy it to the cloud with [ZEIT Now](https://zeit.co/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). +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/active-class-name/components/ActiveLink.js b/examples/active-class-name/components/ActiveLink.js index e9a87243d5baa..0db59b6a9227a 100644 --- a/examples/active-class-name/components/ActiveLink.js +++ b/examples/active-class-name/components/ActiveLink.js @@ -4,12 +4,15 @@ import Link from 'next/link' import React, { Children } from 'react' const ActiveLink = ({ children, activeClassName, ...props }) => { - const { pathname } = useRouter() + const { asPath } = useRouter() const child = Children.only(children) const childClassName = child.props.className || '' + // pages/index.js will be matched via props.href + // pages/about.js will be matched via props.href + // pages/[slug].js will be matched via props.as const className = - pathname === props.href + asPath === props.href || asPath === props.as ? `${childClassName} ${activeClassName}`.trim() : childClassName diff --git a/examples/active-class-name/components/Nav.js b/examples/active-class-name/components/Nav.js index 32e15a23466c2..2c710bb42468f 100644 --- a/examples/active-class-name/components/Nav.js +++ b/examples/active-class-name/components/Nav.js @@ -22,6 +22,11 @@ const Nav = () => ( About +
  • + + Dynamic Route + +
  • ) diff --git a/examples/active-class-name/package.json b/examples/active-class-name/package.json index feb47ede884f6..4f6934191388f 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" + "react": "^17.0.2", + "react-dom": "^17.0.2" }, - "license": "ISC" + "license": "MIT" } diff --git a/examples/active-class-name/pages/[slug].js b/examples/active-class-name/pages/[slug].js new file mode 100644 index 0000000000000..0f8f73e5888d0 --- /dev/null +++ b/examples/active-class-name/pages/[slug].js @@ -0,0 +1,14 @@ +import { useRouter } from 'next/router' +import Nav from '../components/Nav' + +const SlugPage = () => { + const { asPath } = useRouter() + return ( + <> +