Styles take time to load - Next.js
Asked Answered
E

4

8

When I enter my portfolio it loads the unstyled html page, and only after a few seconds the styles load. How can I resolve this?

NOTE: I'm using Styled-Components

  1. When I enter the page: enter image description here

  2. After a few seconds: enter image description here

I tried looking for styled components compatibility with next.js but couldn't find anything about this bug

Exciter answered 29/10, 2022 at 1:59 Comment(2)
did you try removing defer from the script tag? I am not sure but give it a try.Tenuis
Please provide a minimal reproducible example.Trichoid
A
15

Being a CSS-in-JS styling solution, styled-components is geared for client-side rendering, it normally assumes that it's executing in a browser and so it produces CSS styles as a result of JavaScript execution and injects them directly into the document. In this case, since Next.js pre-renders all pages by default, you need to have the CSS styles in the server-side rendered HTML to avoid the flash of unstyled content on first render.

You can do so following these steps:

If you are using Next.js 12+ (with SWC compiler):

Modify next.config.js:

/** @type {import('next').NextConfig} */

const nextConfig = {
  // ...rest of options
  compiler: {
    styledComponents: true,
  },
}

module.exports = nextConfig

Create a custom _document.js file on pages folder and add:

import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: [initialProps.styles, sheet.getStyleElement()],
      }
    } finally {
      sheet.seal()
    }
  }
}
Aquilar answered 29/10, 2022 at 9:46 Comment(4)
what if my css is in global.cssOophorectomy
then what i have to doOophorectomy
what about if using with the folder app instead of peages?Tolu
It worked with this : "dependencies": { "babel-plugin-styled-components": "^2.1.4", "react": "^18", "react-dom": "^18", "next": "14.0.4", "styled-components": "^6.1.8" }Pastel
C
11

If you are using Next 13 or Next 14 with App Router solution with _document.js won't work. Then you need to create a global registry component to collect all CSS style rules during a render.

Add registry.tsx in /app folder

'use client';

import React, { useState } from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import {
  ServerStyleSheet,
  StyleSheetManager,
} from 'styled-components';

export default function StyledComponentsRegistry({
  children,
}: {
  children: React.ReactNode;
}) {
  // Only create stylesheet once with lazy initial state
  // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
  const [styledComponentsStyleSheet] = useState(
    () => new ServerStyleSheet()
  );

  useServerInsertedHTML(() => {
    const styles = styledComponentsStyleSheet.getStyleElement();
    styledComponentsStyleSheet.instance.clearTag();
    return <>{styles}</>;
  });

  if (typeof window !== 'undefined') return <>{children}</>;

  return (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children}
    </StyleSheetManager>
  );
}

Then import it and use in your app/layout.tsx.

import StyledComponentsRegistry from './lib/registry'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>{children}</StyledComponentsRegistry>
      </body>
    </html>
  )
}

Example: https://github.com/vercel/app-playground/tree/main/app/styling/styled-components

Ciliate answered 6/7, 2023 at 17:29 Comment(1)
This is working just fine on Next 14 if anybody wonderingFinal
A
3

Since Next.js supports server-side rendering by default, the CSS-in-JS approach used by styled-components may not work as expected. By default, Next.js pre-renders all pages on the server before they are sent to the client, which means that styles generated by styled-components may not be available in the initial HTML response.

To address this issue, you can use the ServerStyleSheet class from styled-components to collect all the styles used in your application and include them in the server-rendered HTML response by adding the following into your _document.js

import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';

function MyDocument(props) {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

MyDocument.getInitialProps = async (ctx) => {
  const sheet = new ServerStyleSheet();
  const originalRenderPage = ctx.renderPage;

  try {
    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App) => (props) =>
          sheet.collectStyles(<App {...props} />),
      });

    const initialProps = await Document.getInitialProps(ctx);
    return {
      ...initialProps,
      styles: (
        <>
          {initialProps.styles}
          {sheet.getStyleElement()}
        </>
      ),
    };
  } finally {
    sheet.seal();
  }
};

export default MyDocument;
Aperiodic answered 13/3, 2023 at 10:18 Comment(2)
How do you do this same thing with Next.js 13 ?Obduliaobdurate
I praise this one answer for not using the old class components with documentHighcolored
B
0

Answering @BogdanOnu’s question, in Next.js 13+ with TypeScript, you can handle this with the following example:

import { ReactElement } from "react";
import { ServerStyleSheet } from "styled-components";
import Document, { Html, Head, Main, NextScript, DocumentContext, DocumentInitialProps } from "next/document";

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      } as any;
    } finally {
      sheet.seal();
    }
  }
  render(): ReactElement {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}
Battleax answered 24/10, 2023 at 17:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.