34

1. To build a next.js app using styled components it's really easy. You just have to use their _document.js snippet to enable SSR and prevent styles flickering on page load: https://github.com/zeit/next.js/blob/canary/examples/with-styled-components/pages/_document.js

2. To build a next.js app using material-ui is almost as simple. You just have to start with ther project base: https://github.com/mui-org/material-ui/tree/master/examples/nextjs, which has its own implementation on _document.js: https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_document.js

3. Sadly, I couldn't figure out how to "merge" both implementations and get a next app where both styled-components and material-ui components can coexist, SSR and do not flicker on page load.

Can you help me? Is there someone on the internet with better abilities than mine whom already solved that problem but I do not know about?

Thanks in advance.

Amet Alvirde
  • 1,453
  • 3
  • 13
  • 22
  • Here, you can find a working example https://github.com/manakuro/nextjs-styled-component-material-ui-example and bog post about it https://javascript.plainenglish.io/ssr-with-next-js-styled-components-and-material-ui-b1e88ac11dfa . This is a common approach, which I also use. – Evgeny Bobkin Mar 13 '21 at 17:29
  • Here you can also find a good example: [link](https://github.com/mui-org/material-ui/tree/next/examples/nextjs-with-styled-components-typescript) – Matheus Gagno Brunetti Aug 29 '21 at 18:09

5 Answers5

46

Give this a try

_document.js

import React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components'
import { ServerStyleSheets } from '@material-ui/styles';
import theme from '../src/theme';

class MyDocument extends Document {
  static async getInitialProps (ctx) {
    const styledComponentsSheet = new ServerStyleSheet()
    const materialSheets = new ServerStyleSheets()
    const originalRenderPage = ctx.renderPage;

    try {
        ctx.renderPage = () => originalRenderPage({
            enhanceApp: App => props => styledComponentsSheet.collectStyles(materialSheets.collect(<App {...props} />))
          })
        const initialProps = await Document.getInitialProps(ctx)
        return {
          ...initialProps,
          styles: (
            <React.Fragment>
              {initialProps.styles}
              {materialSheets.getStyleElement()}
              {styledComponentsSheet.getStyleElement()}
            </React.Fragment>
          )
        }
      } finally {
        styledComponentsSheet.seal()
      }
  }

  render() {
    return (
      <html lang="en" dir="ltr">
        <Head>
          <meta charSet="utf-8" />
          {/* Use minimum-scale=1 to enable GPU rasterization */}
          <meta
            name="viewport"
            content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
          />
          {/* PWA primary color */}
          <meta
            name="theme-color"
            content={theme.palette.primary.main}
          />
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

export default MyDocument;

.babelrc

{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true }]]
}

For update check https://github.com/nblthree/nextjs-with-material-ui-and-styled-components

Il Harper
  • 57
  • 7
evgeni fotia
  • 4,650
  • 3
  • 16
  • 34
  • @alevardi I updated my answer after testing it. Now it works without doubt. – evgeni fotia Mar 13 '19 at 15:06
  • 3
    @alevardi here is a github repo I created https://github.com/MarchWorks/nextjs-with-material-ui-styled-components – evgeni fotia Mar 13 '19 at 15:27
  • Thank you so much, you are amazing. Works like a charm! :) – Amet Alvirde Mar 15 '19 at 18:49
  • initially I’ve tried the code in here but it didn’t work. then I’ve tried the code in repo, that works well. – Mehmet N. Yarar Jul 04 '19 at 10:35
  • MehmetN.Yarar Yes, since material 4.4 this solution is outdated. Maybe @evgenifotia could update it? That would be amazing, and we will be really thankful. – Amet Alvirde Sep 19 '19 at 17:38
  • 1
    @Alevardi if there is anything out of date just open an issue and I will take a look at it and thanks for reporting – evgeni fotia Sep 19 '19 at 17:58
  • 2
    Actually, I was wrong, maybe it was outdated when @MehmetN.Yarar discovered this thread, buy as of right now I just cloned your repo to see what was happening and everything worked just fine. Thank you so much for this work! – Amet Alvirde Sep 19 '19 at 18:00
2

For everyone who still has the problem with the code above: I had to also include the StylesProvider in pages/_app.tsx.

_app.tsx:

import { StylesProvider } from '@material-ui/core/styles';

<StylesProvider injectFirst>
  {/* Your component tree.
      Now, you can override Material-UI's styles. */}
</StylesProvider>

_document.tsx:

import React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '@material-ui/styles';
import { ServerStyleSheet } from 'styled-components';

class MyDocument extends Document {
  render() {
    return (
      <html lang="en">
        <Head>
          <meta charSet="utf-8" />
          <meta
            name="viewport"
            content="width=device-width, initial-scale=1, shrink-to-fit=no"
          />
          <meta name="theme-color" content="#000000" />
          {/* Fonts and icons */}
          <link
            rel="stylesheet"
            type="text/css"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Roboto+Slab:400,700|Material+Icons"
          />
          <link
            href="https://use.fontawesome.com/releases/v5.0.10/css/all.css"
            rel="stylesheet"
          />
        </Head>
        <body>
          <div id="page-transition" />
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}

MyDocument.getInitialProps = async (ctx) => {

  // Render app and page and get the context of the page with collected side effects.
  const materialSheet = new ServerStyleSheets();
  const styledComponentSheet = new ServerStyleSheet();
  const originalRenderPage = ctx.renderPage;

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

    const initialProps = await Document.getInitialProps(ctx);

    return {
      ...initialProps,
      // Styles fragment is rendered after the app and page rendering finish.
      styles: [
        ...React.Children.toArray(initialProps.styles),
        materialSheet.getStyleElement(),
        styledComponentSheet.getStyleElement(),
      ],
    };
  } finally {
    styledComponentSheet.seal();
  }
};

export default MyDocument;
Patrick Klitzke
  • 1,519
  • 2
  • 14
  • 24
1

This solution works fine for server-side, for client side you also need to change the injection order, as documented here: https://material-ui.com/customization/css-in-js/#css-injection-order

To make this work with next.js, you need to change the asignation of the jss constant like this:

const jss = create({
  ...jssPreset(),
  insertionPoint: process.browser
    ? window.document.getElementById('jss-insertion-point')
    : null
})
emik
  • 903
  • 12
  • 11
1

here is my _document.js file looks like:

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();
    }
  }
}

and below find my .babelrc file:

{ 
  "presets": ["next/babel"], 
  "plugins": [
      ["styled-components", { "ssr": true }]
   ]
}
brandonscript
  • 68,675
  • 32
  • 163
  • 220
Md. Jamal Uddin
  • 754
  • 8
  • 17
0

I have followed these example approach and worked well for me:

https://github.com/mui-org/material-ui/tree/next/examples/nextjs-with-styled-components-typescript

  • Your example works. But at the same time it has an issue with static builds [link](https://stackoverflow.com/questions/69217739/material-ui-new-v5-0-0-injectfirst-fails-to-set-specificity) – Alex Gusev Nov 10 '21 at 18:12