Reputation: 541
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
I tried looking for styled components compatibility with next.js but couldn't find anything about this bug
Upvotes: 9
Views: 11548
Reputation: 131
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
Upvotes: 13
Reputation: 279
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>
)
}
}
Upvotes: 0
Reputation: 39
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;
Upvotes: 3
Reputation: 4033
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()
}
}
}
Upvotes: 15