mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.8k stars 32.25k forks source link

Styles not applied in first loading correctly with NextJS #30410

Closed daniielp closed 2 years ago

daniielp commented 2 years ago

Duplicates

Latest version

Current behavior 😯

When using SX prop I seem to be getting an issue when deploying the application where a lot of the styles are loaded after the dom has rendered. Even though I use the emotion cache in my _documents.tsx, and create and use the theme in _app.tsx.

After my previous issue #29637 got closed because I also thought it solved the issue. I noticed on the live site that it's still an issue even after updating to SX prop.

Expected behavior πŸ€”

What should is that when rendering the page the first time all the style changes should already be there. but takes a small bit of time before it does. https://imgur.com/a/ZujFrXd

Steps to reproduce πŸ•Ή

The issue is now only visible on the live site (Hosted with vercel). https://daniiel.dev/

But for you want want to take a look or maybe have an idea what could cause it.

You can have a look at the repo and play around yourself by using the codesandbox bellow codesandbox: https://codesandbox.io/s/hopeful-bush-5jbz8

Context πŸ”¦

What I'm trying to accomplish is for my production build to look like my local development on initial load.

I think the issue lays in either the _documents.tsx or _app.tsx.

I have attached a link to the 2 images for how it looks in local development and production. https://imgur.com/a/ZujFrXd

Your environment 🌎

Used both Firefox Developer Edition & Google Chrome (Version: 96.0.4664.110) for testing.

System:
OS: Windows 10 10.0.19044
Binaries:
Node: 16.13.1 - C:\Program Files\nodejs\node.EXE Yarn: Not Found npm: 8.1.2 - C:\Program Files\nodejs\npm.CMD Browsers: Chrome: Not Found Edge: Spartan (44.19041.1266.0), Chromium (96.0.1054.62) npmPackages: @emotion/react: ^11.5.0 => 11.5.0 @emotion/styled: ^11.3.0 => 11.3.0 @mui/core: 5.0.0-alpha.53 @mui/lab: ^5.0.0-alpha.53 => 5.0.0-alpha.53 @mui/material: ^5.0.6 => 5.0.6 @mui/private-theming: 5.0.1 @mui/styled-engine: 5.0.2 @mui/system: 5.0.6 @mui/types: 7.0.0 @mui/utils: 5.0.1 @types/react: ^17.0.34 => 17.0.34 react: ^17.0.2 => 17.0.2 react-dom: ^17.0.2 => 17.0.2 typescript: ^4.3.5 => 4.3.5

