stitches icon indicating copy to clipboard operation
stitches copied to clipboard

Next.js Error: Styles don't get correctly applied if _document exports a functional component

Open dlehmhus opened this issue 3 years ago • 9 comments

Bug report

Describe the bug

If you replace (as seen in the with-stitches example)

export default class Document extends NextDocument {
  render() {
    return (
      <Html lang="en">
        <Head>
          <style
            id="stitches"
            dangerouslySetInnerHTML={{ __html: getCssText() }}
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

with (required if you want to enable the concurrent features of React 18)

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <style
          id="stitches"
          dangerouslySetInnerHTML={{ __html: getCssText() }}
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

only the stitches theme gets applied to the document head, but not the styles of the rendered components. (On the server).

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Go to CodeSandbox
  2. Inspect the styling in the html file send from the server

Expected behavior

All styles of the current page should be added.

Screenshots

example

System information

  • OS: macOS 12.0.1
  • Version of Stitches: 1.2.5
  • Version of Next.js: 12.0.4
  • Version of react: 17.0.2
  • Version of react-dom: 17.0.2
  • Version of Node.js: 14.17.0

dlehmhus avatar Nov 23 '21 15:11 dlehmhus

I don't think Stitches is meant to work with React 18 yet, we'd need to make some changes. But Im unsure if this particular example should work.

/cc @hadihallak can you help out?

peduarte avatar Nov 24 '21 10:11 peduarte

@peduarte this example actually uses react 17. This is basically the example with-stitches repo. Except for the _document file of course :)

dlehmhus avatar Nov 24 '21 17:11 dlehmhus

@dlehmhus Yeah this looks like a bug — we will be looking into it. Thanks for reporting it 🙏

hadihallak avatar Nov 25 '21 09:11 hadihallak

Sorry for referencing this issue so many times (am fairly noobish at programming in general and didn't realize it worked like that!).

I actually still had this issue with a class Document component and fixed it by returning the styles tag in getInitialProps like this:

export default class Document extends NextDocument {
  static async getInitialProps(ctx: DocumentContext) {
    try {
      const initialProps = await NextDocument.getInitialProps(ctx);

      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            <style
              id="stitches"
              dangerouslySetInnerHTML={{ __html: getCssText() }}
            />
          </>
        ),
      };
    } catch (e) {
      console.error(e.message);
    } finally {
      null;
    }
  }
  render() {
    return (
      <Html lang="en">
        <Head></Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

System Information:

  • Windows 10
  • Stitches: 1.2.5
  • Next.js: 11.1.0
  • react: 17.0.2
  • react-dom: 17.0.2
  • Node.js: 16.8.0

akevinge avatar Nov 25 '21 17:11 akevinge

Hey @dlehmhus 👋 I did some digging in here and it seems that nextjs renders the page before the render method is called when using a class based _document that extends the NextDocument class and that is the correct order of events required for the getCssText to work correctly. This however does not happen when using a functional component is used as the _document so what this results in, is having stitches collect the styles before the page is even rendered so in order to have things work correctly, we’d need to mock this behavior of a class based document and we can do that by adding a getInitialProps on the function document component :

Document.getInitialProps = async function getInitialProps(ctx) {
  // render page
  const results = await ctx.defaultGetInitialProps(ctx);
  // get the css for the current rendering cycle
  const stitchesCssString = getCssText();
  // reset the styles between renders
  reset();
  return {
    ...results,
    styles: (
      <>
        {results.styles}
        <style
          id="stitches"
          dangerouslySetInnerHTML={{ __html: stitchesCssString }}
        />
      </>
    )
  };
};

you can test it here https://codesandbox.io/s/functional-document-8cqmn?file=/pages/_document.jsx

hadihallak avatar Nov 30 '21 16:11 hadihallak

@innub would you mind sharing a full re-production of what you encountered as i'm not able to reproduce the issue in here https://codesandbox.io/s/class-document-p353i?file=/pages/_document.jsx

hadihallak avatar Nov 30 '21 16:11 hadihallak

@innub would you mind sharing a full re-production of what you encountered as i'm not able to reproduce the issue in here https://codesandbox.io/s/class-document-p353i?file=/pages/_document.jsx

I am also unable to reproduce the issue. Pretty weird because I looked through my commit history and was always using a class Document component, but I definitely had the exact same issue. Sorry for the trouble, and I'll make an issue if something comes up! Thanks for this awesome library 👍

akevinge avatar Nov 30 '21 19:11 akevinge

Hey @hadihallak, thanks for taking the time to look into this issue. Adding getInitalProps on the function document component does the trick. But I'm not sure if this violates the "no static getInitialProps" requirement described in the docs. But then again, as @peduarte mentioned in the beginning, stitches is not supposed to work with react 18 anyways...

dlehmhus avatar Dec 01 '21 08:12 dlehmhus

Hey @dlehmhus 👋 I did some digging in here and it seems that nextjs renders the page before the render method is called when using a class based _document that extends the NextDocument class and that is the correct order of events required for the getCssText to work correctly. This however does not happen when using a functional component is used as the _document so what this results in, is having stitches collect the styles before the page is even rendered so in order to have things work correctly, we’d need to mock this behavior of a class based document and we can do that by adding a getInitialProps on the function document component :

Document.getInitialProps = async function getInitialProps(ctx) {
  // render page
  const results = await ctx.defaultGetInitialProps(ctx);
  // get the css for the current rendering cycle
  const stitchesCssString = getCssText();
  // reset the styles between renders
  reset();
  return {
    ...results,
    styles: (
      <>
        {results.styles}
        <style
          id="stitches"
          dangerouslySetInnerHTML={{ __html: stitchesCssString }}
        />
      </>
    )
  };
};

you can test it here https://codesandbox.io/s/functional-document-8cqmn?file=/pages/_document.jsx

I can confirm that the issue still persists on:

  • "next": "12.3.1"
  • "react": "18.2.0"
  • "@stitches/react": "^1.2.8"

and the solution above does the trick

EDIT: I did not include the reset call. It was working without it.

CeamKrier avatar Nov 17 '22 21:11 CeamKrier