reactjs - Utilizarea componentului Head din Next.js în afara fișierului _document face ca pagina să se prăbușească


Încerc să folosesc Next.js cu Typescript și Material UI. Există nenumărate tutoriale online despre cum să configurezi Next.js pentru a funcționa cu Material UI, și toate par să folosească exact același cod în _document.js și _app.js, respectiv.

Am încercat atât să adaptez puțin codul din _document.js într-un _document.tsx TypeScript, cât și să-l copiez-lipesc așa cum este, dar de fiecare dată primesc aceeași problemă, și anume că ori de câte ori încerc să folosesc <Head /> în afara _document.tsx, chiar și doar în _app.tsx pentru a seta un titlu și meta viewport, așa cum sugerează codul din tutoriale, primesc un mesaj de eroare foarte neajutător (stacktrace complet inclus mai jos):

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

Am avut exact aceeași problemă într-un proiect anterior al meu, motiv pentru care mă surprinde și mă frustrează în același timp că se pare că nu există nimeni care să aibă aceeași problemă ca mine și că fiecare tutorial pe care l-am putut găsi include exact același cod care se pare că nu funcționează pentru mine.

Iată codul meu:

_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

Voi oferi cu plăcere mai multe dacă este nevoie, dar, pentru că nu înțeleg cu adevărat care este problema, nu știu exact care părți sunt relevante să le postez aici.

Aceasta este întreaga stacktrace pentru eroarea pe care o primesc:

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)

Răspuns #1

Head trebuie importat din "next/head" în loc de "next/document". Vezi aici în documentație: https://nextjs.org/docs/api-reference/next/head

Totuși, de data aceasta trebuie să folosești importul implicit:

import Head from "next/head";

Fișierul _app.tsx trebuie ajustat în consecință:

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

Am avut și eu această problemă, deoarece completarea codului în WebStorm a sugerat include-ul greșit.

Răspunsul #2

Se dovedește că atunci când folosești head-uri în afara definiției documentului, trebuie să le imporți din "next/head" în loc de "next/document".