Prevent FOUC on Next.js + Chakra UI

TL;DR, use @emotion/cache and @emotion/server to prevent initial unstyled content

Published on Friday, August 13th, 2021


If you're using Chakra UI with Next.js, chances are that you'll encounter an initial white flash or some parts still unstyled before the full content loads. This is known as FOUC (flash of unstyled content). Here's an example of this website with the FOUC issue: https://v7-git-test-without-emotion-cache-grikomsn.vercel.app/
Preview of flash of unstyled content for this website

Preview of flash of unstyled content for this website

This happens because the Chakra UI stylesheets from <ChakraProvider /> are not loaded server-side and only client-side. The solution, since Chakra UI is based on Emotion, is to render the styles server-side and cache it using two additional Emotion packages, @emotion/cache and @emotion/server.
# using yarn
yarn add @emotion/cache @emotion/server

# using npm
npm install @emotion/cache @emotion/server
After adding those dependencies, first make the Emotion cache instance, preferably on a separate source file, e.g. lib/emotion-cache.ts (example source file for this website):
import createCache from "@emotion/cache";

export default createCache({
  key: "css",
});
Then, render the stylesheets in pages/_document.tsx via Document.getInitialProps and use the previously made cache instance with @emotion/server's extractCritical (example source file for this website):
import * as React from "react";

import emotionCache from "~lib/emotion-cache";

import { ColorModeScript } from "@chakra-ui/react";
import createEmotionServer from "@emotion/server/create-instance";
import Document, {
  DocumentContext,
  Head,
  Html,
  Main,
  NextScript,
} from "next/document";

const { extractCritical } = createEmotionServer(emotionCache);

export default class CustomDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx);
    const styles = extractCritical(initialProps.html);
    return {
      ...initialProps,
      styles: [
        initialProps.styles,
        <style
          key="emotion-css"
          dangerouslySetInnerHTML={{ __html: styles.css }}
          data-emotion-css={styles.ids.join(" ")}
        />,
      ],
    };
  }

  render() {
    return (
      <Html lang="en">
        <Head>
          <meta charSet="UTF-8" />
          <meta content="ie=edge" httpEquiv="X-UA-Compatible" />
        </Head>

        <body>
          <ColorModeScript />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}
With that, the page should now render the stylesheets both server-side and client-side. Here's an example of this website after adding the solution: https://v7-git-test-with-emotion-cache-grikomsn.vercel.app/
Preview of cached stylings for this website

Preview of cached stylings for this website

TL;DR, use @emotion/cache and @emotion/server to prevent initial unstyled content. Theoretically this should also work on Next.js static exports if you're using next export. You can see the full project for this website on GitHub.
Hope this helps! 🙌🏻

This is also cross-posted on dev.to and Medium.