diff --git a/docs/advanced-features/custom-error-page.md b/docs/advanced-features/custom-error-page.md
index 9b2971d0765e2..243a76ce5164b 100644
--- a/docs/advanced-features/custom-error-page.md
+++ b/docs/advanced-features/custom-error-page.md
@@ -2,6 +2,8 @@
description: Override and extend the built-in Error page to handle custom errors.
---
+# Custom Error Page
+
## 404 Page
A 404 page may be accessed very often. Server-rendering an error page for every visit increases the load of the Next.js server. This can result in increased costs and slow experiences.
diff --git a/docs/advanced-features/static-html-export.md b/docs/advanced-features/static-html-export.md
index becfd255d69ed..969a2d20a7c25 100644
--- a/docs/advanced-features/static-html-export.md
+++ b/docs/advanced-features/static-html-export.md
@@ -21,7 +21,7 @@ The exported app supports almost every feature of Next.js, including dynamic rou
>
> If you're looking to make a hybrid site where only _some_ pages are prerendered to static HTML, Next.js already does that automatically for you! Read up on [Automatic Static Optimization](/docs/advanced-features/automatic-static-optimization.md) for details.
>
-> `next export` also causes features like Incremental Static Generation and Regeneration to be disabled, as they require `next start` or a serverless deployment to function.
+> `next export` also causes features like [Incremental Static Generation](/docs/basic-features/data-fetching.md#fallback-true) and [Regeneration](/docs/basic-features/data-fetching.md#incremental-static-regeneration) to be disabled, as they require [`next start`](/docs/api-reference/cli.md#production) or a serverless deployment to function.
## How to use it
@@ -53,7 +53,9 @@ For more advanced scenarios, you can define a parameter called [`exportPathMap`]
## Deployment
-You can read about deploying your Next.js application in the [deployment section](/docs/deployment.md).
+By default, `next export` will generate an `out` directory, which can be served by any static hosting service or CDN.
+
+> We strongly recommend using [Vercel](https://vercel.com/) even if your Next.js app is fully static. [Vercel](https://vercel.com/) is optimized to make static Next.js apps blazingly fast. `next export` works with Zero Config deployments on Vercel.
## Caveats
@@ -62,11 +64,10 @@ You can read about deploying your Next.js application in the [deployment section
- `getInitialProps` cannot be used alongside `getStaticProps` or `getStaticPaths` on any given page. If you have dynamic routes, instead of using `getStaticPaths` you'll need to configure the [`exportPathMap`](/docs/api-reference/next.config.js/exportPathMap.md) parameter in your [`next.config.js`](/docs/api-reference/next.config.js/introduction.md) file to let the exporter know which HTML files it should output.
- When `getInitialProps` is called during export, the `req` and `res` fields of its [`context`](/docs/api-reference/data-fetching/getInitialProps.md#context-object) parameter will be empty objects, since during export there is no server running.
- `getInitialProps` **will be called on every client-side navigation**, if you'd like to only fetch data at build-time, switch to `getStaticProps`.
- - `getInitialProps` cannot use Node.js-specific libraries or the file system like `getStaticProps` can. `getInitialProps` must fetch from an API.
+ - `getInitialProps` should fetch from an API and cannot use Node.js-specific libraries or the file system like `getStaticProps` can.
- For static export, the `getStaticProps` API is always preferred over `getInitialProps`: it's recommended you convert your pages to use the `getStaticProps` if possible.
+ It's recommended to use and migrate towards `getStaticProps` over `getInitialProps` whenever possible.
-- The `fallback: true` mode of `getStaticPaths` is not supported when using `next export`.
-- You won't be able to render HTML dynamically when static exporting, as the HTML files are pre-build. Your application can be a hybrid of [Static Generation](/docs/basic-features/pages.md#static-generation) and [Server-Side Rendering](/docs/basic-features/pages.md#server-side-rendering) when you don't use `next export`. You can learn more about it in the [pages section](/docs/basic-features/pages.md).
+- The [`fallback: true`](/docs/basic-features/data-fetching.md#fallback-true) mode of `getStaticPaths` is not supported when using `next export`.
- [API Routes](/docs/api-routes/introduction.md) are not supported by this method because they can't be prerendered to HTML.
- [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering) cannot be used within pages because the method requires a server. Consider using [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) instead.
diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md
index 5b63772e85a2d..bd9959252349e 100644
--- a/docs/api-reference/cli.md
+++ b/docs/api-reference/cli.md
@@ -48,7 +48,7 @@ NODE_OPTIONS='--inspect' next
The first load is colored green, yellow, or red. Aim for green for performant applications.
-You can enable production profiling for React with the `--profile` flag in `next build`. This requires Next.js 9.5:
+You can enable production profiling for React with the `--profile` flag in `next build`. This requires [Next.js 9.5](https://nextjs.org/blog/next-9-5):
```bash
next build --profile
diff --git a/docs/api-reference/data-fetching/getInitialProps.md b/docs/api-reference/data-fetching/getInitialProps.md
index 6e428820ecd96..cc0d690119a27 100644
--- a/docs/api-reference/data-fetching/getInitialProps.md
+++ b/docs/api-reference/data-fetching/getInitialProps.md
@@ -4,19 +4,11 @@ description: Enable Server-Side Rendering in a page and do initial data populati
# getInitialProps
-> **Recommended: [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) or [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering)**
+> **Recommended: [`getStaticProps`](/docs/basic-features/data-fetching.md#getstaticprops-static-generation) or [`getServerSideProps`](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering)**.
>
> If you're using Next.js 9.3 or newer, we recommend that you use `getStaticProps` or `getServerSideProps` instead of `getInitialProps`.
>
-> These new data fetching methods allow you to have a granular choice between static generation and server-side rendering.
-> Learn more on the documentation for [Pages](/docs/basic-features/pages.md) and [Data fetching](/docs/basic-features/data-fetching.md):
-
-
- Examples
-
-
+> These new data fetching methods allow you to have a granular choice between static generation and server-side rendering. Learn more on the documentation for [Pages](/docs/basic-features/pages.md) and [Data fetching](/docs/basic-features/data-fetching.md).
`getInitialProps` enables [server-side rendering](/docs/basic-features/pages.md#server-side-rendering) in a page and allows you to do **initial data population**, it means sending the [page](/docs/basic-features/pages.md) with the data already populated from the server. This is especially useful for [SEO](https://en.wikipedia.org/wiki/Search_engine_optimization).
diff --git a/docs/api-reference/next.config.js/environment-variables.md b/docs/api-reference/next.config.js/environment-variables.md
index 9a3536fa0c9b3..a0766ef442dbc 100644
--- a/docs/api-reference/next.config.js/environment-variables.md
+++ b/docs/api-reference/next.config.js/environment-variables.md
@@ -4,7 +4,7 @@ description: Learn to add and access environment variables in your Next.js appli
# Environment Variables
-> Since the release of Next.js 9.4 we now have a more intuitive and ergonomic experience for [adding environment variables](/docs/basic-features/environment-variables.md). Give it a try!
+> Since the release of [Next.js 9.4](https://nextjs.org/blog/next-9-4) we now have a more intuitive and ergonomic experience for [adding environment variables](/docs/basic-features/environment-variables.md). Give it a try!
Examples
diff --git a/docs/api-routes/dynamic-api-routes.md b/docs/api-routes/dynamic-api-routes.md
index 8c49755693a6b..272f68025f7e2 100644
--- a/docs/api-routes/dynamic-api-routes.md
+++ b/docs/api-routes/dynamic-api-routes.md
@@ -31,7 +31,7 @@ Now, a request to `/api/post/abc` will respond with the text: `Post: abc`.
A very common RESTful pattern is to set up routes like this:
-- `GET api/posts/` - gets a list of posts, probably paginated
+- `GET api/posts` - gets a list of posts, probably paginated
- `GET api/posts/12345` - gets post id 12345
We can model this in two ways:
@@ -40,11 +40,10 @@ We can model this in two ways:
- `/api/posts.js`
- `/api/posts/[postId].js`
- Option 2:
-
- `/api/posts/index.js`
- `/api/posts/[postId].js`
-Both are equivalent. A third option of only using `/api/posts/[postId].js` is not valid because Dynamic Routes (including Catch-all routes - see below) do not have an `undefined` state and `GET api/posts/` will not match `/api/posts/[postId].js` under any circumstances.
+Both are equivalent. A third option of only using `/api/posts/[postId].js` is not valid because Dynamic Routes (including Catch-all routes - see below) do not have an `undefined` state and `GET api/posts` will not match `/api/posts/[postId].js` under any circumstances.
### Catch all API routes
diff --git a/docs/api-routes/introduction.md b/docs/api-routes/introduction.md
index eac86c6ab3db0..9d71dc4aca031 100644
--- a/docs/api-routes/introduction.md
+++ b/docs/api-routes/introduction.md
@@ -17,7 +17,7 @@ description: Next.js supports API Routes, which allow you to build your API with
API routes provide a straightforward solution to build your **API** with Next.js.
-Any file inside the folder `pages/api` is mapped to `/api/*` and will be treated as an API endpoint instead of a `page`.
+Any file inside the folder `pages/api` is mapped to `/api/*` and will be treated as an API endpoint instead of a `page`. They are server-side only bundles and won't increase your client-side bundle size.
For example, the following API route `pages/api/user.js` handles a `json` response:
@@ -48,9 +48,10 @@ export default (req, res) => {
To fetch API endpoints, take a look into any of the examples at the start of this section.
-> API Routes [do not specify CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), meaning they are **same-origin only** by default. You can customize such behavior by wrapping the request handler with the [cors middleware](/docs/api-routes/api-middlewares.md#connectexpress-middleware-support).
+## Caveats
-> API Routes do not increase your client-side bundle size. They are server-side only bundles.
+- API Routes [do not specify CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), meaning they are **same-origin only** by default. You can customize such behavior by wrapping the request handler with the [cors middleware](/docs/api-routes/api-middlewares.md#connectexpress-middleware-support).
+- API Routes can't be used with [`next export`](/docs/advanced-features/static-html-export.md)
## Related
diff --git a/docs/basic-features/fast-refresh.md b/docs/basic-features/fast-refresh.md
index 0d6040eb9050b..5df2d35968a98 100644
--- a/docs/basic-features/fast-refresh.md
+++ b/docs/basic-features/fast-refresh.md
@@ -108,5 +108,5 @@ with an empty array of dependencies would still re-run once during Fast Refresh.
However, writing code resilient to occasional re-running of `useEffect` is a good practice even
without Fast Refresh. It will make it easier for you to introduce new dependencies to it later on
-and it's enforced by [React Strict Mode](/docs/api-reference/next.config.js/react-strict-mode),
+and it's enforced by [React Strict Mode](/docs/api-reference/next.config.js/react-strict-mode.md),
which we highly recommend enabling.
diff --git a/docs/basic-features/supported-browsers-features.md b/docs/basic-features/supported-browsers-features.md
index e073c9433608f..9da03b356a6b1 100644
--- a/docs/basic-features/supported-browsers-features.md
+++ b/docs/basic-features/supported-browsers-features.md
@@ -26,7 +26,7 @@ In addition to `fetch()` on the client side, Next.js polyfills `fetch()` in the
If your own code or any external npm dependencies require features not supported by your target browsers, you need to add polyfills yourself.
-In this case, you should add a top-level import for the **specific polyfill** you need in your [Custom ``](docs/advanced-features/custom-app.md) or the individual component.
+In this case, you should add a top-level import for the **specific polyfill** you need in your [Custom ``](/docs/advanced-features/custom-app.md) or the individual component.
## JavaScript Language Features
diff --git a/docs/deployment.md b/docs/deployment.md
index eddcc9acd5949..fc1a806e9ccf8 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -72,6 +72,4 @@ Make sure your `package.json` has the `"build"` and `"start"` scripts:
### Static HTML Export
-If you’d like to do a static HTML export of your Next.js app, follow the directions on [our documentation](/docs/advanced-features/static-html-export.md). By default, `next export` will generate an `out` directory, which can be served by any static hosting service or CDN.
-
-> We strongly recommend using [Vercel](https://vercel.com/) even if your Next.js app is fully static. [Vercel](https://vercel.com/) is optimized to make static Next.js apps blazingly fast. `next export` works with Zero Config deployments on Vercel.
+If you’d like to do a static HTML export of your Next.js app, follow the directions on [our documentation](/docs/advanced-features/static-html-export.md).
diff --git a/docs/faq.md b/docs/faq.md
index d13c94b2ff984..8593eed265d7f 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -74,6 +74,6 @@ description: Get to know more about Next.js with the frequently asked questions.
- Can I make a Next.js Progressive Web App?
- Yes! Here's an example .
+ Can I make a Next.js Progressive Web App (PWA)?
+ Yes! Check out our PWA Example to see how it works.
diff --git a/errors/missing-document-component.md b/errors/missing-document-component.md
new file mode 100644
index 0000000000000..89fcdb1081eb0
--- /dev/null
+++ b/errors/missing-document-component.md
@@ -0,0 +1,13 @@
+# Missing Document Components
+
+#### Why This Error Occurred
+
+In your custom `pages/_document` an expected sub-component was not rendered.
+
+#### Possible Ways to Fix It
+
+Make sure to import and render all of the expected `Document` components.
+
+### Useful Links
+
+- [Custom Document Docs](https://nextjs.org/docs/advanced-features/custom-document)
diff --git a/examples/data-fetch/README.md b/examples/data-fetch/README.md
index d7146d911d652..6dc5c58e7ed8c 100644
--- a/examples/data-fetch/README.md
+++ b/examples/data-fetch/README.md
@@ -1,9 +1,9 @@
# Data fetch example
Next.js was conceived to make it easy to create universal apps. That's why fetching data
-on the server and the client when necessary is so easy with Next.
+on the server and the client when necessary is so easy with Next.js.
-Using `getStaticProps` fetches data at build time from a page, Next.js will pre-render this page at build time.
+By using `getStaticProps` Next.js will fetch data at build time from a page, and pre-render the page to static assets.
## Deploy your own
@@ -13,8 +13,6 @@ Deploy the example using [Vercel](https://vercel.com):
## How to use
-### Using `create-next-app`
-
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
```bash
@@ -23,23 +21,4 @@ npx create-next-app --example data-fetch data-fetch-app
yarn create next-app --example data-fetch data-fetch-app
```
-### Download manually
-
-Download the example:
-
-```bash
-curl https://codeload.github.com/vercel/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/data-fetch
-cd data-fetch
-```
-
-Install it and run:
-
-```bash
-npm install
-npm run dev
-# or
-yarn
-yarn dev
-```
-
Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
diff --git a/examples/data-fetch/package.json b/examples/data-fetch/package.json
index 55d94b79a3ea6..17aacdb9fe651 100644
--- a/examples/data-fetch/package.json
+++ b/examples/data-fetch/package.json
@@ -8,9 +8,8 @@
},
"dependencies": {
"next": "latest",
- "node-fetch": "^2.6.0",
- "react": "^16.8.4",
- "react-dom": "^16.8.4"
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1"
},
- "license": "ISC"
+ "license": "MIT"
}
diff --git a/examples/data-fetch/pages/index.js b/examples/data-fetch/pages/index.js
index 90dc176afd3cd..d668c07e8fd6c 100644
--- a/examples/data-fetch/pages/index.js
+++ b/examples/data-fetch/pages/index.js
@@ -1,5 +1,4 @@
import Link from 'next/link'
-import fetch from 'node-fetch'
function Index({ stars }) {
return (
diff --git a/examples/data-fetch/pages/preact-stars.js b/examples/data-fetch/pages/preact-stars.js
index a8435ed70c3ff..0db33607dde37 100644
--- a/examples/data-fetch/pages/preact-stars.js
+++ b/examples/data-fetch/pages/preact-stars.js
@@ -1,5 +1,4 @@
import Link from 'next/link'
-import fetch from 'node-fetch'
function PreactStars({ stars }) {
return (
diff --git a/examples/with-sentry/next.config.js b/examples/with-sentry/next.config.js
index 272fef9c62884..7d5c90f380c5a 100644
--- a/examples/with-sentry/next.config.js
+++ b/examples/with-sentry/next.config.js
@@ -21,6 +21,7 @@ const COMMIT_SHA =
VERCEL_BITBUCKET_COMMIT_SHA
process.env.SENTRY_DSN = SENTRY_DSN
+const basePath = ''
module.exports = withSourceMaps({
serverRuntimeConfig: {
@@ -63,12 +64,12 @@ module.exports = withSourceMaps({
include: '.next',
ignore: ['node_modules'],
stripPrefix: ['webpack://_N_E/'],
- urlPrefix: '~/_next',
+ urlPrefix: `~${basePath}/_next`,
release: COMMIT_SHA,
})
)
}
-
return config
},
+ basePath,
})
diff --git a/examples/z-experimental-refresh/.gitignore b/examples/z-experimental-refresh/.gitignore
deleted file mode 100644
index 1437c53f70bc2..0000000000000
--- a/examples/z-experimental-refresh/.gitignore
+++ /dev/null
@@ -1,34 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-# local env files
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
-# vercel
-.vercel
diff --git a/examples/z-experimental-refresh/README.md b/examples/z-experimental-refresh/README.md
deleted file mode 100644
index 078c5d65e90b5..0000000000000
--- a/examples/z-experimental-refresh/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# z-experimental-refresh
-
-This is an **experimental** demo of React Fast Refresh.
-Please do not use these features in your application or project (yet).
-
-## Usage
-
-Run the following command to get started:
-
-```bash
-yarn dev
-# or
-npm run dev
-```
diff --git a/examples/z-experimental-refresh/components/ClickCount.js b/examples/z-experimental-refresh/components/ClickCount.js
deleted file mode 100644
index 283266612179e..0000000000000
--- a/examples/z-experimental-refresh/components/ClickCount.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { useCallback, useState } from 'react'
-import styles from './ClickCount.module.css'
-
-export default function ClickCount() {
- const [count, setCount] = useState(0)
- const increment = useCallback(() => {
- setCount((v) => v + 1)
- }, [setCount])
-
- return (
-
- Clicks: {count}
-
- )
-}
diff --git a/examples/z-experimental-refresh/components/ClickCount.module.css b/examples/z-experimental-refresh/components/ClickCount.module.css
deleted file mode 100644
index 3cba6f351a456..0000000000000
--- a/examples/z-experimental-refresh/components/ClickCount.module.css
+++ /dev/null
@@ -1,32 +0,0 @@
-button.btn {
- margin: 0;
- border: 1px solid #d1d1d1;
- border-radius: 5px;
- padding: 0.5em;
- vertical-align: middle;
- white-space: normal;
- background: none;
- line-height: 1;
- font-size: 1rem;
- font-family: inherit;
- transition: all 0.2s ease;
-}
-
-button.btn {
- padding: 0.65em 1em;
- background: #0076ff;
- color: #fff;
- border: none;
- cursor: pointer;
- transition: all 0.2s ease;
-}
-button.btn:focus {
- outline: 0;
- border-color: #0076ff;
-}
-button.btn:hover {
- background: rgba(0, 118, 255, 0.8);
-}
-button.btn:focus {
- box-shadow: 0 0 0 2px rgba(0, 118, 255, 0.5);
-}
diff --git a/examples/z-experimental-refresh/global.css b/examples/z-experimental-refresh/global.css
deleted file mode 100644
index e28fd3dfe7a37..0000000000000
--- a/examples/z-experimental-refresh/global.css
+++ /dev/null
@@ -1,41 +0,0 @@
-body {
- font-family: 'SF Pro Text', 'SF Pro Icons', 'Helvetica Neue', 'Helvetica',
- 'Arial', sans-serif;
- padding: 20px 20px 60px;
- max-width: 680px;
- margin: 0 auto;
- font-size: 16px;
- line-height: 1.65;
- word-break: break-word;
- font-kerning: auto;
- font-variant: normal;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- text-rendering: optimizeLegibility;
- hyphens: auto;
-}
-
-h2,
-h3,
-h4 {
- margin-top: 1.5em;
-}
-
-code,
-pre {
- font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
- Bitstream Vera Sans Mono, Courier New, monospace, serif;
- font-size: 0.92em;
- color: #d400ff;
-}
-
-code:before,
-code:after {
- content: '`';
-}
-
-hr {
- border: none;
- border-bottom: 1px solid #efefef;
- margin: 5em auto;
-}
diff --git a/examples/z-experimental-refresh/package.json b/examples/z-experimental-refresh/package.json
deleted file mode 100644
index 97e0becd5a630..0000000000000
--- a/examples/z-experimental-refresh/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "name": "z-experimental-refresh",
- "version": "1.0.0",
- "scripts": {
- "dev": "next",
- "build": "next build",
- "start": "next start"
- },
- "dependencies": {
- "next": "canary",
- "react": "^16.9.0",
- "react-dom": "^16.9.0"
- },
- "license": "MIT"
-}
diff --git a/examples/z-experimental-refresh/pages/_app.js b/examples/z-experimental-refresh/pages/_app.js
deleted file mode 100644
index d305f97bab7f1..0000000000000
--- a/examples/z-experimental-refresh/pages/_app.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import '../global.css'
-
-export default function MyApp({ Component, pageProps }) {
- return
-}
diff --git a/examples/z-experimental-refresh/pages/index.js b/examples/z-experimental-refresh/pages/index.js
deleted file mode 100644
index 6346c5e117737..0000000000000
--- a/examples/z-experimental-refresh/pages/index.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useCallback, useEffect, useState } from 'react'
-import ClickCount from '../components/ClickCount'
-import styles from '../components/ClickCount.module.css'
-
-function a() {
- console.log(
- // hello
- document.body()
- )
-}
-
-function foo() {
- a()
-}
-
-function Home() {
- const [count, setCount] = useState(0)
- const increment = useCallback(() => {
- setCount((v) => v + 1)
- }, [setCount])
-
- useEffect(() => {
- const r = setInterval(() => {
- increment()
- }, 250)
- return () => {
- clearInterval(r)
- }
- }, [increment])
-
- return (
-
- Home
-
-
Auto Incrementing Value
-
Current value: {count}
-
-
-
-
Component with State
-
-
-
-
- {
- setTimeout(() => document.parentNode(), 0)
- foo()
- }}
- >
- Throw an Error
-
-
-
- )
-}
-
-export default Home
diff --git a/lerna.json b/lerna.json
index e88cb15d77dfa..7b24d1e9cddbb 100644
--- a/lerna.json
+++ b/lerna.json
@@ -17,5 +17,5 @@
"registry": "https://registry.npmjs.org/"
}
},
- "version": "9.5.3-canary.20"
+ "version": "9.5.3-canary.21"
}
diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json
index a79c458755614..ee505f6569a9e 100644
--- a/packages/create-next-app/package.json
+++ b/packages/create-next-app/package.json
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"keywords": [
"react",
"next",
diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json
index b2b7f7c76dcff..5b6b487205796 100644
--- a/packages/eslint-plugin-next/package.json
+++ b/packages/eslint-plugin-next/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"description": "ESLint plugin for NextJS.",
"main": "lib/index.js",
"license": "MIT",
diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json
index 62d53ada27dc3..928d386bd50f8 100644
--- a/packages/next-bundle-analyzer/package.json
+++ b/packages/next-bundle-analyzer/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json
index 5c7df344fbd14..829625db0d60e 100644
--- a/packages/next-codemod/package.json
+++ b/packages/next-codemod/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/codemod",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"license": "MIT",
"dependencies": {
"chalk": "4.1.0",
diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json
index 875e4ae23e593..b3c71a342fe13 100644
--- a/packages/next-mdx/package.json
+++ b/packages/next-mdx/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/mdx",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"main": "index.js",
"license": "MIT",
"repository": {
diff --git a/packages/next-plugin-google-analytics/package.json b/packages/next-plugin-google-analytics/package.json
index c35ad8a73d85c..a1cd6adcbef2c 100644
--- a/packages/next-plugin-google-analytics/package.json
+++ b/packages/next-plugin-google-analytics/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-google-analytics",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-google-analytics"
diff --git a/packages/next-plugin-sentry/package.json b/packages/next-plugin-sentry/package.json
index 63c9b9e29de71..537b3d93b754b 100644
--- a/packages/next-plugin-sentry/package.json
+++ b/packages/next-plugin-sentry/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-sentry",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-sentry"
diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json
index f9f9921d9f126..b40824e7a8998 100644
--- a/packages/next-plugin-storybook/package.json
+++ b/packages/next-plugin-storybook/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/plugin-storybook",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"repository": {
"url": "vercel/next.js",
"directory": "packages/next-plugin-storybook"
diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json
index 4b05c023bb0a2..4afafe1384f51 100644
--- a/packages/next-polyfill-nomodule/package.json
+++ b/packages/next-polyfill-nomodule/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/polyfill-nomodule",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
diff --git a/packages/next/build/babel/plugins/no-anonymous-default-export.ts b/packages/next/build/babel/plugins/no-anonymous-default-export.ts
index 32aa89b6815bb..1d21b759fa030 100644
--- a/packages/next/build/babel/plugins/no-anonymous-default-export.ts
+++ b/packages/next/build/babel/plugins/no-anonymous-default-export.ts
@@ -48,6 +48,10 @@ export default function NoAnonymousDefaultExport({
chalk.bold('After'),
chalk.cyan('const Named = () =>
;'),
chalk.cyan('export default Named;'),
+ '',
+ `A codemod is available to fix the most common cases: ${chalk.cyan(
+ 'https://nextjs.link/codemod-ndc'
+ )}`,
].join('\n')
)
break
@@ -67,6 +71,10 @@ export default function NoAnonymousDefaultExport({
'',
chalk.bold('After'),
chalk.cyan('export default function Named() { /* ... */ }'),
+ '',
+ `A codemod is available to fix the most common cases: ${chalk.cyan(
+ 'https://nextjs.link/codemod-ndc'
+ )}`,
].join('\n')
)
}
diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index acfece465c8b5..31ee7da60ec97 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -238,7 +238,7 @@ export default async function getBaseWebpackConfig(
pagesDir,
cwd: dir,
// Webpack 5 has a built-in loader cache
- cache: !config.experimental.unstable_webpack5cache,
+ cache: !isWebpack5,
babelPresetPlugins,
hasModern: !!config.experimental.modern,
development: dev,
@@ -1107,9 +1107,9 @@ export default async function getBaseWebpackConfig(
}
if (isWebpack5) {
- // On by default:
+ // futureEmitAssets is on by default in webpack 5
delete webpackConfig.output?.futureEmitAssets
- // No longer polyfills Node.js modules:
+ // webpack 5 no longer polyfills Node.js modules:
if (webpackConfig.node) delete webpackConfig.node.setImmediate
if (dev) {
@@ -1119,13 +1119,65 @@ export default async function getBaseWebpackConfig(
webpackConfig.optimization.usedExports = false
}
- // Enable webpack 5 caching
- if (config.experimental.unstable_webpack5cache) {
- webpackConfig.cache = {
- type: 'filesystem',
- cacheDirectory: path.join(dir, '.next', 'cache', 'webpack'),
+ const nextPublicVariables = Object.keys(process.env).reduce(
+ (prev: string, key: string) => {
+ if (key.startsWith('NEXT_PUBLIC_')) {
+ return `${prev}|${key}=${process.env[key]}`
+ }
+ return prev
+ },
+ ''
+ )
+ const nextEnvVariables = Object.keys(config.env).reduce(
+ (prev: string, key: string) => {
+ return `${prev}|${key}=${config.env[key]}`
+ },
+ ''
+ )
+
+ const configVars = JSON.stringify({
+ crossOrigin: config.crossOrigin,
+ pageExtensions: config.pageExtensions,
+ trailingSlash: config.trailingSlash,
+ modern: config.experimental.modern,
+ buildActivity: config.devIndicators.buildActivity,
+ autoPrerender: config.devIndicators.autoPrerender,
+ plugins: config.experimental.plugins,
+ reactStrictMode: config.reactStrictMode,
+ reactMode: config.experimental.reactMode,
+ optimizeFonts: config.experimental.optimizeFonts,
+ optimizeImages: config.experimental.optimizeImages,
+ scrollRestoration: config.experimental.scrollRestoration,
+ basePath: config.basePath,
+ pageEnv: config.experimental.pageEnv,
+ excludeDefaultMomentLocales: config.future.excludeDefaultMomentLocales,
+ assetPrefix: config.assetPrefix,
+ target,
+ reactProductionProfiling,
+ })
+
+ const cache: any = {
+ type: 'filesystem',
+ // Includes:
+ // - Next.js version
+ // - NEXT_PUBLIC_ variable values (they affect caching) TODO: make this module usage only
+ // - next.config.js `env` key
+ // - next.config.js keys that affect compilation
+ version: `${process.env.__NEXT_VERSION}|${nextPublicVariables}|${nextEnvVariables}|${configVars}`,
+ cacheDirectory: path.join(dir, '.next', 'cache', 'webpack'),
+ }
+
+ // Adds `next.config.js` as a buildDependency when custom webpack config is provided
+ if (config.webpack && config.configFile) {
+ cache.buildDependencies = {
+ config: [config.configFile],
}
}
+
+ webpackConfig.cache = cache
+
+ // @ts-ignore TODO: remove ignore when webpack 5 is stable
+ webpackConfig.optimization.realContentHash = false
}
webpackConfig = await buildConfiguration(webpackConfig, {
diff --git a/packages/next/build/webpack/loaders/next-serverless-loader.ts b/packages/next/build/webpack/loaders/next-serverless-loader.ts
index 2b0ffc7301e00..8c18f4ec7f488 100644
--- a/packages/next/build/webpack/loaders/next-serverless-loader.ts
+++ b/packages/next/build/webpack/loaders/next-serverless-loader.ts
@@ -399,9 +399,7 @@ const nextServerlessLoader: loader.Loader = function () {
pageIsDynamicRoute
? `
const params = (
- fromExport &&
- !getStaticProps &&
- !getServerSideProps
+ fromExport
) ? {}
: normalizeDynamicRouteParams(
trustQuery
diff --git a/packages/next/build/webpack/plugins/react-loadable-plugin.ts b/packages/next/build/webpack/plugins/react-loadable-plugin.ts
index bb6810c094dfe..c2f3400b4fa56 100644
--- a/packages/next/build/webpack/plugins/react-loadable-plugin.ts
+++ b/packages/next/build/webpack/plugins/react-loadable-plugin.ts
@@ -22,9 +22,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR
// Modified to strip out unneeded results for Next's specific use case
import webpack, {
- Compiler,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
compilation as CompilationType,
+ Compiler,
} from 'webpack'
import sources from 'webpack-sources'
@@ -65,7 +65,12 @@ function buildManifest(
chunkGroup.chunks.forEach((chunk: any) => {
chunk.files.forEach((file: string) => {
- if (!file.match(/\.js$/) || !file.match(/^static\/chunks\//)) {
+ if (
+ !(
+ (file.endsWith('.js') || file.endsWith('.css')) &&
+ file.match(/^static\/(chunks|css)\//)
+ )
+ ) {
return
}
@@ -85,10 +90,7 @@ function buildManifest(
continue
}
- manifest[request].push({
- id,
- file,
- })
+ manifest[request].push({ id, file })
}
})
})
diff --git a/packages/next/next-server/lib/utils.ts b/packages/next/next-server/lib/utils.ts
index ca0f1e0d28312..93b53fe693c1c 100644
--- a/packages/next/next-server/lib/utils.ts
+++ b/packages/next/next-server/lib/utils.ts
@@ -166,6 +166,12 @@ export type DocumentInitialProps = RenderPageResult & {
export type DocumentProps = DocumentInitialProps & {
__NEXT_DATA__: NEXT_DATA
dangerousAsPath: string
+ docComponentsRendered: {
+ Html?: boolean
+ Main?: boolean
+ Head?: boolean
+ NextScript?: boolean
+ }
buildManifest: BuildManifest
ampPath: string
inAmpMode: boolean
diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts
index c9eefdca419a6..3de166a548902 100644
--- a/packages/next/next-server/server/config.ts
+++ b/packages/next/next-server/server/config.ts
@@ -54,7 +54,6 @@ const defaultConfig: { [key: string]: any } = {
optimizeFonts: false,
optimizeImages: false,
scrollRestoration: false,
- unstable_webpack5cache: false,
},
future: {
excludeDefaultMomentLocales: false,
@@ -277,7 +276,11 @@ export default function loadConfig(
)
}
- return assignDefaults({ configOrigin: CONFIG_FILE, ...userConfig })
+ return assignDefaults({
+ configOrigin: CONFIG_FILE,
+ configFile: path,
+ ...userConfig,
+ })
} else {
const configBaseName = basename(CONFIG_FILE, extname(CONFIG_FILE))
const nonJsPath = findUp.sync(
diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx
index c473467d6b875..5172d948a07a2 100644
--- a/packages/next/next-server/server/render.tsx
+++ b/packages/next/next-server/server/render.tsx
@@ -41,6 +41,7 @@ import {
loadGetInitialProps,
NextComponentType,
RenderPage,
+ DocumentProps,
} from '../lib/utils'
import { tryGetPreviewData, __ApiPreviewProps } from './api-utils'
import { denormalizePagePath } from './denormalize-page-path'
@@ -158,6 +159,7 @@ function renderDocument(
Document: DocumentType,
{
buildManifest,
+ docComponentsRendered,
props,
docProps,
pathname,
@@ -188,6 +190,7 @@ function renderDocument(
devOnlyCacheBusterQueryString,
}: RenderOpts & {
props: any
+ docComponentsRendered: DocumentProps['docComponentsRendered']
docProps: DocumentInitialProps
pathname: string
query: ParsedUrlQuery
@@ -233,6 +236,7 @@ function renderDocument(
appGip, // whether the _app has getInitialProps
},
buildManifest,
+ docComponentsRendered,
dangerousAsPath,
canonicalBase,
ampPath,
@@ -402,7 +406,7 @@ export async function renderToHTML(
)
}
- if (isAutoExport) {
+ if (isAutoExport || isFallback) {
// remove query values except ones that will be set during export
query = {
...(query.amp
@@ -759,8 +763,11 @@ export async function renderToHTML(
renderOpts.inAmpMode = inAmpMode
renderOpts.hybridAmp = hybridAmp
+ const docComponentsRendered: DocumentProps['docComponentsRendered'] = {}
+
let html = renderDocument(Document, {
...renderOpts,
+ docComponentsRendered,
buildManifest: filteredBuildManifest,
// Only enabled in production as development mode has features relying on HMR (style injection for example)
unstable_runtimeJS:
@@ -787,6 +794,29 @@ export async function renderToHTML(
devOnlyCacheBusterQueryString,
})
+ if (process.env.NODE_ENV !== 'production') {
+ const nonRenderedComponents = []
+ const expectedDocComponents = ['Main', 'Head', 'NextScript', 'Html']
+
+ for (const comp of expectedDocComponents) {
+ if (!(docComponentsRendered as any)[comp]) {
+ nonRenderedComponents.push(comp)
+ }
+ }
+ const plural = nonRenderedComponents.length !== 1 ? 's' : ''
+
+ if (nonRenderedComponents.length) {
+ console.warn(
+ `Expected Document Component${plural} ${nonRenderedComponents.join(
+ ', '
+ )} ${
+ plural ? 'were' : 'was'
+ } not rendered. Make sure you render them in your custom \`_document\`\n` +
+ `See more info here https://err.sh/next.js/missing-document-component`
+ )
+ }
+ }
+
if (inAmpMode && html) {
// inject HTML to AMP_RENDER_TARGET to allow rendering
// directly to body in AMP mode
diff --git a/packages/next/package.json b/packages/next/package.json
index 6bcf76fc4617c..f43c2483e78c8 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -1,6 +1,6 @@
{
"name": "next",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"description": "The React Framework",
"main": "./dist/server/next.js",
"license": "MIT",
@@ -77,8 +77,8 @@
"@babel/preset-typescript": "7.9.0",
"@babel/runtime": "7.9.6",
"@babel/types": "7.9.6",
- "@next/react-dev-overlay": "9.5.3-canary.20",
- "@next/react-refresh-utils": "9.5.3-canary.20",
+ "@next/react-dev-overlay": "9.5.3-canary.21",
+ "@next/react-refresh-utils": "9.5.3-canary.21",
"ast-types": "0.13.2",
"babel-plugin-syntax-jsx": "6.18.0",
"babel-plugin-transform-define": "2.0.0",
@@ -123,7 +123,7 @@
"react-dom": "^16.6.0"
},
"devDependencies": {
- "@next/polyfill-nomodule": "9.5.3-canary.20",
+ "@next/polyfill-nomodule": "9.5.3-canary.21",
"@taskr/clear": "1.1.0",
"@taskr/esnext": "1.1.0",
"@taskr/watch": "1.1.0",
diff --git a/packages/next/pages/_document.tsx b/packages/next/pages/_document.tsx
index 96b16f7385b12..8b90a4846ef44 100644
--- a/packages/next/pages/_document.tsx
+++ b/packages/next/pages/_document.tsx
@@ -25,9 +25,9 @@ export type OriginProps = {
crossOrigin?: string
}
-function dedupe(bundles: any[]): any[] {
- const files = new Set()
- const kept = []
+function dedupe(bundles: T[]): T[] {
+ const files = new Set()
+ const kept: T[] = []
for (const bundle of bundles) {
if (files.has(bundle.file)) continue
@@ -123,7 +123,12 @@ export function Html(
HTMLHtmlElement
>
) {
- const { inAmpMode } = useContext(DocumentComponentContext)
+ const { inAmpMode, docComponentsRendered } = useContext(
+ DocumentComponentContext
+ )
+
+ docComponentsRendered.Html = true
+
return (
getCssLinks(files: DocumentFiles): JSX.Element[] | null {
- const { assetPrefix, devOnlyCacheBusterQueryString } = this.context
+ const {
+ assetPrefix,
+ devOnlyCacheBusterQueryString,
+ dynamicImports,
+ } = this.context
const cssFiles = files.allFiles.filter((f) => f.endsWith('.css'))
const sharedFiles = new Set(files.sharedFiles)
+ let dynamicCssFiles = dedupe(
+ dynamicImports.filter((f) => f.file.endsWith('.css'))
+ ).map((f) => f.file)
+ if (dynamicCssFiles.length) {
+ const existing = new Set(cssFiles)
+ dynamicCssFiles = dynamicCssFiles.filter(
+ (f) => !(existing.has(f) || sharedFiles.has(f))
+ )
+ cssFiles.push(...dynamicCssFiles)
+ }
+
const cssLinkElements: JSX.Element[] = []
cssFiles.forEach((file) => {
const isSharedFile = sharedFiles.has(file)
@@ -188,7 +208,6 @@ export class Head extends Component<
/>
)
})
-
return cssLinkElements.length === 0 ? null : cssLinkElements
}
@@ -201,7 +220,7 @@ export class Head extends Component<
return (
dedupe(dynamicImports)
- .map((bundle: any) => {
+ .map((bundle) => {
// `dynamicImports` will contain both `.js` and `.module.js` when the
// feature is enabled. This clause will filter down to the modern
// variants only.
@@ -288,6 +307,8 @@ export class Head extends Component<
} = this.context
const disableRuntimeJS = unstable_runtimeJS === false
+ this.context.docComponentsRendered.Head = true
+
let { head } = this.context
let children = this.props.children
// show a warning if Head contains (only in development)
@@ -501,7 +522,12 @@ export class Head extends Component<
}
export function Main() {
- const { inAmpMode, html } = useContext(DocumentComponentContext)
+ const { inAmpMode, html, docComponentsRendered } = useContext(
+ DocumentComponentContext
+ )
+
+ docComponentsRendered.Main = true
+
if (inAmpMode) return <>{AMP_RENDER_TARGET}>
return
}
@@ -528,7 +554,7 @@ export class NextScript extends Component {
devOnlyCacheBusterQueryString,
} = this.context
- return dedupe(dynamicImports).map((bundle: any) => {
+ return dedupe(dynamicImports).map((bundle) => {
let modernProps = {}
if (process.env.__NEXT_MODERN_BUILD) {
modernProps = bundle.file.endsWith('.module.js')
@@ -642,10 +668,13 @@ export class NextScript extends Component {
inAmpMode,
buildManifest,
unstable_runtimeJS,
+ docComponentsRendered,
devOnlyCacheBusterQueryString,
} = this.context
const disableRuntimeJS = unstable_runtimeJS === false
+ docComponentsRendered.NextScript = true
+
if (inAmpMode) {
if (process.env.NODE_ENV === 'production') {
return null
diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json
index 87a32842f60c7..e602a80e40e7c 100644
--- a/packages/react-dev-overlay/package.json
+++ b/packages/react-dev-overlay/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-dev-overlay",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"description": "A development-only overlay for developing React applications.",
"repository": {
"url": "vercel/next.js",
diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json
index ae163a90c7fdd..01668d624eab0 100644
--- a/packages/react-refresh-utils/package.json
+++ b/packages/react-refresh-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@next/react-refresh-utils",
- "version": "9.5.3-canary.20",
+ "version": "9.5.3-canary.21",
"description": "An experimental package providing utilities for React Refresh.",
"repository": {
"url": "vercel/next.js",
diff --git a/test/integration/fallback-route-params/pages/[slug].js b/test/integration/fallback-route-params/pages/[slug].js
new file mode 100644
index 0000000000000..8259ea56dc20c
--- /dev/null
+++ b/test/integration/fallback-route-params/pages/[slug].js
@@ -0,0 +1,34 @@
+import { useRouter } from 'next/router'
+
+export const getStaticProps = () => {
+ return {
+ props: {
+ world: 'world',
+ },
+ }
+}
+
+export const getStaticPaths = () => {
+ return {
+ paths: [],
+ fallback: true,
+ }
+}
+
+export default function Page({ world }) {
+ const router = useRouter()
+
+ if (typeof window !== 'undefined' && !window.setInitialSlug) {
+ window.setInitialSlug = true
+ window.initialSlug = router.query.slug
+ }
+
+ if (router.isFallback) return 'Loading...'
+
+ return (
+ <>
+ hello {world}
+ {JSON.stringify(router.query)}
+ >
+ )
+}
diff --git a/test/integration/fallback-route-params/test/index.test.js b/test/integration/fallback-route-params/test/index.test.js
new file mode 100644
index 0000000000000..877f0b0c1f120
--- /dev/null
+++ b/test/integration/fallback-route-params/test/index.test.js
@@ -0,0 +1,89 @@
+/* eslint-env jest */
+
+import fs from 'fs-extra'
+import { join } from 'path'
+import cheerio from 'cheerio'
+import webdriver from 'next-webdriver'
+import {
+ killApp,
+ findPort,
+ nextBuild,
+ nextStart,
+ renderViaHTTP,
+ launchApp,
+ File,
+} from 'next-test-utils'
+
+jest.setTimeout(1000 * 60 * 2)
+
+const appDir = join(__dirname, '../')
+const nextConfig = new File(join(appDir, 'next.config.js'))
+let appPort
+let app
+
+const runTests = () => {
+ it('should have correct fallback query (skeleton)', async () => {
+ const html = await renderViaHTTP(appPort, '/first')
+ const $ = cheerio.load(html)
+ const { query } = JSON.parse($('#__NEXT_DATA__').text())
+ expect(query).toEqual({})
+ })
+
+ it('should have correct fallback query (hydration)', async () => {
+ const browser = await webdriver(appPort, '/second')
+ const initialSlug = await browser.eval(() => window.initialSlug)
+ expect(initialSlug).toBe(null)
+
+ await browser.waitForElementByCss('#query')
+
+ const hydratedQuery = JSON.parse(
+ await browser.elementByCss('#query').text()
+ )
+ expect(hydratedQuery).toEqual({ slug: 'second' })
+ })
+}
+
+describe('Fallback Dynamic Route Params', () => {
+ describe('dev mode', () => {
+ beforeAll(async () => {
+ await fs.remove(join(appDir, '.next'))
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ })
+ afterAll(() => killApp(app))
+
+ runTests()
+ })
+
+ describe('production mode', () => {
+ beforeAll(async () => {
+ await fs.remove(join(appDir, '.next'))
+ await nextBuild(appDir, [])
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(() => killApp(app))
+
+ runTests()
+ })
+
+ describe('serverless mode', () => {
+ beforeAll(async () => {
+ nextConfig.write(`
+ module.exports = {
+ target: 'experimental-serverless-trace'
+ }
+ `)
+ await fs.remove(join(appDir, '.next'))
+ await nextBuild(appDir, [], { stdout: true })
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(async () => {
+ await killApp(app)
+ nextConfig.delete()
+ })
+
+ runTests()
+ })
+})
diff --git a/test/integration/missing-document-component-error/pages/index.js b/test/integration/missing-document-component-error/pages/index.js
new file mode 100644
index 0000000000000..71d6a609df67c
--- /dev/null
+++ b/test/integration/missing-document-component-error/pages/index.js
@@ -0,0 +1,3 @@
+export default function Index() {
+ return Index page
+}
diff --git a/test/integration/missing-document-component-error/test/index.test.js b/test/integration/missing-document-component-error/test/index.test.js
new file mode 100644
index 0000000000000..5502a0f628d6d
--- /dev/null
+++ b/test/integration/missing-document-component-error/test/index.test.js
@@ -0,0 +1,159 @@
+/* eslint-env jest */
+
+import fs from 'fs-extra'
+import { join } from 'path'
+import {
+ findPort,
+ killApp,
+ launchApp,
+ check,
+ renderViaHTTP,
+} from 'next-test-utils'
+
+jest.setTimeout(1000 * 60 * 2)
+
+const appDir = join(__dirname, '..')
+const docPath = join(appDir, 'pages/_document.js')
+let appPort
+let app
+
+const checkMissing = async (missing = [], docContent) => {
+ await fs.writeFile(docPath, docContent)
+ let stderr = ''
+
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort, {
+ onStderr(msg) {
+ stderr += msg || ''
+ },
+ })
+
+ await renderViaHTTP(appPort, '/')
+
+ await check(() => stderr, new RegExp(`missing-document-component`))
+ await check(() => stderr, new RegExp(`${missing.join(', ')}`))
+
+ await killApp(app)
+ await fs.remove(docPath)
+}
+
+describe('Missing _document components error', () => {
+ it('should detect missing Html component', async () => {
+ await checkMissing(
+ ['Html'],
+ `
+ import Document, { Head, Main, NextScript } from 'next/document'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ `
+ )
+ })
+
+ it('should detect missing Head component', async () => {
+ await checkMissing(
+ ['Head'],
+ `
+ import Document, { Html, Main, NextScript } from 'next/document'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ `
+ )
+ })
+
+ it('should detect missing Main component', async () => {
+ await checkMissing(
+ ['Main'],
+ `
+ import Document, { Html, Head, NextScript } from 'next/document'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ `
+ )
+ })
+
+ it('should detect missing NextScript component', async () => {
+ await checkMissing(
+ ['NextScript'],
+ `
+ import Document, { Html, Head, Main } from 'next/document'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ `
+ )
+ })
+
+ it('should detect multiple missing document components', async () => {
+ await checkMissing(
+ ['Head', 'NextScript'],
+ `
+ import Document, { Html, Main } from 'next/document'
+
+ class MyDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+ )
+ }
+ }
+
+ export default MyDocument
+ `
+ )
+ })
+})
diff --git a/test/integration/production/components/dynamic-css/many-imports/with-css-1.js b/test/integration/production/components/dynamic-css/many-imports/with-css-1.js
new file mode 100644
index 0000000000000..1df8d47c21e5f
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/many-imports/with-css-1.js
@@ -0,0 +1,3 @@
+import styles from './with-css-1.module.css'
+
+export default () => With CSS 1
diff --git a/test/integration/production/components/dynamic-css/many-imports/with-css-1.module.css b/test/integration/production/components/dynamic-css/many-imports/with-css-1.module.css
new file mode 100644
index 0000000000000..69fda78f79397
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/many-imports/with-css-1.module.css
@@ -0,0 +1,3 @@
+.content {
+ color: inherit;
+}
diff --git a/test/integration/production/components/dynamic-css/many-imports/with-css-2.js b/test/integration/production/components/dynamic-css/many-imports/with-css-2.js
new file mode 100644
index 0000000000000..1112809cb0b39
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/many-imports/with-css-2.js
@@ -0,0 +1,3 @@
+import styles from './with-css-2.module.css'
+
+export default () => With CSS 2
diff --git a/test/integration/production/components/dynamic-css/many-imports/with-css-2.module.css b/test/integration/production/components/dynamic-css/many-imports/with-css-2.module.css
new file mode 100644
index 0000000000000..69fda78f79397
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/many-imports/with-css-2.module.css
@@ -0,0 +1,3 @@
+.content {
+ color: inherit;
+}
diff --git a/test/integration/production/components/dynamic-css/many-imports/with-css-3.js b/test/integration/production/components/dynamic-css/many-imports/with-css-3.js
new file mode 100644
index 0000000000000..fa4f705cbac5e
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/many-imports/with-css-3.js
@@ -0,0 +1,3 @@
+import styles from './with-css-3.module.css'
+
+export default () => With CSS 3
diff --git a/test/integration/production/components/dynamic-css/many-imports/with-css-3.module.css b/test/integration/production/components/dynamic-css/many-imports/with-css-3.module.css
new file mode 100644
index 0000000000000..69fda78f79397
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/many-imports/with-css-3.module.css
@@ -0,0 +1,3 @@
+.content {
+ color: inherit;
+}
diff --git a/test/integration/production/components/dynamic-css/many-modules/with-css-2.module.css b/test/integration/production/components/dynamic-css/many-modules/with-css-2.module.css
new file mode 100644
index 0000000000000..3efa99e6fb171
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/many-modules/with-css-2.module.css
@@ -0,0 +1,3 @@
+.text {
+ color: red;
+}
diff --git a/test/integration/production/components/dynamic-css/many-modules/with-css.js b/test/integration/production/components/dynamic-css/many-modules/with-css.js
new file mode 100644
index 0000000000000..fc11238a63deb
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/many-modules/with-css.js
@@ -0,0 +1,8 @@
+import styles from './with-css.module.css'
+import styles2 from './with-css-2.module.css'
+
+export default () => (
+
+)
diff --git a/test/integration/production/components/dynamic-css/many-modules/with-css.module.css b/test/integration/production/components/dynamic-css/many-modules/with-css.module.css
new file mode 100644
index 0000000000000..69fda78f79397
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/many-modules/with-css.module.css
@@ -0,0 +1,3 @@
+.content {
+ color: inherit;
+}
diff --git a/test/integration/production/components/dynamic-css/nested/Nested.jsx b/test/integration/production/components/dynamic-css/nested/Nested.jsx
new file mode 100644
index 0000000000000..7cc93b468ad9d
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/nested/Nested.jsx
@@ -0,0 +1,3 @@
+import styles2 from './with-css-2.module.css'
+
+export default () => With CSS
diff --git a/test/integration/production/components/dynamic-css/nested/with-css-2.module.css b/test/integration/production/components/dynamic-css/nested/with-css-2.module.css
new file mode 100644
index 0000000000000..3efa99e6fb171
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/nested/with-css-2.module.css
@@ -0,0 +1,3 @@
+.text {
+ color: red;
+}
diff --git a/test/integration/production/components/dynamic-css/nested/with-css.js b/test/integration/production/components/dynamic-css/nested/with-css.js
new file mode 100644
index 0000000000000..a513d6e9d10b3
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/nested/with-css.js
@@ -0,0 +1,8 @@
+import styles from './with-css.module.css'
+import Nested from './Nested'
+
+export default () => (
+
+
+
+)
diff --git a/test/integration/production/components/dynamic-css/nested/with-css.module.css b/test/integration/production/components/dynamic-css/nested/with-css.module.css
new file mode 100644
index 0000000000000..69fda78f79397
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/nested/with-css.module.css
@@ -0,0 +1,3 @@
+.content {
+ color: inherit;
+}
diff --git a/test/integration/production/components/dynamic-css/no-css.js b/test/integration/production/components/dynamic-css/no-css.js
new file mode 100644
index 0000000000000..380145b1902c9
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/no-css.js
@@ -0,0 +1 @@
+export default () => Without CSS
diff --git a/test/integration/production/components/dynamic-css/shared-css-module/with-css-2.js b/test/integration/production/components/dynamic-css/shared-css-module/with-css-2.js
new file mode 100644
index 0000000000000..f8514fa12614b
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/shared-css-module/with-css-2.js
@@ -0,0 +1,8 @@
+import styles from './with-css-2.module.css'
+import stylesShared from './with-css-shared.module.css'
+
+export default () => (
+
+)
diff --git a/test/integration/production/components/dynamic-css/shared-css-module/with-css-2.module.css b/test/integration/production/components/dynamic-css/shared-css-module/with-css-2.module.css
new file mode 100644
index 0000000000000..69fda78f79397
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/shared-css-module/with-css-2.module.css
@@ -0,0 +1,3 @@
+.content {
+ color: inherit;
+}
diff --git a/test/integration/production/components/dynamic-css/shared-css-module/with-css-shared.module.css b/test/integration/production/components/dynamic-css/shared-css-module/with-css-shared.module.css
new file mode 100644
index 0000000000000..3efa99e6fb171
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/shared-css-module/with-css-shared.module.css
@@ -0,0 +1,3 @@
+.text {
+ color: red;
+}
diff --git a/test/integration/production/components/dynamic-css/shared-css-module/with-css.js b/test/integration/production/components/dynamic-css/shared-css-module/with-css.js
new file mode 100644
index 0000000000000..d0397a8c33d5b
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/shared-css-module/with-css.js
@@ -0,0 +1,8 @@
+import styles from './with-css.module.css'
+import stylesShared from './with-css-shared.module.css'
+
+export default () => (
+
+)
diff --git a/test/integration/production/components/dynamic-css/shared-css-module/with-css.module.css b/test/integration/production/components/dynamic-css/shared-css-module/with-css.module.css
new file mode 100644
index 0000000000000..69fda78f79397
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/shared-css-module/with-css.module.css
@@ -0,0 +1,3 @@
+.content {
+ color: inherit;
+}
diff --git a/test/integration/production/components/dynamic-css/with-css.js b/test/integration/production/components/dynamic-css/with-css.js
new file mode 100644
index 0000000000000..d988353142973
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/with-css.js
@@ -0,0 +1,3 @@
+import styles from './with-css.module.css'
+
+export default () => With CSS
diff --git a/test/integration/production/components/dynamic-css/with-css.module.css b/test/integration/production/components/dynamic-css/with-css.module.css
new file mode 100644
index 0000000000000..69fda78f79397
--- /dev/null
+++ b/test/integration/production/components/dynamic-css/with-css.module.css
@@ -0,0 +1,3 @@
+.content {
+ color: inherit;
+}
diff --git a/test/integration/production/pages/dynamic/css.js b/test/integration/production/pages/dynamic/css.js
new file mode 100644
index 0000000000000..116e142e80af7
--- /dev/null
+++ b/test/integration/production/pages/dynamic/css.js
@@ -0,0 +1,5 @@
+import dynamic from 'next/dynamic'
+
+const Hello = dynamic(import('../../components/dynamic-css/with-css'))
+
+export default Hello
diff --git a/test/integration/production/pages/dynamic/many-css-modules.js b/test/integration/production/pages/dynamic/many-css-modules.js
new file mode 100644
index 0000000000000..4f77b5b90b668
--- /dev/null
+++ b/test/integration/production/pages/dynamic/many-css-modules.js
@@ -0,0 +1,7 @@
+import dynamic from 'next/dynamic'
+
+const Component = dynamic(
+ import('../../components/dynamic-css/many-modules/with-css')
+)
+
+export default Component
diff --git a/test/integration/production/pages/dynamic/many-dynamic-css.js b/test/integration/production/pages/dynamic/many-dynamic-css.js
new file mode 100644
index 0000000000000..817164b8090b8
--- /dev/null
+++ b/test/integration/production/pages/dynamic/many-dynamic-css.js
@@ -0,0 +1,21 @@
+import dynamic from 'next/dynamic'
+
+const First = dynamic(
+ import('../../components/dynamic-css/many-imports/with-css-1')
+)
+const Second = dynamic(
+ import('../../components/dynamic-css/many-imports/with-css-2')
+)
+const Third = dynamic(
+ import('../../components/dynamic-css/many-imports/with-css-3')
+)
+
+export default function Page() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/test/integration/production/pages/dynamic/nested-css.js b/test/integration/production/pages/dynamic/nested-css.js
new file mode 100644
index 0000000000000..9efdb6d0dcb10
--- /dev/null
+++ b/test/integration/production/pages/dynamic/nested-css.js
@@ -0,0 +1,7 @@
+import dynamic from 'next/dynamic'
+
+const Component = dynamic(
+ import('../../components/dynamic-css/nested/with-css')
+)
+
+export default Component
diff --git a/test/integration/production/pages/dynamic/no-css.js b/test/integration/production/pages/dynamic/no-css.js
new file mode 100644
index 0000000000000..bb67592264e20
--- /dev/null
+++ b/test/integration/production/pages/dynamic/no-css.js
@@ -0,0 +1,5 @@
+import dynamic from 'next/dynamic'
+
+const Hello = dynamic(import('../../components/dynamic-css/no-css'))
+
+export default Hello
diff --git a/test/integration/production/pages/dynamic/shared-css-module.js b/test/integration/production/pages/dynamic/shared-css-module.js
new file mode 100644
index 0000000000000..e90b1dd138ae2
--- /dev/null
+++ b/test/integration/production/pages/dynamic/shared-css-module.js
@@ -0,0 +1,17 @@
+import dynamic from 'next/dynamic'
+
+const First = dynamic(
+ import('../../components/dynamic-css/shared-css-module/with-css')
+)
+const Second = dynamic(
+ import('../../components/dynamic-css/shared-css-module/with-css-2')
+)
+
+export default function Page() {
+ return (
+
+
+
+
+ )
+}
diff --git a/test/integration/production/test/dynamic.js b/test/integration/production/test/dynamic.js
index 510e7f9dde30f..61e4cfaa56f86 100644
--- a/test/integration/production/test/dynamic.js
+++ b/test/integration/production/test/dynamic.js
@@ -16,6 +16,43 @@ export default (context, render) => {
expect($('body').text()).toMatch(/Hello World 1/)
})
+ it('should render one dynamically imported component and load its css files', async () => {
+ const $ = await get$('/dynamic/css')
+ const cssFiles = $('link[rel=stylesheet]')
+ expect(cssFiles.length).toBe(1)
+ })
+
+ it('should render three dynamically imported components and load their css files', async () => {
+ const $ = await get$('/dynamic/many-dynamic-css')
+ const cssFiles = $('link[rel=stylesheet]')
+ expect(cssFiles.length).toBe(3)
+ })
+
+ it('should bundle two css modules for one dynamically imported component into one css file', async () => {
+ const $ = await get$('/dynamic/many-css-modules')
+ const cssFiles = $('link[rel=stylesheet]')
+ expect(cssFiles.length).toBe(1)
+ })
+
+ it('should bundle two css modules for nested components into one css file', async () => {
+ const $ = await get$('/dynamic/nested-css')
+ const cssFiles = $('link[rel=stylesheet]')
+ expect(cssFiles.length).toBe(1)
+ })
+
+ // It seem to be abnormal, dynamic CSS modules are completely self-sufficient, so shared styles are copied across files
+ it('should output two css files even in case of three css module files while one is shared across files', async () => {
+ const $ = await get$('/dynamic/shared-css-module')
+ const cssFiles = $('link[rel=stylesheet]')
+ expect(cssFiles.length).toBe(2)
+ })
+
+ it('should render one dynamically imported component without any css files', async () => {
+ const $ = await get$('/dynamic/no-css')
+ const cssFiles = $('link[rel=stylesheet]')
+ expect(cssFiles.length).toBe(0)
+ })
+
it('should render even there are no physical chunk exists', async () => {
let browser
try {