25

I'm using the example here

https://github.com/zeit/next.js#custom-document

and I want to change the custom_class on the body tag

I have tried passing props on calling components but nothing works. I want to add a 'dark' class based on some condition but I have no idea how to change this.

EDIT:

I'm not sure this is possible. After getting help from the nextjs slack channel I was told

"Pages get rendered client side with next/link
And body is not touched after server side rendering"

I'm going to try and wrap things in another tag that I generate and try and change that.

2
  • 1
    I'm upvoting, did you check the example? I only use <style jsx [global> tags Commented Apr 2, 2018 at 10:50
  • "I'm going to try and wrap things in another tag that I generate and try and change that." That may well be the best solution. Because of the way client-side routing and rendering works, most frameworks mount the app inside some existing element. The app is then able to influence everything inside that element, nut not the element itself or any of it's parents. Commented Sep 11, 2021 at 11:13

9 Answers 9

19

The cleanest solution I found is not declarative, but it works well:

import Head from "next/head"

class HeadElement extends React.Component {
    componentDidMount() {
        const {bodyClass} = this.props
        document.querySelector("body").classList.add(bodyClass || "light")
    }

    render() {
        return <Head>
            {/* Whatever other stuff you're using in Head */}
        </Head>
    }
}

export default HeadElement

With this Head component you would pass in "dark" or "light" (following the question's example for light/dark themes) as the bodyClass prop from the page.

Sign up to request clarification or add additional context in comments.

1 Comment

This answer is correct. I'd just add that the question says "based on some condition". So if that condition is dynamic and the head element gets mounted and unmounted multiple times, the "light" class will keep adding on top of the <body> element. In my case, I had to remove first any reference to an old class using document.querySelector("html")?.classList.remove("my-class")
19

As of current Next (10) and React (17) versions, If you'd like to change the body class from a page, you can can do it like this:

// only example: maybe you'll want some logic before, 
// and maybe pass a variable to classList.add()
useEffect( () => { document.querySelector("body").classList.add("home") } );

Please note that useEffect is a Hook, so it can be used only in modern function components, not class ones.

The useEffect Hook can be used instead of the 'old' componentDidMount LifeCycle.

https://reactjs.org/docs/hooks-effect.html https://reactjs.org/docs/hooks-overview.html https://reactjs.org/docs/components-and-props.html

3 Comments

Also, document is not available on Node is it? So this presumably does not work well with server-side rendering.
This answer is pretty decent. You could even make a custom hook out of it. Pretty sure the useEffect hook doesn't run on the server, so this should not cause issues accessing document while running server-side.
you need to add [] at the end of the useEffect dependencies to call it only once, otherwise it will be called on every page re-render
3

The only way to directly access the body tag on Next js is via the _document.js file but this file is rendered server side as stated in the Documentation.

The work around I suggest is to access the body tag from the component directly. Example:

const handleClick = (e) => {
    document.querySelector('body').classList.toggle('dark')
}

<div onClick={handleClick}>Toggle</div>

Comments

3

Unfortunately, next/head does not allow specifying body class like React Helmet does:

<Helmet>
  <body className="foo"/>
</Helmet>

Luckily, you can use this.props.__NEXT_DATA__.props.pageProps inside _document.js to get access to the page props and use them to to set the class on the <body> element:

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

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

    render() {
        const pageProps = this.props?.__NEXT_DATA__?.props?.pageProps;
        console.log('pageProps => ', pageProps);
        return (
            <Html>
                <Head />
                <body className={pageProps.bodyClassName}>
                    <Main />
                    <NextScript />
                </body>
            </Html>
        );
    }
}

More info here.

3 Comments

I like your line: const pageProps = _.get(this.props, 'NEXT_DATA.props.pageProps');
Why was this downvoted? Looking in the docs, this seems to be the correct way? nextjs.org/docs/advanced-features/custom-document
in the docs it now says: For React 18 support, we recommend avoiding customizing getInitialProps and renderPage, if possible.
2

The solution I came up with for my situation where I don't need a lot of unique body classes was to create a component called BodyClass.js and import that component into my Layout.js component.

BodyClass.js

import  { Component } from 'react';

class BodyClass extends Component {

    componentDidMount() {

        if (window.location.pathname == '/') {
            this.setBodyClass('home');
            
        } else if (window.location.pathname == '/locations') {
            this.setBodyClass('locations');
        } 
    }

    setBodyClass(className) {
        // remove other classes
        document.body.className ='';

        // assign new class
        document.body.classList.add(className);
    }

    render() { 
        return null;
    }

}
 
export default BodyClass;

Layout.js

import Header from './Header';
import Footer from './Footer';
import BodyClass from '../BodyClass';

const Layout = ({ children }) => {

    return (

        <React.Fragment>

            <BodyClass />

            <Header />

            {children}
            
            <Footer />

        </React.Fragment>

    )

}


export default Layout;

Comments

0

It's not exactly the answer you are looking for, but I was able to accomplish the same thing functionally by passing a prop through the component which then updates a class on a top-level element that wraps everything in my app and sits just under the . This way each page can have it's own unique class and operates the same way I'd use a body class.

Here is where I pass the prop through

<Layout page_title={meta_title} page_desc={meta_desc} main_class={'contact_page'}>

And here is where I use it in the Layout component:

<main className={props.main_class}>
    <Header page_title={props.page_title} page_desc={props.page_desc} />

      {props.children}

    <Footer />
</main>

Comments

0

Directly, it's not possible. But with another way, you can use framework like tailwind and insert the class directly in your css.
Here an example using tailwind:

.dark {
  background-color: black;
  color: white;
}

body {
  @apply dark
}

1 Comment

This solution is very nice!
0

This is my CSS only solution:

(I first looked at https://stackoverflow.com/a/66358460/729221 but that felt too complex for changing a bit of styling.)

(This uses Tailwind CSS, but does not need to.)

index.css:

body:has(#__next .set-bg-indigo-50-on-body) {
      @apply bg-indigo-50;
}

layout.tsx:

  //…
  return (
    <div className="set-bg-indigo-50-on-body">{children}</div>
  )

This will work, as long as that div is a direct parent of <div id="__next">. Otherwise you need to update the css :has-rule.

Comments

0

I haven't found a good way to do this in next.js 14, so the best way seems to be using a custom hook like this:

import {DependencyList, useEffect} from 'react'

export default function useBodyClassName(className: string, deps: DependencyList = []): void {
    useEffect(() => {
        document.body.classList.add(className)

        return () => {
            document.body.classList.remove(className)
        }
    }, deps)
}

It will handle adding and removal of provided class name to <body> when dependencies change. With empty dependencies list it will add class name on component mount and remove it on component unmount.

It won't affect existing class names of <body>, so you can use this hook in several components to add several different class names. Just make sure class names are different.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.