vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.41k stars 27.03k forks source link

React 18: Updated `Document` Design & Documentation #31225

Closed devknoll closed 2 years ago

devknoll commented 3 years ago

Overview

Document today:

Document's new design, in order to support Suspense + Streaming + more:

Details

Since Document only runs on the server, it's naturally a Server Component. However, Server Components are not finalized yet, and won't be until after React 18 is released. However, there's a chicken and egg problem, as we'd like to support incremental progress towards Server Components (and certainly support for Suspense + Streaming) today.

The solution is the Next.js Document Server Component, a subset of actual Server Components with additional restrictions. For example, no built-in React hooks (like useState) or Suspense are currently supported. This model will be supported until we can officially replace pages/_document.js with pages/_document.server.js.

The simplest Next.js Document Server Component looks like this:

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

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

Conceptually, this is a Server Component that returns a mixed tree of Server/Client Components. There is only one hook supported, useFlushEffect, described below.

Wrapping the Application

Prior to React 18, many CSS-in-JS libraries would use some global variable that they could write data into during rendering, relying on the fact that rendering would complete synchronously to avoid conflicts. With Suspense in React 18, this is no longer possible: one request could be suspended waiting on I/O, allowing another request to start or unsuspend and access that global data.

Instead, the recommendation is that such libraries use React Context to scope data per request. In order to then allow such libraries to be integrated with Next.js, we add support for a render prop to the <Main /> component:

import { Html, Head, Main, NextScript } from 'next/document'
import MyContext from '../lib/my-context'

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main>
          {children => (
            <MyContext.Provider value={...}>
              {children}
            </MyContext.Provider>
          )}
        </Main>
        <NextScript />
      </body>
    </Html>
  )
}

The render prop will be used to generate the tree that makes up the application content during SSR.

Flushing Styles

Prior to React 18, many CSS-in-JS libraries would just return a script tag for you to insert into your Document at the end of the render. With Suspense, content is generated and flushed incrementally, so this pattern of waiting is no longer possible.

Instead, the recommendation is that libraries incrementally flush styles to the stream. To streamline this (and expose the stream to 3rd party libraries), Next.js should expose a useFlushEffect hook:

useFlushEffect(() => <foo />)

As per the recommendation, functions passed to the useFlushEffect hook will be called right before writing content generated by React.

Migration Path

Note: all existing class component Documents will continue working, but will not support features like streaming or suspense.

carlosagsmendes commented 3 years ago

@devknoll it would be great to have some info on how to access the context when the Documents are Server Components. If this is not the proper place for feedback feel free to delete the comment! Thks in advance

devknoll commented 2 years ago

Closing as obsolete

github-actions[bot] commented 2 years ago

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.