--- tsconfig.json --- { "compilerOptions": { "lib": [ "es6", "dom" ], "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true, "target": "es5", "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve" }, "include": [ "next-env.d.ts", "*/.ts", "*/.tsx", "src/components" ], "exclude": [ "node_modules" ] }

siriwatknp commented 2 years ago

@shorcy Can you try the new _document.js in the our nextjs example? https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_document.js

It is something that we fix internally but how to configure correctly with NextJS.

daniielp commented 2 years ago

That fixed the issue. Thanks a lot, @siriwatknp :)

ApayRus commented 2 years ago

@siriwatknp it doesn't work for me. I just copied everything from example: _app.js, document.js , I runed codemod to convert v4 JSS to MUI v5 styles. But on first app load appears gigantic icons

Shahzayb commented 2 years ago

Getting the same issue. Then I tried setting style={{width: '1em', height: '1em'}} and it look somewhat ok, but still resizing. I think the styles aren't loading somehow. I'm using static build btw.

btdtech commented 2 years ago

Hi @siriwatknp I am experiencing the same issue.

I followed the repo provided by MUI (https://github.com/mui/material-ui/tree/HEAD/examples/nextjs-with-typescript).

The theme is applied in local dev but not when I deploy to production. (https://btdtech.io/)

As you can see from the website, when you first visit it there is no styling applied. If you use the link to navigate to the ABOUT page then you will see it styled. However if you refresh the ABOUT page, the styling disappears again.

This is the link to the repository for your perusal https://github.com/btdtech/company-website.

Look forward to your reply!

Bac

btdtech commented 2 years ago

Hi everyone,

I have been digging and it seems that Emotion cache's style component has to style. Do we know why this is? I really would like to get to the bottom of this, otherwise I might have to find another CSS solution.

Cheers,

Bac

siriwatknp commented 2 years ago

I can confirm that our official example does not work. I looking into this.

siriwatknp commented 2 years ago

It seems like the problem comes from this:

// pages/_document.js
const initialProps = await Document.getInitialProps(ctx);
// console.log(initialProps.html) returns empty string
const emotionStyles = extractCriticalToChunks(initialProps.html);

The initialProps.html returns an empty string which is why emotion cannot create correct styles. When I downgrade react from latest(v18.0.0) to v17.0.2, it works. So I think it is something wrong with NextJS + React 18.

~As a workaround, I suggest downgrading react to v17.0.2 for now and I will open an issue on the nextjs repo.~

siriwatknp commented 2 years ago

I can confirm that the issue comes from nextjs latest. I switch from latest to canary and it works. @btdtech @ApayRus @Shahzayb Can you try the canary version of nextjs?

-"next": "latest",
+"next": "canary",

Change the package json then run yarn and yarn build. It worked for me.

Note that the latest nextjs (v12.1.6) (that does not work) was published 28 days ago and ~I could not find the PR that fixed the issue. If anyone knows, feel free to link the issue and the PR.~

I guess this PR https://github.com/vercel/next.js/pull/36792 fixes the issue.

btdtech commented 2 years ago

I can confirm that the issue comes from nextjs latest. I switch from latest to canary and it works. @btdtech @ApayRus @Shahzayb Can you try the canary version of nextjs?

-"next": "latest",
+"next": "canary",

Change the package json then run yarn and yarn build. It worked for me.

Note that the latest nextjs (v12.1.6) (that does not work) was published 28 days ago.

Thank you so much @siriwatknp . It solved the issue!

I can understand that there is a bug in nextjs, but why was yarn important in all of this? I tried NPM but didn't work.

siriwatknp commented 2 years ago

I can confirm that the issue comes from nextjs latest. I switch from latest to canary and it works. @btdtech @ApayRus @Shahzayb Can you try the canary version of nextjs?

-"next": "latest",
+"next": "canary",

Change the package json then run yarn and yarn build. It worked for me. Note that the latest nextjs (v12.1.6) (that does not work) was published 28 days ago.

Thank you so much @siriwatknp . It solved the issue!

I can understand that there is a bug in nextjs, but why was yarn important in all of this? I tried NPM but didn't work.

Thanks, I am closing this issue then.

ApayRus commented 2 years ago

@siriwatknp I downgraded MUI to 4 :-(

Taliss commented 2 years ago

@siriwatknp One way to fix this (even though I am not sure if it's a fix or just a side-effect) is not to use emotionCache. NextJS now support SSR with emotion. This is also working out with a simple _document file, without the need for enhancing and style injections.

next.config: ( flag emotion )

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  compiler: {
    emotion: true,
  },
}

module.exports = nextConfig

_document: ( no extra enhance code and injections )

import { Html, Head, Main, NextScript } from 'next/document'
import theme from 'theme'

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <meta name="theme-color" content={theme.palette.primary.main} />
        ...
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

_app: ( no cache provider )

export default function App({ Component, pageProps }: Props) {
  const getLayout = Component.getLayout || ((page) => page)

  return (
    // <CacheProvider value={emotionCache}>
    <ThemeProvider theme={theme}>
      {getLayout(<Component {...pageProps} />)}
    </ThemeProvider>
    // </CacheProvider>
  )
}

versions:

    "next": "^12.1.6",
    "@emotion/react": "^11.8.2",
    "@emotion/styled": "^11.8.1",
    "@mui/material": "^5.5.3",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
siriwatknp commented 2 years ago

@Taliss The difference is that the example uses the advanced approach, not the default approach.

giorgiPapava commented 2 years ago

I'm still facing this issue. nothing seems to work for me mentioned above.

ReBank this is the website, I can't give the source code since its enterprise application but I can tell you that I'm using Next.js, MUI, styled-components (not @mui/styled-engine-sc, regular mui configuration with just styled-components styled api). As you can see styles are flickering on the first load. I'm using Next.js SWC and have styled-components turned on in next.config.js and using this configuration. Does anyone have this problem and is there any fix to this?

giorgiPapava commented 2 years ago

I'm still facing this issue. nothing seems to work for me mentioned above.

ReBank this is the website, I can't give the source code since its enterprise application but I can tell you that I'm using Next.js, MUI, styled-components (not @mui/styled-engine-sc, regular mui configuration with just styled-components styled api). As you can see styles are flickering on the first load. I'm using Next.js SWC and have styled-components turned on in next.config.js and using this configuration. Does anyone have this problem and is there any fix to this?

I fixed it with adopting styled-engine-sc with yarn resolutions and next webpack resolve alias

vascojm82 commented 2 years ago

I can confirm that the issue comes from nextjs latest. I switch from latest to canary and it works. @btdtech @ApayRus @Shahzayb Can you try the canary version of nextjs?

-"next": "latest",
+"next": "canary",

Change the package json then run yarn and yarn build. It worked for me.

Note that the latest nextjs (v12.1.6) (that does not work) was published 28 days ago and ~I could not find the PR that fixed the issue. If anyone knows, feel free to link the issue and the PR.~

I guess this PR vercel/next.js#36792 fixes the issue.

Hi, I upgraded NextJS from v12.0.10 to v12.1.7-canary.39 and I'm still seeing this issue, the style from the theme won't load on the page's first render, anyone can help?

daveteu commented 2 years ago

"next": "^12.2.0", seems to have fixed the issue.

Working version as of today

       "next": "^12.2.0", 
        "@emotion/react": "^11.9.3",
        "@emotion/server": "^11.4.0",
        "@emotion/styled": "^11.9.3",

        "@mui/icons-material": "^5.8.4",
        "@mui/lab": "^5.0.0-alpha.88",
        "@mui/material": "^5.8.5",
Homerlrh commented 2 years ago

I can confirm that the issue comes from nextjs latest. I switch from latest to canary and it works. @btdtech @ApayRus @Shahzayb Can you try the canary version of nextjs?

-"next": "latest",
+"next": "canary",

Change the package json then run yarn and yarn build. It worked for me.

Note that the latest nextjs (v12.1.6) (that does not work) was published 28 days ago and ~I could not find the PR that fixed the issue. If anyone knows, feel free to link the issue and the PR.~

I guess this PR vercel/next.js#36792 fixes the issue.

Still facing the same problem, style is not load on first load, I also followed the _document.js. What else am i missing ?

"react": "^17.0.2",
"next": "canary",

"@emotion/cache": "latest",
"@emotion/react": "latest",
"@emotion/server": "latest",
"@emotion/styled": "latest",

"@mui/icons-material": "^5.6.2",
"@mui/lab": "^5.0.0-alpha.78",
"@mui/material": "^5.6.2",
"@mui/styles": "^5.6.2",
"@mui/system": "5.9.1",
stevenkeith85 commented 2 years ago

Yup...

Just wasted some time with this one. In case this is anyone else's issue.

https://developer.chrome.com/blog/new-in-devtools-102/#network-preview

I used to be able to click on the dev tools preview tab, and see a nicely rendered page. At some point I've presumably upgraded Chrome (or its done it itself πŸ€·β€β™‚οΈ) and things stopped working.

I probably never noticed at the time; so when I next clicked on the preview tab I got the whole 'plain html' with no CSS applied. (and assumed I'd broken something)

I've just turned JavaScript via dev tools and checked; and everything is properly rendering.

TLDR; things might actually be working / don't trust the chrome preview tab.

Edit: Not 100% sure CSP applies to inline stylesheets; but I can see the styles in the response so πŸ€·β€β™‚οΈ

kelvindecosta commented 2 years ago

I followed @Taliss 's comment in this thread: https://github.com/mui/material-ui/issues/30410#issuecomment-1148489813

Works like a charm. I'm using all the latest packages.

Is there a benefit to using the default approach that is missing with this configuration? It just seems needlessly complicated.

YauhenDavidovich commented 2 years ago

Adding this to _document.js finally solved the problem `MyDocument.getInitialProps = async (ctx) => { const originalRenderPage = ctx.renderPage; const cache = createEmotionCache(); const { extractCriticalToChunks } = createEmotionServer(cache);

const sheets = new ServerStyleSheets();

ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => function EnhanceApp(props) { return sheets.collect(<App emotionCache={cache} {...props} />); }, });

const initialProps = await Document.getInitialProps(ctx); const emotionStyles = extractCriticalToChunks(initialProps.html); const emotionStyleTags = emotionStyles.styles.map((style) => ( <style data-emotion={${style.key} ${style.ids.join(" ")}} key={style.key} // eslint-disable-next-line react/no-danger dangerouslySetInnerHTML={{ __html: style.css }} /> ));

return { ...initialProps, styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()], emotionStyleTags, }; };`

roux1max commented 2 years ago

It seems the example repo is now working. I just changed my _app.ts and _document.ts and everything seems to work fine both in local dev and in staging env (wth next build).

I am testing on Brave Browser (which is just a layer on top of Chrome) version 1.43.93 (with Chromium: 105.0.5195.127) and with the following env:

System:
  OS: Linux 5.15 Ubuntu 20.04.5 LTS (Focal Fossa)
Binaries:
  Node: 18.9.0
  npm: 8.19.1
Browsers:
  Chrome: 105.0.5195.125
npmPackages:
  @emotion/react: 11.10.x => 11.10.4 
  @emotion/styled: 11.10.x => 11.10.4 
  @mui/base:  5.0.0-alpha.98 
  @mui/core-downloads-tracker:  5.10.6 
  @mui/icons-material: 5.10.6 => 5.10.6 
  @mui/material: 5.10.6 => 5.10.6 
  @mui/private-theming:  5.10.6 
  @mui/styled-engine:  5.10.6 
  @mui/system:  5.10.6 
  @mui/types:  7.2.0 
  @mui/utils:  5.10.6 
  @types/react: 18.0.20 => 18.0.20 
  react: 18.2.0 => 18.2.0 
  react-dom: 18.2.0 => 18.2.0 
  typescript: 4.8.3 => 4.8.3
vascojm82 commented 1 year ago

"next": "^12.2.0", seems to have fixed the issue.

Working version as of today

       "next": "^12.2.0", 
        "@emotion/react": "^11.9.3",
        "@emotion/server": "^11.4.0",
        "@emotion/styled": "^11.9.3",

        "@mui/icons-material": "^5.8.4",
        "@mui/lab": "^5.0.0-alpha.88",
        "@mui/material": "^5.8.5",

I did what daveteu posted above and I still see the same issue happening but not as often. I have: "next": "^12.2.0", "@mui/lab": "^5.0.0-alpha.88", "@mui/material": "^5.8.5", "@mui/styles": "^5.4.1", "@emotion/react": "^11.9.3", "@emotion/server": "^11.4.0", "@emotion/styled": "^11.9.3",

Can anyone help please?

rahulcn commented 1 year ago

Complete the v5 migration with tss-react, it worked for me. https://mui.com/material-ui/migration/migrating-from-jss/#2-use-tss-react https://docs.tss-react.dev/ssr/next.js#mui-and-tss-use-different-caches

hamza172 commented 1 year ago

Hey I had come across the same issue. I have tried all the things suggested above but nothing has worked. Oh and my issue isnt only for first time, it never works https://myfreecourse-front-v2-r0rfrryqq-hamza172.vercel.app

Using @Mui v5 Next.js 13 with Typescript

my _document file is `import React from 'react'; import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'; import { ServerStyleSheets } from '@mui/styles';

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

render() { return (

);

} }

// getInitialProps belongs to _document (instead of _app), // it's compatible with server-side generation (SSG). MyDocument.getInitialProps = async (ctx) => { // Resolution order // // On the server: // 1. app.getInitialProps // 2. page.getInitialProps // 3. document.getInitialProps // 4. app.render // 5. page.render // 6. document.render // // On the server with error: // 1. document.getInitialProps // 2. app.render // 3. page.render // 4. document.render // // On the client // 1. app.getInitialProps // 2. page.getInitialProps // 3. app.render // 4. page.render

// Render app and page and get the context of the page with collected side effects. 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 fragment is rendered after the app and page rendering finish. styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()], }; }; `

my_app file is `import '@/styles/globals.css' import '@/styles/universal.css' import '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; import ScopedCssBaseline from '@mui/material/ScopedCssBaseline'; import type { AppProps } from 'next/app'

export default function App({ Component,pageProps }: AppProps) { return ( <>

  <ScopedCssBaseline>
    <Component {...pageProps} />
  </ScopedCssBaseline>
</>

); }

`

saadkhan6670 commented 1 year ago

I followed @Taliss 's comment in this thread: https://github.com/mui/material-ui/issues/30410#issuecomment-1148489813

Works like a charm. I'm using all the latest packages.

kaiquevalentim commented 1 year ago

I had a similar problem but i worked around it probably with not the best solution. In my case i was making a project using react and django, this project had a google verification to login and after this it load the home page. In the first load only the buttons itens from material ui package were without the styled, but after you refresh the page the style works again. So i created a context to use a variable that apoint this first load and automatically refresh the page. Like i said, this is not the best way but worked for me. I hope someone with it.

kukadiyaAni commented 1 year ago
"next": "^13.5.2",
"react": "^18.2.0",

"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.45",
"@material-ui/styles": "^4.11.5",

I have the above configuration work fine but get this issue after updating with the latest version 13.5.2,

@hamza172 I tried your solution but now work in above version

Thanks in advance if anyone can help with this

famdude commented 7 months ago

so what is the right solution now in 2024?

famdude commented 6 months ago

so what is the right solution now in 2024?

@siriwatknp I think it's an important issue. and now after more than 2 years, even with the new versions of Next js, MUI and Emotion, the problem still exists. can you look around please?

famdude commented 6 months ago

I can confirm that not using CacheProvider, this problem is fixed. But as my app is RTL so I HAVE to use it. here's _app.js file:

import * as React from "react";
import Head from "next/head";
import { ThemeProvider } from "@mui/material/styles";
import rtlPlugin from "stylis-plugin-rtl";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import { prefixer } from "stylis";
import theme from "../src/theme";

// Create rtl cache
const cacheRtl = createCache({
  key: "muirtl",
  stylisPlugins: [prefixer, rtlPlugin],
});

function MyApp({ Component, pageProps }) {
return (
    <CacheProvider value={cacheRtl}>
      <ThemeProvider theme={theme}>
        <Head>
        // header tags here
        </Head>
        <Component {...pageProps} />
      </ThemeProvider>
    </CacheProvider>
  );
}

export default MyApp;

commenting <CacheProvider value={cacheRtl}></CacheProvider> out fixes problem, but makes the app look crushed as it changes to LTR.

hadarhubara10 commented 2 months ago

https://github.com/mui/material-ui/issues/30410#issuecomment-2037024412 @famdude I have the same issue. Have you come up with a solution?

leonardo015 commented 2 months ago

My solution used suppressHydrationWarning and solved other details.

It works this way:

Ps: Im using Next.js v14 and Material UI v5.16

(simplified code)

Root Layout:

import { CustomThemeProvider } from "@/context/CustomThemeContext";
import React from "react";
import "./globals.css";

// trick to avoid SSR mismatch due to html inline colorMode script
const mockSSRColorMode = () => "light";

export default function RootLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    return (
        <html
            lang="en"
            style={{ colorScheme: mockSSRColorMode() }}
            suppressHydrationWarning
        >
            <head>
                {/* Inline script to set color scheme on the first load (to avoid wrong color loading screen) */}
                <script
                    dangerouslySetInnerHTML={{
                        __html: `
                        (function() {
                            const storedColorMode = localStorage.getItem('colorMode');
                            const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
                            const colorMode = storedColorMode || (prefersDarkMode ? 'dark' : 'light');
                            document.documentElement.style.colorScheme = colorMode;
                        })();
                    `,
                    }}
                />
            </head>
            <body>
                <CustomThemeProvider>{children}</CustomThemeProvider>
            </body>
        </html>
    );
}

CustomThemeContext:

"use client";
import Loading from "@/app/loading";
import { PaletteMode, useMediaQuery } from "@mui/material";
import {
    Theme,
    ThemeProvider
} from "@mui/material/styles";
import React, {
    createContext,
    ReactNode,
    useContext,
    useEffect,
    useMemo,
    useState,
} from "react";

interface ThemeContextType {
    colorMode: PaletteMode;
    toggleColorMode: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

interface CustomThemeProviderProps {
    children: ReactNode;
}

export const CustomThemeProvider: React.FC<CustomThemeProviderProps> = ({
    children,
}) => {
    const [colorMode, setColorMode] = useState<PaletteMode>("light");
    const [loading, setLoading] = useState(true);
    const sysPreferDark = useMediaQuery("(prefers-color-scheme: dark)", {
        noSsr: true,
    });

    // This prop determines native elements theme
    const setColorSchemeOnHtml = (newMode: PaletteMode) =>
        document.documentElement.style.setProperty("color-scheme", newMode);

    // First-load color-mode routine: use stored OR system-preference OR fallback to 'light'
    // Set it on state+themecontext AND on <html>'s color-scheme style prop
    useEffect(() => {
        const storedColorMode = localStorage.getItem("colorMode") as PaletteMode;
        const newMode = storedColorMode || (sysPreferDark ? "dark" : "light");
        setColorMode(newMode);
        setColorSchemeOnHtml(newMode);
        setLoading(false);
    }, [sysPreferDark]);

    const theme: Theme = useMemo(() => {
        // ... mui createStyles()
    }, [colorMode]);

    // User-triggered color-mode change: set preference on storage and html
    const toggleColorMode = () => {
        setColorMode((prevMode) => {
            const newMode = prevMode === "light" ? "dark" : "light";
            localStorage.setItem("colorMode", newMode);
            setColorSchemeOnHtml(newMode);
            return newMode;
        });
    };

    // on first-load the loading screen is immediatelly displayed
    // until the theme is loaded with the uptodate color-mode
    if (loading) {
        return <Loading />;
    }

    return (
        <ThemeContext.Provider value={{ colorMode, toggleColorMode }}>
            <ThemeProvider theme={theme}>{children}</ThemeProvider>
        </ThemeContext.Provider>
    );
};

export const useCustomThemeContext = (): ThemeContextType => {
    const context = useContext(ThemeContext);
    if (!context) {
        throw new Error("useCustomTheme must be used within a CustomThemeProvider");
    }
    return context;
};

And no color-scheme related styles on any CSS file.

medeirosjoaquim commented 1 month ago

still happening on Next 14.0.4 , this problem was marked as solved in https://github.com/vercel/next.js/issues/15642 but it is still persisting