diff --git a/.alexignore b/.alexignore
new file mode 100644
index 0000000000..1bf6581c26
--- /dev/null
+++ b/.alexignore
@@ -0,0 +1,2 @@
+CODE_OF_CONDUCT.md
+examples/
diff --git a/.alexrc b/.alexrc
new file mode 100644
index 0000000000..1f1de4b438
--- /dev/null
+++ b/.alexrc
@@ -0,0 +1,23 @@
+{
+ "allow": [
+ "attacks",
+ "color",
+ "dead",
+ "execute",
+ "executed",
+ "executes",
+ "execution",
+ "executions",
+ "failed",
+ "failure",
+ "failures",
+ "fire",
+ "fires",
+ "hook",
+ "hooks",
+ "host-hostess",
+ "invalid",
+ "remains",
+ "white"
+ ]
+}
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000000..7efa40b087
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,25 @@
+# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
+ARG VARIANT=16-bullseye
+FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
+
+# [Optional] Uncomment this section to install additional OS packages.
+# These are required to run playwright properly
+RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
+ && apt-get -y install --no-install-recommends \
+ gconf-service libxext6 libxfixes3 libxi6 libxrandr2 \
+ libxrender1 libcairo2 libcups2 libdbus-1-3 libexpat1 \
+ libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 \
+ libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 \
+ libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
+ libxdamage1 libxss1 libxtst6 libappindicator1 libnss3 libasound2 \
+ libatk1.0-0 libc6 libdrm-dev libgbm-dev ca-certificates fonts-liberation lsb-release xdg-utils wget
+
+# [Optional] Uncomment if you want to install an additional version of node using nvm
+# ARG EXTRA_NODE_VERSION=10
+# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
+
+# [Optional] Uncomment if you want to install more global node modules
+# RUN su node -c "npm install -g Some time: {date.toJSON()} Some time: {date.toJSON()}
+ {statusCode
+ ? `An error ${statusCode} occurred on server`
+ : 'An error occurred on client'}
+ Loading... Hello! This is a list in markdown: {post.content} Welcome to my homepage! Hello Hello, Next.js! Hello World Hello World Hello world! Hello world! Welcome to my homepage! Post: {pid} ... ...{function callbackForResult(e,p){if(e){d(e)}else if(!p){d(new Error("Unknown error"))}else{a(p)}}this.requestRawWithCallback(e,p,callbackForResult)}))}))}requestRawWithCallback(e,p,a){if(typeof p==="string"){if(!e.options.headers){e.options.headers={}}e.options.headers["Content-Length"]=Buffer.byteLength(p,"utf8")}let d=false;function handleResult(e,p){if(!d){d=true;a(e,p)}}const t=e.httpModule.request(e.options,(e=>{const p=new HttpClientResponse(e);handleResult(undefined,p)}));let r;t.on("socket",(e=>{r=e}));t.setTimeout(this._socketTimeout||3*6e4,(()=>{if(r){r.end()}handleResult(new Error(`Request timeout: ${e.options.path}`))}));t.on("error",(function(e){handleResult(e)}));if(p&&typeof p==="string"){t.write(p,"utf8")}if(p&&typeof p!=="string"){p.on("close",(function(){t.end()}));p.pipe(t)}else{t.end()}}getAgent(e){const p=new URL(e);return this._getAgent(p)}_prepareRequest(e,p,a){const d={};d.parsedUrl=p;const t=d.parsedUrl.protocol==="https:";d.httpModule=t?o:i;const r=t?443:80;d.options={};d.options.host=d.parsedUrl.hostname;d.options.port=d.parsedUrl.port?parseInt(d.parsedUrl.port):r;d.options.path=(d.parsedUrl.pathname||"")+(d.parsedUrl.search||"");d.options.method=e;d.options.headers=this._mergeHeaders(a);if(this.userAgent!=null){d.options.headers["user-agent"]=this.userAgent}d.options.agent=this._getAgent(d.parsedUrl);if(this.handlers){for(const e of this.handlers){e.prepareRequest(d.options)}}return d}_mergeHeaders(e){if(this.requestOptions&&this.requestOptions.headers){return Object.assign({},lowercaseKeys(this.requestOptions.headers),lowercaseKeys(e||{}))}return lowercaseKeys(e||{})}_getExistingOrDefaultHeader(e,p,a){let d;if(this.requestOptions&&this.requestOptions.headers){d=lowercaseKeys(this.requestOptions.headers)[p]}return e[p]||d||a}_getAgent(e){let p;const a=n.getProxyUrl(e);const d=a&&a.hostname;if(this._keepAlive&&d){p=this._proxyAgent}if(this._keepAlive&&!d){p=this._agent}if(p){return p}const t=e.protocol==="https:";let r=100;if(this.requestOptions){r=this.requestOptions.maxSockets||i.globalAgent.maxSockets}if(a&&a.hostname){const e={maxSockets:r,keepAlive:this._keepAlive,proxy:Object.assign(Object.assign({},(a.username||a.password)&&{proxyAuth:`${a.username}:${a.password}`}),{host:a.hostname,port:a.port})};let d;const s=a.protocol==="https:";if(t){d=s?l.httpsOverHttps:l.httpsOverHttp}else{d=s?l.httpOverHttps:l.httpOverHttp}p=d(e);this._proxyAgent=p}if(this._keepAlive&&!p){const e={keepAlive:this._keepAlive,maxSockets:r};p=t?new o.Agent(e):new i.Agent(e);this._agent=p}if(!p){p=t?o.globalAgent:i.globalAgent}if(t&&this._ignoreSslError){p.options=Object.assign(p.options||{},{rejectUnauthorized:false})}return p}_performExponentialBackoff(e){return s(this,void 0,void 0,(function*(){e=Math.min(w,e);const p=_*Math.pow(2,e);return new Promise((e=>setTimeout((()=>e()),p)))}))}_processResponse(e,p){return s(this,void 0,void 0,(function*(){return new Promise(((a,d)=>s(this,void 0,void 0,(function*(){const t=e.message.statusCode||0;const r={statusCode:t,result:null,headers:{}};if(t===m.NotFound){a(r)}function dateTimeDeserializer(e,p){if(typeof p==="string"){const e=new Date(p);if(!isNaN(e.valueOf())){return e}}return p}let s;let i;try{i=yield e.readBody();if(i&&i.length>0){if(p&&p.deserializeDates){s=JSON.parse(i,dateTimeDeserializer)}else{s=JSON.parse(i)}r.result=s}r.headers=e.message.headers}catch(e){}if(t>299){let e;if(s&&s.message){e=s.message}else if(i&&i.length>0){e=i}else{e=`Failed request: (${t})`}const p=new HttpClientError(e,t);p.result=r.result;d(p)}else{a(r)}}))))}))}}p.HttpClient=HttpClient;const lowercaseKeys=e=>Object.keys(e).reduce(((p,a)=>(p[a.toLowerCase()]=e[a],p)),{})},9835:(e,p)=>{Object.defineProperty(p,"__esModule",{value:true});p.checkBypass=p.getProxyUrl=void 0;function getProxyUrl(e){const p=e.protocol==="https:";if(checkBypass(e)){return undefined}const a=(()=>{if(p){return process.env["https_proxy"]||process.env["HTTPS_PROXY"]}else{return process.env["http_proxy"]||process.env["HTTP_PROXY"]}})();if(a){return new URL(a)}else{return undefined}}p.getProxyUrl=getProxyUrl;function checkBypass(e){if(!e.hostname){return false}const p=process.env["no_proxy"]||process.env["NO_PROXY"]||"";if(!p){return false}let a;if(e.port){a=Number(e.port)}else if(e.protocol==="http:"){a=80}else if(e.protocol==="https:"){a=443}const d=[e.hostname.toUpperCase()];if(typeof a==="number"){d.push(`${d[0]}:${a}`)}for(const e of p.split(",").map((e=>e.trim().toUpperCase())).filter((e=>e))){if(d.some((p=>p===e))){return true}}return false}p.checkBypass=checkBypass},334:(e,p)=>{Object.defineProperty(p,"__esModule",{value:true});const a=/^v1\./;const d=/^ghs_/;const t=/^ghu_/;async function auth(e){const p=e.split(/\./).length===3;const r=a.test(e)||d.test(e);const s=t.test(e);const i=p?"app":r?"installation":s?"user-to-server":"oauth";return{type:"token",token:e,tokenType:i}}function withAuthorizationPrefix(e){if(e.split(/\./).length===3){return`bearer ${e}`}return`token ${e}`}async function hook(e,p,a,d){const t=p.endpoint.merge(a,d);t.headers.authorization=withAuthorizationPrefix(e);return p(t)}const r=function createTokenAuth(e){if(!e){throw new Error("[@octokit/auth-token] No token passed to createTokenAuth")}if(typeof e!=="string"){throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string")}e=e.replace(/^(token|bearer) +/i,"");return Object.assign(auth.bind(null,e),{hook:hook.bind(null,e)})};p.createTokenAuth=r},6762:(e,p,a)=>{Object.defineProperty(p,"__esModule",{value:true});var d=a(5030);var t=a(3682);var r=a(6234);var s=a(8467);var i=a(334);function _objectWithoutPropertiesLoose(e,p){if(e==null)return{};var a={};var d=Object.keys(e);var t,r;for(r=0;r253||i.length===0){r.error=true}for(var o=0;o${groupKey}${groupTotalChange}
\n\n`
+ resultContent += groupTable
+ resultContent += `\nDiff for ${shortenLabel(
+ itemKey
+ )}
\n\n`
+
+ if (curDiff.length > 36 * 1000) {
+ diffContent += 'Diff too large to display'
+ } else {
+ diffContent += `\`\`\`diff\n${curDiff}\n\`\`\``
+ }
+ diffContent += `\n${result.title}${increaseDecreaseNote}
\n\n
\n\n`
+ comment += resultContent
+ 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 0000000000..7d199d9dd1
--- /dev/null
+++ b/.github/actions/next-stats-action/src/constants.js
@@ -0,0 +1,30 @@
+const path = require('path')
+const os = require('os')
+const fs = require('fs')
+
+const benchTitle = 'Page Load Tests'
+const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'next-stats'))
+const mainRepoDir = path.join(workDir, 'main-repo')
+const diffRepoDir = path.join(workDir, 'diff-repo')
+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,
+ 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 0000000000..23ea3882c5
--- /dev/null
+++ b/.github/actions/next-stats-action/src/index.js
@@ -0,0 +1,163 @@
+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)
+ }
+
+ if (actionInfo.isRelease) {
+ process.env.STATS_IS_RELEASE = 'true'
+ }
+
+ // 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)
+ let mainNextSwcVersion
+
+ if (!actionInfo.skipClone) {
+ if (actionInfo.isRelease) {
+ logger('Release detected, resetting mainRepo to last stable tag')
+ const lastStableTag = await getLastStable(mainRepoDir, actionInfo.prRef)
+ mainNextSwcVersion = lastStableTag
+ 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) {
+ const usePnpm = await fs.pathExists(path.join(dir, 'pnpm-lock.yaml'))
+
+ let buildCommand = `cd ${dir}${
+ !statsConfig.skipInitialInstall
+ ? usePnpm
+ ? // --no-frozen-lockfile is used here to tolerate lockfile
+ // changes from merging latest changes
+ ` && pnpm install --no-frozen-lockfile && pnpm run build`
+ : ' && 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 })
+ }
+
+ await fs
+ .copy(
+ path.join(__dirname, '../native'),
+ path.join(dir, 'packages/next-swc/native')
+ )
+ .catch(console.error)
+
+ logger(`Linking packages in ${dir}`)
+ const isMainRepo = dir === mainRepoDir
+ const pkgPaths = await linkPackages({
+ repoDir: dir,
+ nextSwcVersion: isMainRepo ? mainNextSwcVersion : undefined,
+ })
+
+ if (isMainRepo) 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 0000000000..83aa1deea8
--- /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 0000000000..55cecb54fa
--- /dev/null
+++ b/.github/actions/next-stats-action/src/prepare/load-stats-config.js
@@ -0,0 +1,44 @@
+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 (err) {
+ if (err.code !== 'MODULE_NOT_FOUND') {
+ console.error('Failed to load stats-config at', configPath, err)
+ }
+ /* */
+ }
+ }
+
+ 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 0000000000..21ad117508
--- /dev/null
+++ b/.github/actions/next-stats-action/src/prepare/repo-setup.js
@@ -0,0 +1,200 @@
+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')
+const execa = require('execa')
+
+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, nextSwcVersion }) {
+ let useTestPack = process.env.NEXT_TEST_PACK
+
+ if (useTestPack) {
+ execa.sync('pnpm', ['turbo', 'run', 'test-pack'], {
+ cwd: repoDir,
+ env: { NEXT_SWC_VERSION: nextSwcVersion },
+ })
+
+ const pkgPaths = new Map()
+ const pkgs = (await fs.readdir(path.join(repoDir, 'packages'))).filter(
+ (item) => !item.startsWith('.')
+ )
+
+ pkgs.forEach((pkgDirname) => {
+ const { name } = require(path.join(
+ repoDir,
+ 'packages',
+ pkgDirname,
+ 'package.json'
+ ))
+ pkgPaths.set(
+ name,
+ path.join(
+ repoDir,
+ 'packages',
+ pkgDirname,
+ `packed-${pkgDirname}.tgz`
+ )
+ )
+ })
+ return pkgPaths
+ } else {
+ // TODO: remove after next stable release (current v13.1.2)
+ 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') {
+ require('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')
+ if (!fs.existsSync(pkgDataPath)) {
+ require('console').log(`Skipping ${pkgDataPath}`)
+ continue
+ }
+ 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
+ }
+
+ // make sure native binaries are included in local linking
+ if (pkg === '@next/swc') {
+ if (!pkgData.files) {
+ pkgData.files = []
+ }
+ pkgData.files.push('native/*')
+ require('console').log(
+ 'using swc binaries: ',
+ await exec(`ls ${path.join(path.dirname(pkgDataPath), 'native')}`)
+ )
+ }
+
+ if (pkg === 'next') {
+ if (nextSwcVersion) {
+ Object.assign(pkgData.dependencies, {
+ '@next/swc-linux-x64-gnu': nextSwcVersion,
+ })
+ } else {
+ if (pkgDatas.get('@next/swc')) {
+ pkgData.dependencies['@next/swc'] =
+ pkgDatas.get('@next/swc').packedPkgPath
+ } else {
+ pkgData.files.push('native/*')
+ }
+ }
+ }
+
+ if (pkgData?.scripts?.prepublishOnly) {
+ // There's a bug in `pnpm pack` where it will run
+ // the prepublishOnly script and that will fail.
+ // See https://github.com/pnpm/pnpm/issues/2941
+ delete pkgData.scripts.prepublishOnly
+ }
+
+ 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
+ await Promise.all(
+ Array.from(pkgDatas.keys()).map(async (pkgName) => {
+ const { pkg, pkgPath, pkgData, packedPkgPath } =
+ pkgDatas.get(pkgName)
+ // Copied from pnpm source: https://github.com/pnpm/pnpm/blob/5a5512f14c47f4778b8d2b6d957fb12c7ef40127/releasing/plugin-commands-publishing/src/pack.ts#L96
+ const tmpTarball = path.join(
+ pkgPath,
+ `${pkgData.name.replace('@', '').replace('/', '-')}-${
+ pkgData.version
+ }.tgz`
+ )
+ await execa('pnpm', ['pack'], {
+ cwd: pkgPath,
+ })
+ await fs.copyFile(tmpTarball, packedPkgPath)
+ })
+ )
+
+ 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 0000000000..956ecefe9b
--- /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 an 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 0000000000..3440d8066b
--- /dev/null
+++ b/.github/actions/next-stats-action/src/run/collect-diffs.js
@@ -0,0 +1,116 @@
+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) {
+ const prettierPath = path.join(
+ __dirname,
+ '../../node_modules/.bin/prettier'
+ )
+ await exec(
+ `cd "${process.env.LOCAL_STATS ? process.cwd() : diffingDir}" && ` +
+ `${prettierPath} --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 0000000000..de50a50f88
--- /dev/null
+++ b/.github/actions/next-stats-action/src/run/collect-stats.js
@@ -0,0 +1,171 @@
+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 startTime = Date.now()
+ const child = spawn(statsConfig.appStartCommand, {
+ cwd: curDir,
+ env: {
+ PORT: port,
+ },
+ stdio: 'pipe',
+ })
+ let exitCode = null
+ let logStderr = true
+
+ let serverReadyResolve
+ let serverReadyResolved = false
+ const serverReadyPromise = new Promise((resolve) => {
+ serverReadyResolve = resolve
+ })
+
+ child.stdout.on('data', (data) => {
+ if (data.toString().includes('started server') && !serverReadyResolved) {
+ serverReadyResolved = true
+ serverReadyResolve()
+ }
+ process.stdout.write(data)
+ })
+ child.stderr.on('data', (data) => logStderr && process.stderr.write(data))
+
+ child.on('exit', (code) => {
+ if (!serverReadyResolved) {
+ serverReadyResolve()
+ serverReadyResolved = true
+ }
+ exitCode = code
+ })
+
+ await serverReadyPromise
+ if (!orderedStats['General']) {
+ orderedStats['General'] = {}
+ }
+ orderedStats['General']['nextStartReadyDuration (ms)'] =
+ Date.now() - startTime
+
+ 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.slice(0, -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 0000000000..aa16e51938
--- /dev/null
+++ b/.github/actions/next-stats-action/src/run/get-dir-size.js
@@ -0,0 +1,25 @@
+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) => {
+ // we use dev builds so the size isn't helpful to track
+ // here as it's not reflective of full releases
+ if (curDir.includes('@next/swc')) return
+
+ 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 0000000000..2f0e053c35
--- /dev/null
+++ b/.github/actions/next-stats-action/src/run/index.js
@@ -0,0 +1,216 @@
+const path = require('path')
+const fs = require('fs-extra')
+const getPort = require('get-port')
+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()
+ console.log(
+ 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]{16}(\.|-)/g, '$1HASH$2')
+ : rename.dest
+ if (result === dest) continue
+ await fs.move(
+ path.join(statsAppDir, result),
+ path.join(statsAppDir, dest)
+ )
+ }
+ }
+
+ const collectedStats = await collectStats(config, statsConfig)
+
+ for (const key of Object.keys(collectedStats)) {
+ curStats[key] = Object.assign({}, curStats[key], collectedStats[key])
+ }
+
+ 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()
+ console.log(
+ 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} && pnpm install --strict-peer-dependencies=false`,
+ 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 0000000000..689bd5b295
--- /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 0000000000..297e429897
--- /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 0000000000..695d1ec68d
--- /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/issue-labeler.yml b/.github/issue-labeler.yml
new file mode 100644
index 0000000000..cedfa239fa
--- /dev/null
+++ b/.github/issue-labeler.yml
@@ -0,0 +1,45 @@
+# https://github.com/github/issue-labeler#basic-examples
+
+# Should match the list "Which area(s) of Next.js are affected?" in:
+# https://github.com/vercel/next.js/blob/canary/.github/ISSUE_TEMPLATE/1.bug_report.yml
+'area: app': 'App directory (appDir: true)'
+'area: create-next-app': 'CLI (create-next-app)'
+'area: data fetching': 'Data fetching (gS(S)P, getInitialProps)'
+'area: Edge': 'Middleware / Edge (API routes, runtime)'
+'area: ESLint': 'ESLint (eslint-config-next)'
+'area: export': 'Static HTML Export (next export)'
+'area: Font Optimization': 'Font optimization (@next/font)'
+'area: I18n': 'Internationalzation (i18n)'
+'area: Jest': 'Jest (next/jest)'
+'area: MDX': 'MDX (@next/mdx)'
+'area: Metadata': 'Metadata (metadata, generateMetadata, next/head, head.js)'
+'area: next/dynamic': 'Dynamic imports (next/dynamic)'
+'area: next/image': 'Image optmization (next/image, next/legacy/image)'
+'area: next/script': 'Script optimizatzion (next/script)'
+'area: package manager': 'Package manager (npm, pnpm, Yarn)'
+'area: Routing': 'Routing (next/router, next/navigation, next/link)'
+'area: standalone mode': 'Standalone mode (output: "standalone")'
+'area: SWC Minify': 'SWC minifier (swcMinify: true)'
+'area: SWC transforms': 'SWC transpilation'
+'area: Turbopack': 'Turbopack (--turbo)'
+'area: TypeScript': 'TypeScript'
+# Less used/old/redundant labels
+# area: AMP
+# area: API routes
+# area: Application Code
+# area: Codemods
+# area: Compiler Performance
+# area: Compiler
+# area: Concurrent Features
+# area: Debugger
+# area: Developer Experience
+# area: Ecosystem
+# area: experimental
+# area: Middleware
+# area: Reliability
+# area: Repository Maintenance
+# area: Server Components
+# area: Static Generation
+# area: styled-jsx
+# area: Tracing
+# area: URL Imports
diff --git a/.github/labeler.json b/.github/labeler.json
new file mode 100644
index 0000000000..6bf909a239
--- /dev/null
+++ b/.github/labeler.json
@@ -0,0 +1,65 @@
+{
+ "labels": {
+ "area: create-next-app": ["packages/create-next-app/**"],
+ "area: documentation": ["docs/**", "errors/**"],
+ "area: examples": ["examples/**"],
+ "area: next/image": [
+ "**/*image*",
+ "**/*image*/**",
+ "packages/next/src/client/use-intersection.tsx",
+ "packages/next/src/server/lib/squoosh/",
+ "packages/next/src/server/serve-static.ts"
+ ],
+ "area: Font Optimization": ["**/*font*"],
+ "area: tests": ["test/**", "bench/**"],
+ "created-by: Chrome Aurora": [
+ { "type": "user", "pattern": "atcastle" },
+ { "type": "user", "pattern": "devknoll" },
+ { "type": "user", "pattern": "housseindjirdeh" },
+ { "type": "user", "pattern": "janicklas-ralph" },
+ { "type": "user", "pattern": "kara" },
+ { "type": "user", "pattern": "kyliau" },
+ { "type": "user", "pattern": "spanicker" }
+ ],
+ "created-by: Next.js team": [
+ { "type": "user", "pattern": "acdlite" },
+ { "type": "user", "pattern": "balazsorban44" },
+ { "type": "user", "pattern": "Brooooooklyn" },
+ { "type": "user", "pattern": "feedthejim" },
+ { "type": "user", "pattern": "ForsakenHarmony" },
+ { "type": "user", "pattern": "gnoff" },
+ { "type": "user", "pattern": "hanneslund" },
+ { "type": "user", "pattern": "huozhi" },
+ { "type": "user", "pattern": "ijjk" },
+ { "type": "user", "pattern": "JanKaifer" },
+ { "type": "user", "pattern": "kdy1" },
+ { "type": "user", "pattern": "kwonoj" },
+ { "type": "user", "pattern": "leerob" },
+ { "type": "user", "pattern": "padmaia" },
+ { "type": "user", "pattern": "sebmarkbage" },
+ { "type": "user", "pattern": "shuding" },
+ { "type": "user", "pattern": "sokra" },
+ { "type": "user", "pattern": "styfle" },
+ { "type": "user", "pattern": "timneutkens" },
+ { "type": "user", "pattern": "wyattjoh" }
+ ],
+ "created-by: Next.js docs team": [
+ { "type": "user", "pattern": "ismaelrumzan" },
+ { "type": "user", "pattern": "MaedahBatool" },
+ { "type": "user", "pattern": "molebox" }
+ ],
+ "type: next": [
+ "packages/eslint-config-next/**",
+ "packages/eslint-plugin-next/**",
+ "packages/font/**",
+ "packages/next-bundle-analyzer/**",
+ "packages/next-codemod/**",
+ "packages/next-env/**",
+ "packages/next-mdx/**",
+ "packages/next-swc/**",
+ "packages/next/**",
+ "packages/react-dev-overlay/**",
+ "packages/react-refresh-utils/**"
+ ]
+ }
+}
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000000..b898a90279
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,45 @@
+
diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml
new file mode 100644
index 0000000000..01290fe933
--- /dev/null
+++ b/.github/workflows/build_test_deploy.yml
@@ -0,0 +1,1652 @@
+on:
+ push:
+ branches: [canary]
+ pull_request:
+ types: [opened, synchronize]
+
+name: Build, test, and deploy
+
+env:
+ NAPI_CLI_VERSION: 2.14.7
+ TURBO_VERSION: 1.6.3
+ RUST_TOOLCHAIN: nightly-2023-03-09
+ PNPM_VERSION: 7.24.3
+ NODE_MAINTENANCE_VERSION: 16
+ NODE_LTS_VERSION: 18
+
+jobs:
+ check-examples:
+ name: Check examples
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install moreutils
+ run: sudo apt install moreutils
+ - name: Check examples
+ run: ./scripts/check-examples.sh
+
+ build:
+ runs-on: ubuntu-latest
+ env:
+ NEXT_TELEMETRY_DISABLED: 1
+ # we build a dev binary for use in CI so skip downloading
+ # canary next-swc binaries in the monorepo
+ NEXT_SKIP_NATIVE_POSTINSTALL: 1
+ TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
+ outputs:
+ docsChange: ${{ steps.docs-change.outputs.DOCS_CHANGE }}
+ isRelease: ${{ steps.check-release.outputs.IS_RELEASE }}
+ swcChange: ${{ steps.swc-change.outputs.SWC_CHANGE }}
+ turboToken: ${{ steps.turbo-token.outputs.TURBO_TOKEN }}
+ weekNum: ${{ steps.get-week.outputs.WEEK }}
+ steps:
+ - name: Setup node
+ uses: actions/setup-node@v3
+ if: ${{ steps.docs-change.outputs.docsChange == 'nope' }}
+ with:
+ node-version: 16
+ check-latest: true
+
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 25
+
+ # https://github.com/actions/virtual-environments/issues/1187
+ - name: tune linux network
+ run: sudo ethtool -K eth0 tx off rx off
+
+ - name: Check non-docs only change
+ run: echo "DOCS_CHANGE<{items()}
+}
+
+const items = () => {
+ var out = new Array(10000)
+ for (let i = 0; i < out.length; i++) {
+ out[i] = My component!
diff --git a/bench/rendering/readme.md b/bench/rendering/readme.md
new file mode 100644
index 0000000000..71c7070d8f
--- /dev/null
+++ b/bench/rendering/readme.md
@@ -0,0 +1,29 @@
+# Next.js server-side benchmarks
+
+## Installation
+
+Follow the steps in [contributing.md](../contributing.md)
+
+Both benchmarks use `ab`. So make sure you have that installed.
+
+## Usage
+
+Before running the test:
+
+```
+npm run start
+```
+
+Then run one of these tests:
+
+- Stateless application which renders `My component!
`. Runs 3000 http requests.
+
+```
+npm run bench:stateless
+```
+
+- Stateless application which renders `` element, and finally the URL pathname. For the most accessible user experience, ensure that each page in your application has a unique and descriptive title.
+
+## Linting
+
+Next.js provides an [integrated ESLint experience](/docs/basic-features/eslint.md) out of the box, including custom rules for Next.js. By default, Next.js includes `eslint-plugin-jsx-a11y` to help catch accessibility issues early, including warning on:
+
+- [aria-props](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/aria-props.md?rgh-link-date=2021-06-04T02%3A10%3A36Z)
+- [aria-proptypes](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/aria-proptypes.md?rgh-link-date=2021-06-04T02%3A10%3A36Z)
+- [aria-unsupported-elements](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/aria-unsupported-elements.md?rgh-link-date=2021-06-04T02%3A10%3A36Z)
+- [role-has-required-aria-props](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/role-has-required-aria-props.md?rgh-link-date=2021-06-04T02%3A10%3A36Z)
+- [role-supports-aria-props](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/role-supports-aria-props.md?rgh-link-date=2021-06-04T02%3A10%3A36Z)
+
+For example, this plugin helps ensure you add alt text to `img` tags, use correct `aria-*` attributes, use correct `role` attributes, and more.
+
+## Disabling JavaScript
+
+By default, Next.js prerenders pages to static HTML files. This means that JavaScript is not required to view the HTML markup from the server and is instead used to add interactivity on the client side.
+
+If your application requires JavaScript to be disabled, and only HTML to be used, you can remove all JavaScript from your application using an experimental flag:
+
+```js
+// next.config.js
+export const config = {
+ unstable_runtimeJS: false,
+}
+```
+
+## Accessibility Resources
+
+- [WebAIM WCAG checklist](https://webaim.org/standards/wcag/checklist)
+- [WCAG 2.1 Guidelines](https://www.w3.org/TR/WCAG21/)
+- [The A11y Project](https://www.a11yproject.com/)
+- Check [color contrast ratios](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Perceivable/Color_contrast) between foreground and background elements
+- Use [`prefers-reduced-motion`](https://web.dev/prefers-reduced-motion/) when working with animations
diff --git a/docs/advanced-features/amp-support/adding-amp-components.md b/docs/advanced-features/amp-support/adding-amp-components.md
new file mode 100644
index 0000000000..63305791d5
--- /dev/null
+++ b/docs/advanced-features/amp-support/adding-amp-components.md
@@ -0,0 +1,70 @@
+---
+description: Add components from the AMP community to AMP pages, and make your pages more interactive.
+---
+
+# Adding AMP Components
+
+The AMP community provides [many components](https://amp.dev/documentation/components/) to make AMP pages more interactive. Next.js will automatically import all components used on a page and there is no need to manually import AMP component scripts:
+
+```jsx
+export const config = { amp: true }
+
+function MyAmpPage() {
+ const date = new Date()
+
+ return (
+
` component used here should only be used for any `` code that is common for all pages. For all other cases, such as `Examples
+
+
+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@latest 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 Version History
+
+| Version | Changes |
+| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `v13.1.0` | [Module Transpilation](https://nextjs.org/blog/next-13-1#built-in-module-transpilation-stable) and [Modularize Imports](https://nextjs.org/blog/next-13-1#import-resolution-for-smaller-bundles) stable. |
+| `v13.0.0` | SWC Minifier enabled by default. |
+| `v12.3.0` | SWC Minifier [stable](https://nextjs.org/blog/next-12-3#swc-minifier-stable). |
+| `v12.2.0` | [SWC Plugins](#swc-plugins-Experimental) experimental support added. |
+| `v12.1.0` | Added support for Styled Components, Jest, Relay, Remove React Properties, Legacy Decorators, Remove Console, and jsxImportSource. |
+| `v12.0.0` | Next.js Compiler [introduced](https://nextjs.org/blog/next-12). |
+
+Examples
+
+404 - Page Not Found
+}
+```
+
+> **Note**: You can use [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) inside this page if you need to fetch data at build time.
+
+## 500 Page
+
+Server-rendering an error page for every visit adds complexity to responding to errors. To help users get responses to errors as fast as possible, Next.js provides a static 500 page by default without having to add any additional files.
+
+### Customizing The 500 Page
+
+To customize the 500 page you can create a `pages/500.js` file. This file is statically generated at build time.
+
+```jsx
+// pages/500.js
+export default function Custom500() {
+ return 500 - Server-side error occurred
+}
+```
+
+> **Note**: You can use [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) 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:
+
+```jsx
+function Error({ statusCode }) {
+ return (
+ Examples
+
+
+Examples
+
+Examples
+
+Click to view the configuration to enable CSS Grid Layout
+
+```json
+{
+ "plugins": [
+ "postcss-flexbugs-fixes",
+ [
+ "postcss-preset-env",
+ {
+ "autoprefixer": {
+ "flexbox": "no-2009",
+ "grid": "autoplace"
+ },
+ "stage": 3,
+ "features": {
+ "custom-properties": false
+ }
+ }
+ ]
+ ]
+}
+```
+
+
+
+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/).
+
+## Customizing Target Browsers
+
+Next.js allows you to configure the target browsers (for [Autoprefixer](https://github.com/postcss/autoprefixer) and compiled css features) through [Browserslist](https://github.com/browserslist/browserslist).
+
+To customize browserslist, create a `browserslist` key in your `package.json` like so:
+
+```json
+{
+ "browserslist": [">0.3%", "not dead", "not op_mini all"]
+}
+```
+
+You can use the [browsersl.ist](https://browsersl.ist/?q=%3E0.3%25%2C+not+ie+11%2C+not+dead%2C+not+op_mini+all) tool to visualize what browsers you are targeting.
+
+## CSS Modules
+
+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.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.
+
+This is the default configuration used by Next.js:
+
+```json
+{
+ "plugins": [
+ "postcss-flexbugs-fixes",
+ [
+ "postcss-preset-env",
+ {
+ "autoprefixer": {
+ "flexbox": "no-2009"
+ },
+ "stage": 3,
+ "features": {
+ "custom-properties": false
+ }
+ }
+ ]
+ ]
+}
+```
+
+> **Note**: Next.js also allows the file to be named `.postcssrc.json`, or, to be read from the `postcss` key in `package.json`.
+
+It is also possible to configure PostCSS with a `postcss.config.js` file, which is useful when you want to conditionally include plugins based on environment:
+
+```js
+module.exports = {
+ plugins:
+ process.env.NODE_ENV === 'production'
+ ? [
+ 'postcss-flexbugs-fixes',
+ [
+ 'postcss-preset-env',
+ {
+ autoprefixer: {
+ flexbox: 'no-2009',
+ },
+ stage: 3,
+ features: {
+ 'custom-properties': false,
+ },
+ },
+ ],
+ ]
+ : [
+ // No transformations in development
+ ],
+}
+```
+
+> **Note**: Next.js also allows the file to be named `.postcssrc.js`.
+
+Do **not use `require()`** to import the PostCSS Plugins. Plugins must be provided as strings.
+
+> **Note**: If your `postcss.config.js` needs to support other non-Next.js tools in the same project, you must use the interoperable object-based format instead:
+>
+> ```js
+> module.exports = {
+> plugins: {
+> 'postcss-flexbugs-fixes': {},
+> 'postcss-preset-env': {
+> autoprefixer: {
+> flexbox: 'no-2009',
+> },
+> stage: 3,
+> features: {
+> 'custom-properties': false,
+> },
+> },
+> },
+> }
+> ```
diff --git a/docs/advanced-features/debugging.md b/docs/advanced-features/debugging.md
new file mode 100644
index 0000000000..86ba80d003
--- /dev/null
+++ b/docs/advanced-features/debugging.md
@@ -0,0 +1,105 @@
+---
+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 [VS Code debugger](https://code.visualstudio.com/docs/editor/debugging) or [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools).
+
+Any debugger that can attach to Node.js can also be used to debug a Next.js application. You can find more details in the Node.js [Debugging Guide](https://nodejs.org/en/docs/guides/debugging-getting-started/).
+
+## Debugging with VS Code
+
+Create a file named `.vscode/launch.json` at the root of your project with the following content:
+
+```json
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Next.js: debug server-side",
+ "type": "node-terminal",
+ "request": "launch",
+ "command": "npm run dev"
+ },
+ {
+ "name": "Next.js: debug client-side",
+ "type": "chrome",
+ "request": "launch",
+ "url": "http://localhost:3000"
+ },
+ {
+ "name": "Next.js: debug full stack",
+ "type": "node-terminal",
+ "request": "launch",
+ "command": "npm run dev",
+ "serverReadyAction": {
+ "pattern": "started server on .+, url: (https?://.+)",
+ "uriFormat": "%s",
+ "action": "debugWithChrome"
+ }
+ }
+ ]
+}
+```
+
+`npm run dev` can be replaced with `yarn dev` if you're using Yarn. If you're [changing the port number](/docs/api-reference/cli#development) your application starts on, replace the `3000` in `http://localhost:3000` with the port you're using instead.
+
+Now go to the Debug panel (Ctrl+Shift+D on Windows/Linux, ⇧+⌘+D on macOS), select a launch configuration, then press F5 or select **Debug: Start Debugging** from the Command Palette to start your debugging session.
+
+## Using the Debugger in Jetbrains WebStorm
+
+Click the drop down menu listing the runtime configuration, and click `Edit Configurations...`. Create a `Javascript Debug` debug configuration with `http://localhost:3000` as the URL. Customize to your liking (e.g. Browser for debugging, store as project file), and click `OK`. Run this debug configuration, and the selected browser should automatically open. At this point, you should have 2 applications in debug mode: the NextJS node application, and the client/ browser application.
+
+## Debugging with Chrome DevTools
+
+### Client-side code
+
+Start your development server as usual by running `next dev`, `npm run dev`, or `yarn dev`. Once the server starts, open `http://localhost:3000` (or your alternate URL) in Chrome. Next, open Chrome's Developer Tools (Ctrl+Shift+J on Windows/Linux, ⌥+⌘+I on macOS), then go to the **Sources** tab.
+
+Now, any time your client-side code reaches a [`debugger`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger) statement, code execution will pause and that file will appear in the debug area. You can also press Ctrl+P on Windows/Linux or ⌘+P on macOS to search for a file and set breakpoints manually. Note that when searching here, your source files will have paths starting with `webpack://_N_E/./`.
+
+### Server-side code
+
+To debug server-side Next.js code with Chrome DevTools, you need to pass the [`--inspect`](https://nodejs.org/api/cli.html#cli_inspect_host_port) flag to the underlying Node.js process:
+
+```bash
+NODE_OPTIONS='--inspect' next dev
+```
+
+If you're using `npm run dev` or `yarn dev` (see [Getting Started](/docs/getting-started)) then you should update the `dev` script on your `package.json`:
+
+```json
+"dev": "NODE_OPTIONS='--inspect' next dev"
+```
+
+Launching the Next.js dev server with the `--inspect` flag will look something like this:
+
+```bash
+Debugger listening on ws://127.0.0.1:9229/0cf90313-350d-4466-a748-cd60f4e47c95
+For help, see: https://nodejs.org/en/docs/inspector
+ready - started server on 0.0.0.0:3000, url: http://localhost:3000
+```
+
+> Be aware that running `NODE_OPTIONS='--inspect' npm run dev` or `NODE_OPTIONS='--inspect' yarn dev` won't work. This would try to start multiple debuggers on the same port: one for the npm/yarn process and one for Next.js. You would then get an error like `Starting inspector on 127.0.0.1:9229 failed: address already in use` in your console.
+
+Once the server starts, open a new tab in Chrome and visit `chrome://inspect`, where you should see your Next.js application inside the **Remote Target** section. Click **inspect** under your application to open a separate DevTools window, then go to the **Sources** tab.
+
+Debugging server-side code here works much like debugging client-side code with Chrome DevTools, except that when you search for files here with Ctrl+P or ⌘+P, your source files will have paths starting with `webpack://{application-name}/./` (where `{application-name}` will be replaced with the name of your application according to your `package.json` file).
+
+### Debugging on Windows
+
+Windows users may run into an issue when using `NODE_OPTIONS='--inspect'` as that syntax is not supported on Windows platforms. To get around this, install the [`cross-env`](https://www.npmjs.com/package/cross-env) package as a development dependency (`-D` with `npm` and `yarn`) and replace the `dev` script with the following.
+
+```json
+"dev": "cross-env NODE_OPTIONS='--inspect' next dev",
+```
+
+`cross-env` will set the `NODE_OPTIONS` environment variable regardless of which platform you are on (including Mac, Linux, and Windows) and allow you to debug consistently across devices and operating systems.
+
+## More information
+
+To learn more about how to use a JavaScript debugger, take a look at the following documentation:
+
+- [Node.js debugging in VS Code: Breakpoints](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_breakpoints)
+- [Chrome DevTools: Debug JavaScript](https://developers.google.com/web/tools/chrome-devtools/javascript)
diff --git a/docs/advanced-features/dynamic-import.md b/docs/advanced-features/dynamic-import.md
new file mode 100644
index 0000000000..f9947809dd
--- /dev/null
+++ b/docs/advanced-features/dynamic-import.md
@@ -0,0 +1,96 @@
+---
+description: Dynamically import JavaScript modules and React Components and split your code into manageable chunks.
+---
+
+# Dynamic Import
+
+Examples
+
+
+Results: {JSON.stringify(results, null, 2)}
+ Oops, there is an error!
+
+ Examples
+
+
+Version History
+
+| Version | Changes |
+| --------- | ------------------------------------------------------------------------------------------ |
+| `v13.1.0` | Advanced Middleware flags added |
+| `v13.0.0` | Middleware can modify request headers, response headers, and send responses |
+| `v12.2.0` | Middleware is stable |
+| `v12.0.9` | Enforce absolute URLs in Edge Runtime ([PR](https://github.com/vercel/next.js/pull/33410)) |
+| `v12.0.0` | Middleware (Beta) added |
+
+Examples
+
+Hello World
+
+ >
+ )
+}
+```
+
+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
+}
+```
+
+```jsx
+// pages/index.js
+import Button from '@/components/button'
+
+export default function HomePage() {
+ return (
+ <>
+ Hello World
+
+ >
+ )
+}
+```
diff --git a/docs/advanced-features/multi-zones.md b/docs/advanced-features/multi-zones.md
new file mode 100644
index 0000000000..2045d487ce
--- /dev/null
+++ b/docs/advanced-features/multi-zones.md
@@ -0,0 +1,30 @@
+# Multi Zones
+
+Examples
+
+
+Examples
+
+
+Examples
+
+
+H1 heading
+
+H2 heading
+
+
+
+```
+
+When you want to style your own elements to give a custom feel to your website or application, you can pass in shortcodes. These are your own custom components that map to `HTML` elements. To do this you use the `MDXProvider` and pass a components object as a prop. Each object key in the components object maps to a `HTML` element name.
+
+To enable you need to specify `providerImportSource: "@mdx-js/react"` in `next.config.js`.
+
+```js
+// next.config.js
+
+const withMDX = require('@next/mdx')({
+ // ...
+ options: {
+ providerImportSource: '@mdx-js/react',
+ },
+})
+```
+
+Then setup the provider in your page
+
+```jsx
+// pages/index.js
+
+import { MDXProvider } from '@mdx-js/react'
+import Image from 'next/image'
+import { Heading, InlineCode, Pre, Table, Text } from 'my-components'
+
+const ResponsiveImage = (props) => (
+ Version History
+
+| Version | Changes |
+| --------- | ------------------------------------------------------------------- |
+| `v10.0.0` | `locale`, `locales`, `defaultLocale`, and `notFound` options added. |
+| `v9.3.0` | `getServerSideProps` introduced. |
+
+Version History
+
+| Version | Changes |
+| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `v12.2.0` | [On-Demand Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation) is stable. |
+| `v12.1.0` | [On-Demand Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation) added (beta). |
+| `v9.5.0` | Stable [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md) |
+| `v9.3.0` | `getStaticPaths` introduced. |
+
+Examples
+
+Version History
+
+| Version | Changes |
+| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `v12.2.0` | [On-Demand Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation) is stable. |
+| `v12.1.0` | [On-Demand Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md#on-demand-revalidation) added (beta). |
+| `v10.0.0` | `locale`, `locales`, `defaultLocale`, and `notFound` options added. |
+| `v10.0.0` | `fallback: 'blocking'` return option added. |
+| `v9.5.0` | Stable [Incremental Static Regeneration](/docs/basic-features/data-fetching/incremental-static-regeneration.md) |
+| `v9.3.0` | `getStaticProps` introduced. |
+
+
+ {posts.map((post) => (
+
+ )
+}
+
+// This function gets called at build time on server-side.
+// It won't be called on client-side, so you can even do
+// direct database queries.
+export async function getStaticProps() {
+ const postsDirectory = path.join(process.cwd(), 'posts')
+ const filenames = await fs.readdir(postsDirectory)
+
+ const posts = filenames.map(async (filename) => {
+ const filePath = path.join(postsDirectory, filename)
+ const fileContents = await fs.readFile(filePath, 'utf8')
+
+ // Generally you would parse/transform the contents
+ // For example you can transform markdown to HTML here
+
+ return {
+ filename,
+ content: fileContents,
+ }
+ })
+ // By returning { props: { posts } }, the Blog component
+ // will receive `posts` as a prop at build time
+ return {
+ props: {
+ posts: await Promise.all(posts),
+ },
+ }
+}
+
+export default Blog
+```
+
+## getStaticProps with TypeScript
+
+The type of `getStaticProps` can be specified using `GetStaticProps` from `next`:
+
+```ts
+import { GetStaticProps } from 'next'
+
+type Post = {
+ author: string
+ content: string
+}
+
+export const getStaticProps: GetStaticProps<{ posts: Post[] }> = async (
+ context
+) => {
+ const res = await fetch('https://.../posts')
+ const posts: Post[] = await res.json()
+
+ return {
+ props: {
+ posts,
+ },
+ }
+}
+```
+
+If you want to get inferred typings for your props, you can use `InferGetStaticPropsType{post.filename}
+ Version History
+
+| Version | Changes |
+| -------- | ---------------- |
+| `v9.5.0` | Base Path added. |
+
+My Homepage
+ Examples
+
+
+The value of customKey is: {process.env.customKey}
+}
+
+export default Page
+```
+
+Next.js will replace `process.env.customKey` with `'my-value'` at build time. Trying to destructure `process.env` variables won't work due to the nature of webpack [DefinePlugin](https://webpack.js.org/plugins/define-plugin/).
+
+For example, the following line:
+
+```jsx
+return The value of customKey is: {process.env.customKey}
+```
+
+Will end up being:
+
+```jsx
+return The value of customKey is: {'my-value'}
+```
+
+## Related
+
+
+
+
diff --git a/docs/api-reference/next.config.js/exportPathMap.md b/docs/api-reference/next.config.js/exportPathMap.md
new file mode 100644
index 0000000000..4180363e6d
--- /dev/null
+++ b/docs/api-reference/next.config.js/exportPathMap.md
@@ -0,0 +1,98 @@
+---
+description: Customize the pages that will be exported as HTML files when using `next export`.
+---
+
+# exportPathMap
+
+> This feature is exclusive to `next export`. Please refer to [Static HTML export](/docs/advanced-features/static-html-export.md) if you want to learn more about it.
+
+Examples
+
+
+Examples
+
+
+Version History
+
+| Version | Changes |
+| --------- | -------------- |
+| `v10.2.0` | `has` added. |
+| `v9.5.0` | Headers added. |
+
+Examples
+
+
+Version History
+
+| Version | Changes |
+| --------- | ---------------- |
+| `v10.2.0` | `has` added. |
+| `v9.5.0` | Redirects added. |
+
+Examples
+
+
+Version History
+
+| Version | Changes |
+| --------- | ---------------- |
+| `v13.3.0` | `missing` added. |
+| `v10.2.0` | `has` added. |
+| `v9.5.0` | Rewrites added. |
+
+Examples
+
+Version History
+
+| Version | Changes |
+| -------- | --------------------- |
+| `v9.5.0` | Trailing Slash added. |
+
+Examples
+
+
+My AMP About Page!
+}
+
+export default About
+```
+
+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 a user-accessible (optimized) version of the page and a search-engine indexable (unoptimized) version of the page
+
+## Hybrid AMP Page
+
+Take a look at the following example:
+
+```jsx
+import { useAmp } from 'next/amp'
+
+export const config = { amp: 'hybrid' }
+
+function About(props) {
+ const isAmp = useAmp()
+
+ return (
+ My AMP About Page!
+ {isAmp ? (
+ Version History
+
+| Version | Changes |
+| --------- | --------------------------------------------------------------------- |
+| `v13.2.0` | `@next/font` renamed to `next/font`. Installation no longer required. |
+| `v13.0.0` | `next/font` was added. |
+
+Examples
+
+My Homepage
+ About
+ Blog
+ Home
+ All Blog Posts
+ Post Slug: {slug}
+}
+```
+
+Rather than using the `:slug` syntax inside your `Route` component, Next.js uses the `[slug]` syntax in the file name for [Dynamic Routes](/docs/routing/dynamic-routes.md). We can transform this to Next.js by creating two new files, `pages/blog/index.js` (showing all pages) and `pages/blog/[slug].js` (showing an individual post).
+
+```jsx
+// pages/blog/index.js
+
+export default function Blog() {
+ return All Blog Posts
+}
+
+// pages/blog/[slug].js
+
+import { useRouter } from 'next/router'
+
+export default function Post() {
+ const router = useRouter()
+ const { slug } = router.query
+
+ return Post Slug: {slug}
+}
+```
+
+## Server Rendering
+
+Next.js has built-in support for [Server-side Rendering](/docs/basic-features/pages#server-side-rendering.md). This means you can remove any instances of `StaticRouter` in your code.
+
+## Code Splitting
+
+Next.js has built-in support for [Code Splitting](https://v5.reactrouter.com/web/guides/code-splitting). This means you can remove any instances of:
+
+- `@loadable/server`, `@loadable/babel-plugin`, and `@loadable/webpack-plugin`
+- Modifications to your `.babelrc` for `@loadable/babel-plugin`
+
+Each file inside your `pages/` directory will be code split into its own JavaScript bundle during the build process. Next.js [also supports](/docs/basic-features/supported-browsers-features.md#javascript-language-features) ES2020 dynamic `import()` for JavaScript. With it you can import JavaScript modules dynamically and work with them. They also work with SSR.
+
+For more information, read about [Dynamic Imports](https://nextjs.org/docs/advanced-features/dynamic-import).
+
+## Scroll Restoration
+
+Next.js has built-in support for [Scroll Restoration](https://v5.reactrouter.com/web/guides/scroll-restoration). This means you can remove any custom `ScrollToTop` components you have defined.
+
+The default behavior of `next/link` and `next/router` is to scroll to the top of the page. You can also [disable this](https://nextjs.org/docs/api-reference/next/link#disable-scrolling-to-the-top-of-the-page) if you prefer.
+
+## Learn More
+
+For more information on what to do next, we recommend the following sections:
+
+
+
+
+
+
diff --git a/docs/migrating/incremental-adoption.md b/docs/migrating/incremental-adoption.md
new file mode 100644
index 0000000000..e7bf9ae047
--- /dev/null
+++ b/docs/migrating/incremental-adoption.md
@@ -0,0 +1,93 @@
+---
+description: Learn different strategies for incrementally adopting Next.js into your development workflow.
+---
+
+# Incrementally Adopting Next.js
+
+Examples
+
+
+Examples
+
+
+
+
+ )
+}
+
+export default Home
+```
+
+Read our docs for [Linking between pages](/docs/routing/introduction.md#linking-between-pages) to learn more.
+
+### Catch all routes
+
+Examples
+
+
+Examples
+
+
+
+
+ )
+}
+
+export default Home
+```
+
+The example above uses multiple links. Each one maps a path (`href`) to a known page:
+
+- `/` → `pages/index.js`
+- `/about` → `pages/about.js`
+- `/blog/hello-world` → `pages/blog/[slug].js`
+
+Any `` in the viewport (initially or through scroll) will be prefetched by default (including the corresponding data) for pages using [Static Generation](/docs/basic-features/data-fetching/get-static-props.md). The corresponding data for [server-rendered](/docs/basic-features/data-fetching/get-server-side-props.md) routes is fetched _only when_ the `` is clicked.
+
+### Linking to dynamic paths
+
+You can also use interpolation to create the path, which comes in handy for [dynamic route segments](#dynamic-route-segments). For example, to show a list of posts which have been passed to the component as a prop:
+
+```jsx
+import Link from 'next/link'
+
+function Posts({ posts }) {
+ return (
+
+ {posts.map((post) => (
+
+ )
+}
+
+export default Posts
+```
+
+> [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) is used in the example to keep the path utf-8 compatible.
+
+Alternatively, using a URL Object:
+
+```jsx
+import Link from 'next/link'
+
+function Posts({ posts }) {
+ return (
+
+ {posts.map((post) => (
+
+ )
+}
+
+export default Posts
+```
+
+Now, instead of using interpolation to create the path, we use a URL object in `href` where:
+
+- `pathname` is the name of the page in the `pages` directory. `/blog/[slug]` in this case.
+- `query` is an object with the dynamic segment. `slug` in this case.
+
+## Injecting the router
+
+Examples
+
+
+Examples
+
+
+Examples
+
+About Page
+ Homepage
+ About Page
+
+
+
+
+### Useful Links
+
+- [Enabling AMP Support](https://nextjs.org/docs/advanced-features/amp-support/introduction)
+- [API Routes Request Helpers](https://nextjs.org/docs/api-routes/request-helpers)
+- [Switchable Runtime](https://nextjs.org/docs/advanced-features/react-18/switchable-runtime)
diff --git a/errors/invalid-project-dir-casing.md b/errors/invalid-project-dir-casing.md
new file mode 100644
index 0000000000..8f650f95ac
--- /dev/null
+++ b/errors/invalid-project-dir-casing.md
@@ -0,0 +1,16 @@
+# Invalid Project Directory Casing
+
+#### Why This Error Occurred
+
+When starting Next.js, the current directory is a different casing than the actual directory on your filesystem. This can cause files to resolve inconsistently.
+
+This can occur when using a case-insensitive filesystem. For example, opening PowerShell on Windows navigating to `cd path/to/myproject` instead of `cd path/to/MyProject`.
+
+#### Possible Ways to Fix It
+
+Ensure the casing for the current working directory matches the actual case of the real directory. Use a terminal that enforces case-sensitivity.
+
+### Useful Links
+
+- [Next.js CLI documentation](https://nextjs.org/docs/api-reference/cli)
+- [Case sensitivity in filesystems](https://en.wikipedia.org/wiki/Case_sensitivity#In_filesystems)
diff --git a/errors/invalid-react-version.md b/errors/invalid-react-version.md
new file mode 100644
index 0000000000..4746d60e56
--- /dev/null
+++ b/errors/invalid-react-version.md
@@ -0,0 +1,9 @@
+# Invalid React Version
+
+#### Why This Error Occurred
+
+You tried running `next` in a project with an incompatible react version. Next.js uses certain react features that when are unavailable show this error since it can't work without them.
+
+#### Possible Ways to Fix It
+
+Run `npm i react@latest react-dom@latest` or `yarn add react@latest react-dom@latest` in your project and then try running `next` again.
diff --git a/errors/invalid-redirect-gssp.md b/errors/invalid-redirect-gssp.md
new file mode 100644
index 0000000000..d75fbb9fe4
--- /dev/null
+++ b/errors/invalid-redirect-gssp.md
@@ -0,0 +1,32 @@
+# Invalid Redirect getStaticProps/getServerSideProps
+
+#### Why This Error Occurred
+
+The `redirect` value returned from your `getStaticProps` or `getServerSideProps` function had invalid values.
+
+#### Possible Ways to Fix It
+
+Make sure you return the proper values for the `redirect` value.
+
+```js
+export const getStaticProps = ({ params }) => {
+ if (params.slug === 'deleted-post') {
+ return {
+ redirect: {
+ permanent: true, // or false
+ destination: '/some-location',
+ },
+ }
+ }
+
+ return {
+ props: {
+ // data
+ },
+ }
+}
+```
+
+### Useful Links
+
+- [Data Fetching Documentation](https://nextjs.org/docs/basic-features/data-fetching/get-static-props)
diff --git a/errors/invalid-relative-url-external-as.md b/errors/invalid-relative-url-external-as.md
new file mode 100644
index 0000000000..848b56d422
--- /dev/null
+++ b/errors/invalid-relative-url-external-as.md
@@ -0,0 +1,47 @@
+# Invalid relative `href` and external `as` values
+
+#### Why This Error Occurred
+
+Somewhere you are utilizing the `next/link` component, `Router#push`, or `Router#replace` with a relative route in your `href` that has an external `as` value. The `as` value must be relative also or only `href` should be used with an external URL.
+
+Note: this error will only show when the `next/link` component is clicked not when only rendered.
+
+**Incompatible `href` and `as`**
+
+```jsx
+import Link from 'next/link'
+
+export default function Page(props) {
+ return (
+ <>
+
+ Invalid link
+
+ >
+ )
+}
+```
+
+**Compatible `href` and `as`**
+
+```jsx
+import Link from 'next/link'
+
+export default function Page(props) {
+ return (
+ <>
+
+ Invalid link
+
+ >
+ )
+}
+```
+
+#### Possible Ways to Fix It
+
+Look for any usage of the `next/link` component, `Router#push`, or `Router#replace` and make sure that the provided `href` and `as` values are compatible
+
+### Useful Links
+
+- [Routing section in Documentation](https://nextjs.org/docs/routing/introduction)
diff --git a/errors/invalid-resolve-alias.md b/errors/invalid-resolve-alias.md
new file mode 100644
index 0000000000..4c2c88aa1f
--- /dev/null
+++ b/errors/invalid-resolve-alias.md
@@ -0,0 +1,22 @@
+# Invalid webpack resolve alias
+
+#### Why This Error Occurred
+
+When overriding `config.resolve.alias` incorrectly in `next.config.js` webpack will throw an error because private-next-pages is not defined.
+
+#### Possible Ways to Fix It
+
+This is not a bug in Next.js, it's related to the user adding a custom webpack(config) config to next.config.js and overriding internals by not applying Next.js' aliases. Solution would be:
+
+```js
+webpack(config) {
+ config.resolve.alias = {
+ ...config.resolve.alias,
+ // your aliases
+ }
+}
+```
+
+### Useful Links
+
+- [Related issue](https://github.com/vercel/next.js/issues/6681)
diff --git a/errors/invalid-route-source.md b/errors/invalid-route-source.md
new file mode 100644
index 0000000000..0995c5714b
--- /dev/null
+++ b/errors/invalid-route-source.md
@@ -0,0 +1,58 @@
+# Invalid Custom Route `source`
+
+#### Why This Error Occurred
+
+When defining custom routes, or a middleware `matcher`, a pattern could not be parsed.
+
+This could have been due to trying to use normal `RegExp` syntax like negative lookaheads (`?!exclude`) without following [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp)'s syntax for it.
+
+#### Possible Ways to Fix It
+
+Wrap the `RegExp` part of your `source` as an un-named parameter.
+
+---
+
+Custom routes:
+
+**Before**
+
+```js
+{
+ source: '/feedback/(?!general)',
+ destination: '/feedback/general'
+}
+```
+
+**After**
+
+```js
+{
+ source: '/feedback/((?!general).*)',
+ destination: '/feedback/general'
+}
+```
+
+---
+
+Middleware:
+
+**Before**
+
+```js
+const config = {
+ matcher: '/feedback/(?!general)',
+}
+```
+
+**After**
+
+```js
+const config = {
+ matcher: '/feedback/((?!general).*)',
+}
+```
+
+### Useful Links
+
+- [path-to-regexp](https://github.com/pillarjs/path-to-regexp)
+- [un-named parameters](https://github.com/pillarjs/path-to-regexp#unnamed-parameters)
diff --git a/errors/invalid-script.md b/errors/invalid-script.md
new file mode 100644
index 0000000000..f919a25a00
--- /dev/null
+++ b/errors/invalid-script.md
@@ -0,0 +1,38 @@
+# Invalid Script
+
+#### Why This Error Occurred
+
+Somewhere in your application, you are using the `next/script` component without including an inline script or `src` attribute.
+
+#### Possible Ways to Fix It
+
+Look for any usage of the `next/script` component and make sure that `src` is provided or an inline script is used.
+
+**Compatible `src` attribute**
+
+```jsx
+
+```
+
+**Compatible inline script with curly braces**
+
+```jsx
+
+```
+
+**Compatible inline script with `dangerouslySetInnerHtml`**
+
+```jsx
+
+```
+
+### Useful Links
+
+- [Script Component in Documentation](https://nextjs.org/docs/basic-features/script)
diff --git a/errors/invalid-segment-export.md b/errors/invalid-segment-export.md
new file mode 100644
index 0000000000..ce27690b42
--- /dev/null
+++ b/errors/invalid-segment-export.md
@@ -0,0 +1,11 @@
+# Invalid Layout or Page Export
+
+#### Why This Error Occurred
+
+Your [layout](https://beta.nextjs.org/docs/api-reference/file-conventions/layout) or [page](https://beta.nextjs.org/docs/api-reference/file-conventions/page) inside the app directory exports an invalid field. In these files, you're only allowed to export a default React component, or [Segment Configuration Options](https://beta.nextjs.org/docs/api-reference/segment-config) for layout and pages, such as `revalidate`, `generateStaticParams`, etc.
+
+Other custom exports are not allowed to catch misspelt configuration options and prevent conflicts with future options.
+
+#### Possible Ways to Fix It
+
+You can create a new file and co-locate it with the page or layout. In the new file, you can export any custom fields and import it from anywhere.
diff --git a/errors/invalid-server-options.md b/errors/invalid-server-options.md
new file mode 100644
index 0000000000..b688e53942
--- /dev/null
+++ b/errors/invalid-server-options.md
@@ -0,0 +1,25 @@
+# It looks like the next instance is being instantiated incorrectly.
+
+#### Why This Error Occurred
+
+You have passed a null or undefined parameter to the next() call.
+
+#### Possible Ways to Fix It
+
+Make sure you are passing the variables properly:
+
+```js
+const app = next()
+```
+
+And make sure you're passing dev as shown in the examples below:
+
+```js
+const app = next({ dev: boolean })
+```
+
+### Useful Links
+
+- [custom-server-express](https://github.com/vercel/next.js/blob/6ca00bfe312c8d3fc5c20d25a9cd8d2741a29332/examples/custom-server-express/server.js#L6)
+- [custom-server](https://github.com/vercel/next.js/blob/6ca00bfe312c8d3fc5c20d25a9cd8d2741a29332/examples/custom-server/server.js#L6)
+- [custom-server-typescript](https://github.com/vercel/next.js/blob/6ca00bfe312c8d3fc5c20d25a9cd8d2741a29332/examples/custom-server-typescript/server/index.ts#L7)
diff --git a/errors/invalid-styled-jsx-children.md b/errors/invalid-styled-jsx-children.md
new file mode 100644
index 0000000000..86c0b3447f
--- /dev/null
+++ b/errors/invalid-styled-jsx-children.md
@@ -0,0 +1,29 @@
+# Invalid `styled-jsx` children
+
+#### Why This Error Occurred
+
+You have passed invalid children to a `
+
+)
+```
+
+Please see the links for more examples.
+
+### Useful Links
+
+- [Built-In CSS-in-JS](https://nextjs.org/docs/basic-features/built-in-css-support#css-in-js)
+- [styled-jsx documentation](https://github.com/vercel/styled-jsx)
diff --git a/errors/invalid-webpack-5-version.md b/errors/invalid-webpack-5-version.md
new file mode 100644
index 0000000000..5c10e71793
--- /dev/null
+++ b/errors/invalid-webpack-5-version.md
@@ -0,0 +1,13 @@
+# Invalid webpack 5 version
+
+#### Why This Error Occurred
+
+While leveraging webpack 5 support in Next.js the minimum required version of `v5.15.0` was not met. This version is needed while leveraging webpack 5 support with Next.js as early versions are missing patches that cause unexpected behavior.
+
+#### Possible Ways to Fix It
+
+Upgrade the version of webpack 5 being used with Next.js to at least `v5.15.0` by updating your resolutions field for `webpack` in your `package.json`.
+
+### Useful Links
+
+- [Yarn Selective Dependency Resolutions Documentation](https://classic.yarnpkg.com/en/docs/selective-version-resolutions/)
diff --git a/errors/large-page-data.md b/errors/large-page-data.md
new file mode 100644
index 0000000000..6a195072f4
--- /dev/null
+++ b/errors/large-page-data.md
@@ -0,0 +1,19 @@
+# Large Page Data
+
+#### Why This Error Occurred
+
+One of your pages includes a large amount of page data (>= 128kB). This can negatively impact performance since page data must be parsed by the client before the page is hydrated.
+
+#### Possible Ways to Fix It
+
+Reduce the amount of data returned from `getStaticProps`, `getServerSideProps`, or `getInitialProps` to only the essential data to render the page. The default threshold of 128kB can be configured in `largePageDataBytes` if absolutely necessary and the performance implications are understood.
+
+To inspect the props passed to your page, you can inspect the below element's content in your browser devtools:
+
+```sh
+document.getElementById("__NEXT_DATA__").text
+```
+
+### Useful Links
+
+- [Data Fetching Documentation](https://nextjs.org/docs/basic-features/data-fetching/overview)
diff --git a/errors/link-multiple-children.md b/errors/link-multiple-children.md
new file mode 100644
index 0000000000..54476589c6
--- /dev/null
+++ b/errors/link-multiple-children.md
@@ -0,0 +1,36 @@
+# Multiple children were passed to
+
+#### Why This Error Occurred
+
+In your application code multiple children were passed to `next/link` but only one child is supported:
+
+For example:
+
+```js
+import Link from 'next/link'
+
+export default function Home() {
+ return (
+
+ To About
+ Second To About
+
+ )
+}
+```
+
+#### Possible Ways to Fix It
+
+Make sure only one child is used when using ``:
+
+```js
+import Link from 'next/link'
+
+export default function Home() {
+ return (
+
+ To About
+
+ )
+}
+```
diff --git a/errors/link-no-children.md b/errors/link-no-children.md
new file mode 100644
index 0000000000..48fd21c803
--- /dev/null
+++ b/errors/link-no-children.md
@@ -0,0 +1,41 @@
+# No children were passed to
+
+#### Why This Error Occurred
+
+In your application code `next/link` was used without passing a child:
+
+For example:
+
+```js
+import Link from 'next/link'
+
+export default function Home() {
+ return (
+ <>
+
+ // or
+
+ >
+ )
+}
+```
+
+#### Possible Ways to Fix It
+
+Make sure one child is used when using ``:
+
+```js
+import Link from 'next/link'
+
+export default function Home() {
+ return (
+ <>
+ To About
+ // or
+
+ To About
+
+ >
+ )
+}
+```
diff --git a/errors/link-passhref.md b/errors/link-passhref.md
new file mode 100644
index 0000000000..e22a15b718
--- /dev/null
+++ b/errors/link-passhref.md
@@ -0,0 +1,34 @@
+# Link `passHref`
+
+> Ensure `passHref` is used with custom `Link` components.
+
+### Why This Error Occurred
+
+`passHref` was not used for a `Link` component that wraps a custom component. This is needed in order to pass the `href` to the child `` tag.
+
+### Possible Ways to Fix It
+
+If you're using a custom component that wraps an `` tag, make sure to add `passHref`:
+
+```jsx
+import Link from 'next/link'
+import styled from 'styled-components'
+
+const StyledLink = styled.a`
+ color: red;
+`
+
+function NavLink({ href, name }) {
+ return (
+
+
+
+
+
+
+Not Allowed
+ Allowed
+
+
+
+
+
+```js
+// `config` should be an object
+export const config = 'hello world'
+```
+
+
+
+
+```js
+export const config = {}
+```
+
+
+
+
+
+
+
+```js
+export const config = {}
+// `config.amp` is defined after `config` is exported
+config.amp = true
+
+// `config.amp` contains a dynamic expression
+export const config = {
+ amp: 1 + 1 > 2,
+}
+```
+
+
+
+
+```js
+export const config = {
+ amp: true,
+}
+
+export const config = {
+ amp: false,
+}
+```
+
+
+
+
+
+
+
+```js
+// `config.runtime` contains a dynamic expression
+export const config = {
+ runtime: `node${'js'}`,
+}
+```
+
+
+
+
+```js
+export const config = {
+ runtime: 'nodejs',
+}
+export const config = {
+ runtime: `nodejs`,
+}
+```
+
+
+
+
+
+
+
+
+```js
+// Re-exported `config` is not allowed
+export { config } from '../config'
+```
+
+
+
+
+```js
+export const config = {}
+```
+
+
+Hello
+}
+```
+
+```js
+// pages/index.js
+// Note how `components/MyComponent` exists but `Mycomponent` without the capital `c` is imported
+import MyComponent from '../components/Mycomponent'
+```
+
+Incorrect casing will lead to build failures on case-sensitive environments like most Linux-based continuous integration and can cause issues with Fast Refresh.
+
+##### The module you're trying to import uses Node.js specific modules
+
+`getStaticProps`, `getStaticPaths`, and `getServerSideProps` allow for using modules that can only run in the Node.js environment. This allows you to do direct database queries or reading data from Redis to name a few examples.
+
+The tree shaking only runs on top level pages, so it can't be relied on in separate React components.
+
+You can verify the tree shaking on [next-code-elimination.vercel.app](https://next-code-elimination.vercel.app/).
+
+Example of correctly tree shaken code:
+
+```js
+// lib/redis.js
+import Redis from 'ioredis'
+
+const redis = new Redis(process.env.REDIS_URL)
+
+export default redis
+```
+
+```js
+// pages/index.js
+import redis from '../lib/redis'
+
+export async function getStaticProps() {
+ const message = await redis.get('message')
+ return {
+ message,
+ }
+}
+
+export default function Home({ message }) {
+ return {message}
+}
+```
+
+Example of code that would break:
+
+```js
+// lib/redis.js
+import Redis from 'ioredis'
+
+const redis = new Redis(process.env.REDIS_URL)
+
+export default redis
+```
+
+```js
+// pages/index.js
+// Redis is a Node.js specific library that can't run in the browser
+// Trying to use it in code that runs on both Node.js and the browser will result in a module not found error for modules that ioredis relies on
+// If you run into such an error it's recommended to move the code to `getStaticProps` or `getServerSideProps` as those methods guarantee that the code is only run in Node.js.
+import redis from '../lib/redis'
+import { useEffect, useState } from 'react'
+
+export default function Home() {
+ const [message, setMessage] = useState()
+ useEffect(() => {
+ redis.get('message').then((result) => {
+ setMessage(result)
+ })
+ }, [])
+ return {message}
+}
+```
+
+Example of code that would break:
+
+```js
+// lib/redis.js
+import Redis from 'ioredis'
+
+// Modules that hold Node.js-only code can't also export React components
+// Tree shaking of getStaticProps/getStaticPaths/getServerSideProps is ran only on page files
+const redis = new Redis(process.env.REDIS_URL)
+
+export function MyComponent() {
+ return Hello
+}
+
+export default redis
+```
+
+```js
+// pages/index.js
+// In practice you'll want to refactor the `MyComponent` to be a separate file so that tree shaking ensures that specific import is not included for the browser compilation
+import redis, { MyComponent } from '../lib/redis'
+
+export async function getStaticProps() {
+ const message = await redis.get('message')
+ return {
+ message,
+ }
+}
+
+export default function Home() {
+ return
Hello world
+ + +` component: + +```jsx +import Head from 'next/head' + +function Index() { + return ( + <> +
+
+ + + > + ) +} + +export default Index +``` + +### Useful Links + +- [next/head](https://nextjs.org/docs/api-reference/next/head) diff --git a/errors/no-head-import-in-document.md b/errors/no-head-import-in-document.md new file mode 100644 index 0000000000..cb9c18e08e --- /dev/null +++ b/errors/no-head-import-in-document.md @@ -0,0 +1,36 @@ +# No Head Import in Document + +> Prevent usage of `next/head` in `pages/_document.js`. + +### Why This Error Occurred + +`next/head` was imported in `pages/_document.js`. This can cause unexpected issues in your application. + +### Possible Ways to Fix It + +Only import and use `next/document` within `pages/_document.js` to override the default `Document` component. If you are importing `next/head` to use the `Head` component, import it from `next/document` instead in order to modify `
` code across all pages: + +```jsx +// pages/_document.js +import Document, { Html, Head, Main, NextScript } from 'next/document' + +class MyDocument extends Document { + static async getInitialProps(ctx) { + //... + } + + render() { + return ( + +
+
+ )
+ }
+}
+
+export default MyDocument
+```
+
+### Useful Links
+
+- [Custom Document](https://nextjs.org/docs/advanced-features/custom-document)
diff --git a/errors/no-html-link-for-pages.md b/errors/no-html-link-for-pages.md
new file mode 100644
index 0000000000..9e3a188877
--- /dev/null
+++ b/errors/no-html-link-for-pages.md
@@ -0,0 +1,63 @@
+# No HTML link for pages
+
+> Prevent usage of `` elements to navigate to internal Next.js pages.
+
+### Why This Error Occurred
+
+An `` element was used to navigate to a page route without using the `next/link` component, causing unnecessary full page refreshes.
+
+The `Link` component is required in order to enable client-side route transitions between pages and provide a single-page app experience.
+
+### Possible Ways to Fix It
+
+Make sure to import the `Link` component and wrap anchor elements that route to different page routes.
+
+**Before:**
+
+```jsx
+function Home() {
+ return (
+