Editing the Next.js Root Div

If you've done any development with next.js then you have no doubtably seen the root div on the page.

<div id="__next">
/* your app */
</div>

I'm working on a project where I needed to add a class to this div.  Turns out there's two ways to interact with this element.

useEffect

The easy way is to add a useEffect hook to _app.js.

function MyApp({ Component, pageProps }) {
  useEffect(() => {
    document.getElementById("__next").className = "custom-class-name";
  }, []);

  return <Component {...pageProps} />;
}

export default MyApp;

Override <Main />

Option 1, while easy and straightforward, is very boring.  And honestly, it wasn't the first thought I had.  I'm using a custom document which looks more or less like

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>
    )
  }
}

export default MyDocument
Boilerplate custom document from next.js documentation

What's this <Main /> component?  Turns out it's a pretty simple and can be found in the next source here.

export function Main() {
  const { inAmpMode, html, docComponentsRendered } = useContext(
    DocumentComponentContext
  )

  docComponentsRendered.Main = true

  if (inAmpMode) return <>{AMP_RENDER_TARGET}</>
  return <div id="__next" dangerouslySetInnerHTML={{ __html: html }} />
}
Source code for <Main /> component

And while I'm not going to pretend that I understand what it's doing – it's pretty obvious that there's the __next element that I want to edit.  Seems logical that if I replace <Main /> with my own version that includes a classname, should work.

It took some digging to find out the correct file that exports DocumentComponentContext, but after that worked like a charm.

const DocumentContext = require("next/dist/next-server/lib/document-context")
  .DocumentContext;

function MainOverride() {
  const { inAmpMode, html, docComponentsRendered } = useContext(
    DocumentContext
  );

  docComponentsRendered.Main = true;

  if (inAmpMode) return <>{AMP_RENDER_TARGET}</>;
  return <div id="__next" className="custom-class" dangerouslySetInnerHTML={{ __html: html }} />;
}

The render function in the custom document file becomes

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

While option 2 was more exciting to implement, I do not know if it's the wisest move. Since I'm importing directly from the next dist files, a future release could alter the underlying mechanism and break the app.

Until then, Happy coding.