Skip to content

Commit

Permalink
Update custom document docs to prepare for React 18. (#33814)
Browse files Browse the repository at this point in the history
To help prepare for React 18, I've found myself sharing the updated code snippet of `_document` linked in the [React 18 docs](https://nextjs.org/docs/advanced-features/react-18#react-server-components), which uses a function instead of a class.

This PR updates the Custom Document docs to help prepare for this change, by encourage readers to use the new version, and listing more tangible examples of why you would override `_document` (`lang` on `html`, className on `body`). It also reorganizes some of the caveats and warnings related to `getInitialProps` usage to only be in the single section that applies to it.

These overrides and ejections (`getInitialProps` and `renderPage`) only apply to a subset of the people looking to change document. 

Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
  • Loading branch information
leerob and ijjk committed Feb 3, 2022
1 parent 4bd76ff commit 5abd92f
Showing 1 changed file with 44 additions and 39 deletions.
83 changes: 44 additions & 39 deletions docs/advanced-features/custom-document.md
Expand Up @@ -4,76 +4,67 @@ description: Extend the default document markup added by Next.js.

# Custom `Document`

A custom `Document` is commonly used to augment your application's `<html>` and `<body>` tags. This is necessary because Next.js pages skip the definition of the surrounding document's markup.
A custom `Document` can update the `<html>` and `<body>` tags used to render a [Page](/docs/basic-features/pages.md). This file is only rendered on the server, so event handlers like `onClick` cannot be used in `_document`.

To override the default `Document`, create the file `./pages/_document.js` and extend the `Document` class as shown below:
To override the default `Document`, create the file `pages/_document.js` as shown below:

```jsx
import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}

render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}

export default MyDocument
```

> The code above is the default `Document` added by Next.js. Feel free to remove the `getInitialProps` or `render` function from `MyDocument` if you don't need to change them.
`<Html>`, `<Head />`, `<Main />` and `<NextScript />` are required for the page to be properly rendered.

Custom attributes are allowed as props, like `lang`:
The code above is the default `Document` added by Next.js. Custom attributes are allowed as props. For example, we might want to add `lang="en"` to the `<html>` tag:

```jsx
<Html lang="en">
```

The `<Head />` component used here is not the same one from [`next/head`](/docs/api-reference/next/head.md). The `<Head />` component used here should only be used for any `<head>` code that is common for all pages. For all other cases, such as `<title>` tags, we recommend using [`next/head`](/docs/api-reference/next/head.md) in your pages or components.
Or add a `className` to the `body` tag:

The `ctx` object is equivalent to the one received in [`getInitialProps`](/docs/api-reference/data-fetching/get-initial-props.md#context-object), with one addition:
```jsx
<body className="bg-white">
```

- `renderPage`: `Function` - a callback that runs the actual React rendering logic (synchronously). It's useful to decorate this function in order to support server-rendering wrappers like Aphrodite's [`renderStatic`](https://github.com/Khan/aphrodite#server-side-rendering)
`<Html>`, `<Head />`, `<Main />` and `<NextScript />` are required for the page to be properly rendered.

## Caveats

- `Document` is only rendered in the server, event handlers like `onClick` won't work.
- React components outside of `<Main />` will not be initialized by the browser. Do _not_ add application logic here or custom CSS (like `styled-jsx`). If you need shared components in all your pages (like a menu or a toolbar), take a look at the [`App`](/docs/advanced-features/custom-app.md) component instead.
- `Document`'s `getInitialProps` function is not called during client-side transitions, nor when a page is [statically optimized](/docs/advanced-features/automatic-static-optimization.md).
- The `<Head />` component used in `_document` is not the same as [`next/head`](/docs/api-reference/next/head.md). The `<Head />` component used here should only be used for any `<head>` code that is common for all pages. For all other cases, such as `<title>` tags, we recommend using [`next/head`](/docs/api-reference/next/head.md) in your pages or components.
- React components outside of `<Main />` will not be initialized by the browser. Do _not_ add application logic here or custom CSS (like `styled-jsx`). If you need shared components in all your pages (like a menu or a toolbar), read [Layouts](/docs/basic-features/layouts.md) intead.
- `Document` currently does not support Next.js [Data Fetching methods](/docs/basic-features/data-fetching/overview.md) like [`getStaticProps`](/docs/basic-features/data-fetching/get-static-props.md) or [`getServerSideProps`](/docs/basic-features/data-fetching/get-server-side-props.md).

## Customizing `renderPage`

> It should be noted that the only reason you should be customizing `renderPage` is for usage with **css-in-js** libraries that need to wrap the application to properly work with server-side rendering.
> **Note:** This is advanced and only needed for libraries like CSS-in-JS to support server-side rendering. This is not needed for built-in `styled-jsx` support.
It takes as argument an options object for further customization:
To prepare for [React 18](/docs/advanced-features/react-18.md), we recommend avoiding customizing `getInitiaProps` and `renderPage`, if possible.

The `ctx` object shown below is equivalent to the one received in [`getInitialProps`](/docs/api-reference/data-fetching/get-initial-props.md#context-object), with the addition of `renderPage`.

```jsx
import Document from 'next/document'
import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
static async getInitialProps(ctx) {
const originalRenderPage = ctx.renderPage

// Run the React rendering logic synchronously
ctx.renderPage = () =>
originalRenderPage({
// useful for wrapping the whole react tree
// Useful for wrapping the whole react tree
enhanceApp: (App) => App,
// useful for wrapping in a per-page basis
// Useful for wrapping in a per-page basis
enhanceComponent: (Component) => Component,
})

Expand All @@ -82,11 +73,25 @@ class MyDocument extends Document {

return initialProps
}

render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

export default MyDocument
```

> **Note**: `getInitialProps` in `_document` is not called during client-side transitions.
## TypeScript

You can use the built-in `DocumentContext` type and change the file name to `./pages/_document.tsx` like so:
Expand Down

0 comments on commit 5abd92f

Please sign in to comment.