diff --git a/docs/api-reference/create-next-app.md b/docs/api-reference/create-next-app.md
index 1b96f94179d20..24f4255df576c 100644
--- a/docs/api-reference/create-next-app.md
+++ b/docs/api-reference/create-next-app.md
@@ -18,7 +18,7 @@ yarn create next-app
- **-e, --example [name]|[github-url]** - An example to bootstrap the app with. You can use an example name from the [Next.js repo](https://github.com/vercel/next.js/tree/master/examples) or a GitHub URL. The URL can use any branch and/or subdirectory.
- **--example-path [path-to-example]** - In a rare case, your GitHub URL might contain a branch name with a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar). In this case, you must specify the path to the example separately: `--example-path foo/bar`
-- **--use-npm** - Explicitly tell the CLI to bootstrap the app using npm. Yarn will be used by default if it's installed
+- **--use-npm** - Explicitly tell the CLI to bootstrap the app using npm. To bootstrap using yarn we recommend to run `yarn create next-app`
### Why use Create Next App?
diff --git a/errors/no-sync-scripts.md b/errors/no-sync-scripts.md
index 4af7ef58b5211..1788236d85df1 100644
--- a/errors/no-sync-scripts.md
+++ b/errors/no-sync-scripts.md
@@ -16,7 +16,7 @@ import Script from 'next/experimental-script'
const Home = () => {
return (
-
+
Home Page
)
diff --git a/packages/create-next-app/README.md b/packages/create-next-app/README.md
index deeb80d655671..8e629058467a3 100644
--- a/packages/create-next-app/README.md
+++ b/packages/create-next-app/README.md
@@ -19,7 +19,7 @@ npx create-next-app blog-app
- **--ts, --typescript** - Initialize as a TypeScript project.
- **-e, --example [name]|[github-url]** - An example to bootstrap the app with. You can use an example name from the [Next.js repo](https://github.com/vercel/next.js/tree/master/examples) or a GitHub URL. The URL can use any branch and/or subdirectory.
- **--example-path <path-to-example>** - In a rare case, your GitHub URL might contain a branch name with a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar). In this case, you must specify the path to the example separately: `--example-path foo/bar`
-- **--use-npm** - Explicitly tell the CLI to bootstrap the app using npm. Yarn will be used by default if it's installed
+- **--use-npm** - Explicitly tell the CLI to bootstrap the app using npm. To bootstrap using yarn we recommend to run `yarn create next-app`
## Why use Create Next App?
diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index 9f3c72a177d24..438796298254a 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -1260,6 +1260,7 @@ export default async function getBaseWebpackConfig(
pageEnv: config.experimental.pageEnv,
excludeDefaultMomentLocales: config.future.excludeDefaultMomentLocales,
assetPrefix: config.assetPrefix,
+ disableOptimizedLoading: config.experimental.disableOptimizedLoading,
target,
reactProductionProfiling,
webpack: !!config.webpack,
diff --git a/packages/next/build/webpack/loaders/next-serverless-loader/utils.ts b/packages/next/build/webpack/loaders/next-serverless-loader/utils.ts
index de8e14e420d84..2619b5a4f1142 100644
--- a/packages/next/build/webpack/loaders/next-serverless-loader/utils.ts
+++ b/packages/next/build/webpack/loaders/next-serverless-loader/utils.ts
@@ -289,7 +289,13 @@ export function getUtils({
// on the parsed params, this is used to signal if we need
// to parse x-now-route-matches or not
const isDefaultValue = Array.isArray(value)
- ? value.every((val, idx) => val === defaultRouteMatches![key][idx])
+ ? value.some((val) => {
+ const defaultValue = defaultRouteMatches![key]
+
+ return Array.isArray(defaultValue)
+ ? defaultValue.includes(val)
+ : defaultValue === val
+ })
: value === defaultRouteMatches![key]
if (isDefaultValue || typeof value === 'undefined') {
diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts
index 7d8cc3fca1034..bdc3ad525c003 100644
--- a/packages/next/export/index.ts
+++ b/packages/next/export/index.ts
@@ -369,6 +369,7 @@ export default async function exportApp(
defaultLocale: i18n?.defaultLocale,
domainLocales: i18n?.domains,
trailingSlash: nextConfig.trailingSlash,
+ disableOptimizedLoading: nextConfig.experimental.disableOptimizedLoading,
}
const { serverRuntimeConfig, publicRuntimeConfig } = nextConfig
@@ -541,6 +542,8 @@ export default async function exportApp(
optimizeFonts: nextConfig.optimizeFonts,
optimizeImages: nextConfig.experimental.optimizeImages,
optimizeCss: nextConfig.experimental.optimizeCss,
+ disableOptimizedLoading:
+ nextConfig.experimental.disableOptimizedLoading,
parentSpanId: pageExportSpan.id,
})
diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts
index 68f03b80f4ba6..057c4c5f1ebd5 100644
--- a/packages/next/export/worker.ts
+++ b/packages/next/export/worker.ts
@@ -52,6 +52,7 @@ interface ExportPageInput {
optimizeFonts: boolean
optimizeImages?: boolean
optimizeCss: any
+ disableOptimizedLoading: any
parentSpanId: any
}
@@ -70,6 +71,7 @@ interface RenderOpts {
ampSkipValidation?: boolean
optimizeFonts?: boolean
optimizeImages?: boolean
+ disableOptimizedLoading?: boolean
optimizeCss?: any
fontManifest?: FontManifest
locales?: string[]
@@ -98,6 +100,7 @@ export default async function exportPage({
optimizeFonts,
optimizeImages,
optimizeCss,
+ disableOptimizedLoading,
}: ExportPageInput): Promise {
const exportPageSpan = trace('export-page-worker', parentSpanId)
@@ -284,6 +287,7 @@ export default async function exportPage({
optimizeImages,
/// @ts-ignore
optimizeCss,
+ disableOptimizedLoading,
distDir,
fontManifest: optimizeFonts
? requireFontManifest(distDir, serverless)
@@ -357,6 +361,7 @@ export default async function exportPage({
optimizeFonts,
optimizeImages,
optimizeCss,
+ disableOptimizedLoading,
fontManifest: optimizeFonts
? requireFontManifest(distDir, serverless)
: null,
diff --git a/packages/next/next-server/lib/utils.ts b/packages/next/next-server/lib/utils.ts
index eaaa30b2c806c..6af8e33dbdb7f 100644
--- a/packages/next/next-server/lib/utils.ts
+++ b/packages/next/next-server/lib/utils.ts
@@ -193,6 +193,7 @@ export type DocumentProps = DocumentInitialProps & {
devOnlyCacheBusterQueryString: string
scriptLoader: { afterInteractive?: string[]; beforeInteractive?: any[] }
locale?: string
+ disableOptimizedLoading?: boolean
}
/**
diff --git a/packages/next/next-server/server/config-shared.ts b/packages/next/next-server/server/config-shared.ts
index fb45863f3269e..f64f09bef497d 100644
--- a/packages/next/next-server/server/config-shared.ts
+++ b/packages/next/next-server/server/config-shared.ts
@@ -61,6 +61,7 @@ export type NextConfig = { [key: string]: any } & {
eslint?: boolean
reactRoot: boolean
enableBlurryPlaceholder: boolean
+ disableOptimizedLoading: boolean
}
}
@@ -118,6 +119,7 @@ export const defaultConfig: NextConfig = {
eslint: false,
reactRoot: Number(process.env.NEXT_PRIVATE_REACT_ROOT) > 0,
enableBlurryPlaceholder: false,
+ disableOptimizedLoading: true,
},
future: {
strictPostcssConfiguration: false,
diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts
index 0b367f43a1a4e..edb480402608a 100644
--- a/packages/next/next-server/server/next-server.ts
+++ b/packages/next/next-server/server/next-server.ts
@@ -157,6 +157,7 @@ export default class Server {
images: string
fontManifest: FontManifest
optimizeImages: boolean
+ disableOptimizedLoading: boolean
optimizeCss: any
locale?: string
locales?: string[]
@@ -217,6 +218,8 @@ export default class Server {
: null,
optimizeImages: !!this.nextConfig.experimental.optimizeImages,
optimizeCss: this.nextConfig.experimental.optimizeCss,
+ disableOptimizedLoading: this.nextConfig.experimental
+ .disableOptimizedLoading,
domainLocales: this.nextConfig.i18n?.domains,
distDir: this.distDir,
}
@@ -815,28 +818,30 @@ export default class Server {
}
// Headers come very first
- const headers = this.customRoutes.headers.map((r) => {
- const headerRoute = getCustomRoute(r, 'header')
- return {
- match: headerRoute.match,
- has: headerRoute.has,
- type: headerRoute.type,
- name: `${headerRoute.type} ${headerRoute.source} header route`,
- fn: async (_req, res, params, _parsedUrl) => {
- const hasParams = Object.keys(params).length > 0
-
- for (const header of (headerRoute as Header).headers) {
- let { key, value } = header
- if (hasParams) {
- key = compileNonPath(key, params)
- value = compileNonPath(value, params)
- }
- res.setHeader(key, value)
- }
- return { finished: false }
- },
- } as Route
- })
+ const headers = this.minimalMode
+ ? []
+ : this.customRoutes.headers.map((r) => {
+ const headerRoute = getCustomRoute(r, 'header')
+ return {
+ match: headerRoute.match,
+ has: headerRoute.has,
+ type: headerRoute.type,
+ name: `${headerRoute.type} ${headerRoute.source} header route`,
+ fn: async (_req, res, params, _parsedUrl) => {
+ const hasParams = Object.keys(params).length > 0
+
+ for (const header of (headerRoute as Header).headers) {
+ let { key, value } = header
+ if (hasParams) {
+ key = compileNonPath(key, params)
+ value = compileNonPath(value, params)
+ }
+ res.setHeader(key, value)
+ }
+ return { finished: false }
+ },
+ } as Route
+ })
// since initial query values are decoded by querystring.parse
// we need to re-encode them here but still allow passing through
@@ -968,12 +973,14 @@ export default class Server {
let afterFiles: Route[] = []
let fallback: Route[] = []
- if (Array.isArray(this.customRoutes.rewrites)) {
- afterFiles = this.customRoutes.rewrites.map(buildRewrite)
- } else {
- beforeFiles = this.customRoutes.rewrites.beforeFiles.map(buildRewrite)
- afterFiles = this.customRoutes.rewrites.afterFiles.map(buildRewrite)
- fallback = this.customRoutes.rewrites.fallback.map(buildRewrite)
+ if (!this.minimalMode) {
+ if (Array.isArray(this.customRoutes.rewrites)) {
+ afterFiles = this.customRoutes.rewrites.map(buildRewrite)
+ } else {
+ beforeFiles = this.customRoutes.rewrites.beforeFiles.map(buildRewrite)
+ afterFiles = this.customRoutes.rewrites.afterFiles.map(buildRewrite)
+ fallback = this.customRoutes.rewrites.fallback.map(buildRewrite)
+ }
}
const catchAllRoute: Route = {
diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx
index b00e19fb162d4..6508c887415a1 100644
--- a/packages/next/next-server/server/render.tsx
+++ b/packages/next/next-server/server/render.tsx
@@ -190,6 +190,7 @@ export type RenderOptsPartial = {
locales?: string[]
defaultLocale?: string
domainLocales?: DomainLocales
+ disableOptimizedLoading?: boolean
}
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
@@ -234,6 +235,7 @@ function renderDocument(
defaultLocale,
domainLocales,
isPreview,
+ disableOptimizedLoading,
}: RenderOpts & {
props: any
docComponentsRendered: DocumentProps['docComponentsRendered']
@@ -305,6 +307,7 @@ function renderDocument(
devOnlyCacheBusterQueryString,
scriptLoader,
locale,
+ disableOptimizedLoading,
...docProps,
})}
diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx
index 98d1d7e1907c0..d3a486a0e4de9 100644
--- a/packages/next/pages/_document.tsx
+++ b/packages/next/pages/_document.tsx
@@ -51,6 +51,115 @@ function getDocumentFiles(
}
}
+function getPolyfillScripts(context: DocumentProps, props: OriginProps) {
+ // polyfills.js has to be rendered as nomodule without async
+ // It also has to be the first script to load
+ const {
+ assetPrefix,
+ buildManifest,
+ devOnlyCacheBusterQueryString,
+ disableOptimizedLoading,
+ } = context
+
+ return buildManifest.polyfillFiles
+ .filter(
+ (polyfill) => polyfill.endsWith('.js') && !polyfill.endsWith('.module.js')
+ )
+ .map((polyfill) => (
+
+ ))
+}
+
+function getPreNextScripts(context: DocumentProps, props: OriginProps) {
+ const { scriptLoader, disableOptimizedLoading } = context
+
+ return (scriptLoader.beforeInteractive || []).map(
+ (file: ScriptLoaderProps) => {
+ const { strategy, ...scriptProps } = file
+ return (
+
+ )
+ }
+ )
+}
+
+function getDynamicChunks(
+ context: DocumentProps,
+ props: OriginProps,
+ files: DocumentFiles
+) {
+ const {
+ dynamicImports,
+ assetPrefix,
+ isDevelopment,
+ devOnlyCacheBusterQueryString,
+ disableOptimizedLoading,
+ } = context
+
+ return dynamicImports.map((file) => {
+ if (!file.endsWith('.js') || files.allFiles.includes(file)) return null
+
+ return (
+
+ )
+ })
+}
+
+function getScripts(
+ context: DocumentProps,
+ props: OriginProps,
+ files: DocumentFiles
+) {
+ const {
+ assetPrefix,
+ buildManifest,
+ isDevelopment,
+ devOnlyCacheBusterQueryString,
+ disableOptimizedLoading,
+ } = context
+
+ const normalScripts = files.allFiles.filter((file) => file.endsWith('.js'))
+ const lowPriorityScripts = buildManifest.lowPriorityFiles?.filter((file) =>
+ file.endsWith('.js')
+ )
+
+ return [...normalScripts, ...lowPriorityScripts].map((file) => {
+ return (
+
+ )
+ })
+}
+
/**
* `Document` component handles the initial `document` markup and renders only on the server side.
* Commonly used for implementing server side rendering for `css-in-js` libraries.
@@ -285,6 +394,22 @@ export class Head extends Component<
]
}
+ getDynamicChunks(files: DocumentFiles) {
+ return getDynamicChunks(this.context, this.props, files)
+ }
+
+ getPreNextScripts() {
+ return getPreNextScripts(this.context, this.props)
+ }
+
+ getScripts(files: DocumentFiles) {
+ return getScripts(this.context, this.props, files)
+ }
+
+ getPolyfillScripts() {
+ return getPolyfillScripts(this.context, this.props)
+ }
+
handleDocumentScriptLoaderItems(children: React.ReactNode): ReactNode[] {
const { scriptLoader } = this.context
const scriptLoaderItems: ScriptLoaderProps[] = []
@@ -347,9 +472,12 @@ export class Head extends Component<
headTags,
unstable_runtimeJS,
unstable_JsPreload,
+ disableOptimizedLoading,
} = this.context
+
const disableRuntimeJS = unstable_runtimeJS === false
- const disableJsPreload = unstable_JsPreload === false
+ const disableJsPreload =
+ unstable_JsPreload === false || !disableOptimizedLoading
this.context.docComponentsRendered.Head = true
@@ -582,6 +710,18 @@ export class Head extends Component<
{!disableRuntimeJS &&
!disableJsPreload &&
this.getPreloadMainLinks(files)}
+ {!disableOptimizedLoading &&
+ !disableRuntimeJS &&
+ this.getPolyfillScripts()}
+ {!disableOptimizedLoading &&
+ !disableRuntimeJS &&
+ this.getPreNextScripts()}
+ {!disableOptimizedLoading &&
+ !disableRuntimeJS &&
+ this.getDynamicChunks(files)}
+ {!disableOptimizedLoading &&
+ !disableRuntimeJS &&
+ this.getScripts(files)}
{process.env.__NEXT_OPTIMIZE_CSS && this.getCssLinks(files)}
{process.env.__NEXT_OPTIMIZE_CSS && (
@@ -627,106 +767,19 @@ export class NextScript extends Component {
'!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();'
getDynamicChunks(files: DocumentFiles) {
- const {
- dynamicImports,
- assetPrefix,
- isDevelopment,
- devOnlyCacheBusterQueryString,
- } = this.context
-
- return dynamicImports.map((file) => {
- if (!file.endsWith('.js') || files.allFiles.includes(file)) return null
-
- return (
-
- )
- })
+ return getDynamicChunks(this.context, this.props, files)
}
getPreNextScripts() {
- const { scriptLoader } = this.context
-
- return (scriptLoader.beforeInteractive || []).map(
- (file: ScriptLoaderProps) => {
- const { strategy, ...props } = file
- return (
-
- )
- }
- )
+ return getPreNextScripts(this.context, this.props)
}
getScripts(files: DocumentFiles) {
- const {
- assetPrefix,
- buildManifest,
- isDevelopment,
- devOnlyCacheBusterQueryString,
- } = this.context
-
- const normalScripts = files.allFiles.filter((file) => file.endsWith('.js'))
- const lowPriorityScripts = buildManifest.lowPriorityFiles?.filter((file) =>
- file.endsWith('.js')
- )
-
- return [...normalScripts, ...lowPriorityScripts].map((file) => {
- return (
-
- )
- })
+ return getScripts(this.context, this.props, files)
}
getPolyfillScripts() {
- // polyfills.js has to be rendered as nomodule without async
- // It also has to be the first script to load
- const {
- assetPrefix,
- buildManifest,
- devOnlyCacheBusterQueryString,
- } = this.context
-
- return buildManifest.polyfillFiles
- .filter(
- (polyfill) =>
- polyfill.endsWith('.js') && !polyfill.endsWith('.module.js')
- )
- .map((polyfill) => (
-
- ))
+ return getPolyfillScripts(this.context, this.props)
}
static getInlineScriptSource(documentProps: Readonly): string {
@@ -752,6 +805,7 @@ export class NextScript extends Component {
unstable_runtimeJS,
docComponentsRendered,
devOnlyCacheBusterQueryString,
+ disableOptimizedLoading,
} = this.context
const disableRuntimeJS = unstable_runtimeJS === false
@@ -841,10 +895,16 @@ export class NextScript extends Component {
}}
/>
)}
- {!disableRuntimeJS && this.getPolyfillScripts()}
- {!disableRuntimeJS && this.getPreNextScripts()}
- {disableRuntimeJS ? null : this.getDynamicChunks(files)}
- {disableRuntimeJS ? null : this.getScripts(files)}
+ {disableOptimizedLoading &&
+ !disableRuntimeJS &&
+ this.getPolyfillScripts()}
+ {disableOptimizedLoading &&
+ !disableRuntimeJS &&
+ this.getPreNextScripts()}
+ {disableOptimizedLoading &&
+ !disableRuntimeJS &&
+ this.getDynamicChunks(files)}
+ {disableOptimizedLoading && !disableRuntimeJS && this.getScripts(files)}
>
)
}
diff --git a/test/integration/optimized-loading/next.config.js b/test/integration/optimized-loading/next.config.js
new file mode 100644
index 0000000000000..85ca85bea7b4c
--- /dev/null
+++ b/test/integration/optimized-loading/next.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ experimental: { disableOptimizedLoading: false },
+}
diff --git a/test/integration/optimized-loading/pages/index.js b/test/integration/optimized-loading/pages/index.js
new file mode 100644
index 0000000000000..33e1dc15b43c1
--- /dev/null
+++ b/test/integration/optimized-loading/pages/index.js
@@ -0,0 +1,10 @@
+import Link from 'next/link'
+
+export default () => (
+