Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using the Next.js Head component outside of _document crashes the page

I am trying to use Next.js with Typescript and Material UI. There are countless tutorials online on how to set up Next.js to work with Material UI, and all of them seem to be using exactly the same code in _document.js and _app.js respectively.

I've tried both adapting the code in _document.js a tiny bit into a typescript _document.tsx, and copy-pasting it as-is, but everytime I get the same issue, which is that whenever I try to use <Head /> outside of _document.tsx, even just within _app.tsx to set a title and viewport meta, as the code in the tutorials suggests, I get a very much non-helpful error message (full stacktrace included below):

TypeError: Cannot destructure property 'styles' of 'this.context' as it is null.

I had the exact same issue in a previous project of mine, which is why it both surprises and frustrates me that there seems to be no one having the same problem as me, and that every single tutorial I could find includes the exact same code which doesn't seem to work for me.

Here is my code:

_document.tsx

import React from "react";
import Document, { DocumentContext, DocumentInitialProps, Head, Html, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "@material-ui/core/styles";
import theme from "../constants/theme";

class MyDocument extends Document {
    render() {
        return (
            <Html lang="en">
                <Head>
                    <meta name="theme-color" content={theme.palette.primary.main} />
                </Head>
                <body>
                    <Main />
                    <NextScript />
                </body>
            </Html>
        );
    }

    static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
        const sheets = new ServerStyleSheets();
        const originalRenderPage = ctx.renderPage;

        ctx.renderPage = () => originalRenderPage({
            enhanceApp: (App) => (props) => sheets.collect(<App {...props} />)
        });

        const initialProps = await Document.getInitialProps(ctx);

        return {
            ...initialProps,
            styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()]
        }
    }
}

export default MyDocument;

_app.tsx

import { CssBaseline, ThemeProvider } from "@material-ui/core";
import { Head } from "next/document";
import React from "react"
import theme from "../constants/theme";

function MyApp({ Component, pageProps }) {
  React.useEffect(() => {
    const jssStyles = document.querySelector("#jss-server-side");
    if (jssStyles) jssStyles.parentElement.removeChild(jssStyles);
  }, []);

  return (
    <React.Fragment>
      <Head>
        <title>Title</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </Head>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </React.Fragment>
  )
}

export default MyApp

I'll gladly provide more if needed, but since I don't really understand what the problem is, I don't really know which parts are relevant to post here.

This is the full stacktrace for the error I'm getting:

TypeError: Cannot destructure property 'styles' of 'this.context' as it is null.
    at Head.render (E:\dev\weblore-web\.next\server\pages\_document.js:484:7)
    at processChild (E:\dev\weblore-web\node_modules\react-dom\cjs\react-dom-server.node.development.js:3450:18)
    at resolve (E:\dev\weblore-web\node_modules\react-dom\cjs\react-dom-server.node.development.js:3270:5)
    at ReactDOMServerRenderer.render (E:\dev\weblore-web\node_modules\react-dom\cjs\react-dom-server.node.development.js:3753:22)
    at ReactDOMServerRenderer.read (E:\dev\weblore-web\node_modules\react-dom\cjs\react-dom-server.node.development.js:3690:29)
    at renderToString (E:\dev\weblore-web\node_modules\react-dom\cjs\react-dom-server.node.development.js:4298:27)
    at renderPage (E:\dev\weblore-web\node_modules\next\dist\next-server\server\render.js:54:851)   
    at Object.ctx.renderPage (E:\dev\weblore-web\.next\server\pages\_document.js:963:28)
    at Function.getInitialProps (E:\dev\weblore-web\.next\server\pages\_document.js:310:19)
    at Function.getInitialProps (E:\dev\weblore-web\.next\server\pages\_document.js:971:85)
    at loadGetInitialProps (E:\dev\weblore-web\node_modules\next\dist\next-server\lib\utils.js:5:101)
    at renderToHTML (E:\dev\weblore-web\node_modules\next\dist\next-server\server\render.js:54:1142)    at async E:\dev\weblore-web\node_modules\next\dist\next-server\server\next-server.js:107:97     
    at async E:\dev\weblore-web\node_modules\next\dist\next-server\server\next-server.js:100:142    
    at async DevServer.renderToHTMLWithComponents (E:\dev\weblore-web\node_modules\next\dist\next-server\server\next-server.js:132:387)
    at async DevServer.renderToHTML (E:\dev\weblore-web\node_modules\next\dist\next-server\server\next-server.js:133:522)
like image 416
Phantom Avatar asked Jan 13 '21 04:01

Phantom


People also ask

Why you shouldn't use next JS?

Disadvantages of NextJS Although NextJS is developing rapidly and many features arrive, it still has some cons and issues which you can see below: Cost of flexibility – Next JS does not provide many built-in front pages, so you have to create the whole front-end layer from the ground up.

How do you handle errors in next JS?

Handling Client Errors In addition to preventing the page from crashing, it allows you to provide a custom fallback component and even log error information. To use Error Boundaries for your Next. js application, you must create a class component ErrorBoundary and wrap the Component prop in the pages/_app. js file.


1 Answers

Head needs to be imported from "next/head" instead of "next/document". See here in the docs: https://nextjs.org/docs/api-reference/next/head

However, this time you have to use the default import:

import Head from "next/head";

The _app.tsx needs to be adjusted accordingly:

import { CssBaseline, ThemeProvider } from "@material-ui/core";
import Head from "next/head";
import React from "react"
import theme from "../constants/theme";

function MyApp({ Component, pageProps }) {
  React.useEffect(() => {
    const jssStyles = document.querySelector("#jss-server-side");
    if (jssStyles) jssStyles.parentElement.removeChild(jssStyles);
  }, []);

  return (
    <React.Fragment>
      <Head>
        <title>Title</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </Head>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </React.Fragment>
  )
}

export default MyApp

I've had this issue as well, since code completion in WebStorm suggested the wrong include.

like image 199
J R Avatar answered Oct 13 '22 22:10

J R