Recently, Material UI 5 was released. Previously (in Material UI 4), I used to connect it by modifying _document.js and _app.js. Is it the same for Material UI 5?
for MUI v4 _app.js
import CssBaseline from '#material-ui/core/CssBaseline';
import {ThemeProvider} from '#material-ui/core/styles';
class MyApp extends App {
render() {
const {Component, pageProps, router} = this.props;
return (
<ThemeProvider>
<CssBaseline />
<Component {...pageProps}/>
</ThemeProvider>
)
}
}
export default MyApp
for MUI v4 _document.js
import Document, {Html, Main, NextScript} from "next/document";
import { ServerStyleSheets } from '#material-ui/core/styles';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
let props = {...initialProps};
return props;
}
render() {
return (
<Html>
<body>
<Main/>
<NextScript/>
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
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 register rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
What I want to ask is: Should I connect it in the same way to MUI v5?
Maybe there is a more elegant way to do it?
It is connected still the same way as I provided in my question, by some changes in _app.js and _document.js
Official MUI example with Next js
https://github.com/mui/material-ui/tree/master/examples/nextjs
If it's just css you're importing from #materail-ui/core/styles, you can simply import the CSS directly in your _App.js with...
import '#materail-ui/core/styles/someFilename.css' (no from needed) And it should be accessible anywhere in your app from there.
Related
I have a problem with a new nextjs application, I have configured Material UI
Each time I refresh the page (hard) the style is not correct anymore, I have to redo modifications in each file to find my previous style...
I show you my _app and _document files
Thanks in advance for your help
_app.js
import React from "react";
import { CacheProvider } from "#emotion/react";
import { CssBaseline } from "#mui/material";
import { ThemeProvider } from "#mui/styles";
import createEmotionCache from "../styles/createEmotionCache";
import theme from "../config/MUITheme";
const clientSideEmotionCache = createEmotionCache();
const MyApp = ({ Component, emotionCache = clientSideEmotionCache, pageProps }) => (
<CacheProvider value={emotionCache}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
export default MyApp;
_document.js
import * as React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import createEmotionServer from "#emotion/server/create-instance";
import createEmotionCache from "../styles/createEmotionCache";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
/* eslint-disable */
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) =>
function EnhanceApp(props) {
return <App emotionCache={cache} {...props} />;
},
});
/* eslint-enable */
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 fragment is rendered after the app and page rendering finish.
styles: [...React.Children.toArray(initialProps.styles), ...emotionStyleTags],
};
};
Before refresh :
After refresh :
Environment :
"next": "12.1.0",
"react": "17.0.2",
"#emotion/cache": "^11.7.1",
"#emotion/react": "^11.8.1",
"#emotion/server": "^11.4.0",
"#emotion/styled": "^11.8.1",
"#fontsource/roboto": "^4.5.3",
"#mui/icons-material": "^5.4.4",
"#mui/material": "^5.4.4",
"#mui/styles": "^5.4.2"
You can reproduce with : https://codesandbox.io/s/throbbing-rain-72v10e
I had the same problem for months and I realized even though your app.js and document.js is fine by hard refreshing it takes a bit to read theme(I still don't know why). My workaround for this was fully load the components that had this issue on client side. Components that don't need to be SSR you can turned off SSR feature for them. you can do this in two ways
turn off SSR when importing:
export const component = dynamic(
() => import('../components/component'),
{
ssr: false,
}
);
turn off ssr with a wrapper around component:
import React from 'react';
import dynamic from 'next/dynamic';
const NoSSRWrapper = props => <React.Fragment>{props.children}</React.Fragment>;
export default dynamic(() => Promise.resolve(NoSSRWrapper), {
ssr: false,
});
return (
<NoSSRWrapper>
//your component code
</NoSSRWrapper>
)
this way everything Waites and loads fully on client side therefore refreshing/hard refreshing doesn't affect the load of CSS.
this link was really helpful for me
Have spent a couple days on this issue sourcing solutions and ideas from most of SA and Reddit however to no avail..
Upon load both in production and local every load presents the whole html without any styling before then being injected into DOM..
Currently this is my projects key files:
_document.js
import { ServerStyleSheet } from "styled-components";
import Document, { Main, NextScript } from "next/document";
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();
}
}
render() {
return (
<html lang="en">
<Head>
<title>namesjames</title>
<link rel="icon" href="/favicon.ico" />
<script src="/static/chrome-fix.js" />
<link href="/above-the-fold.css" />
</Head>
<body>
<script src="/noflash.js" />
<Main />
<NextScript />
<script> </script>
</body>
</html>
);
}
}
_app.js
/* eslint-disable class-methods-use-this */
import App from "next/app";
import React from "react";
import { ThemeProvider } from "styled-components";
import { ParallaxProvider } from 'react-scroll-parallax';
import Header from "../components/Header";
import theme from "../theme";
import GlobalStyles from "../GlobalStyles";
import DarkModeToggle from '../components/toggle/toggleMode';
import Footer from '../components/Footer'
import LazyLoad from 'react-lazy-load';
import Router from 'next/router';
import styled from 'styled-components'
// import '../style.css'
const Loaded = styled.div`
opacity: ${(props) => props.loaded ? "1" : "0"};
`
export default class MyApp extends App {
state = { isLoading: false, loaded: false }
componentDidMount() {
// Logging to prove _app.js only mounts once,
// but initializing router events here will also accomplishes
// goal of setting state on route change
console.log('MOUNT');
this.setState({loaded: true})
Router.events.on('routeChangeStart', () => {
this.setState({ isLoading: true });
console.log('loading is true, routechangeStart')
});
Router.events.on('routeChangeComplete', () => {
this.setState({ isLoading: false });
console.log('loading is false, routeChangeComplete')
});
Router.events.on('routeChangeError', () => {
this.setState({ isLoading: false });
console.log('loading is false, routeChangeError')
});
}
render(): JSX.Element {
const { isLoading } = this.state;
const { Component, pageProps, router, loaded } = this.props;
return (
<Loaded loaded={this.state.loaded}>
<ThemeProvider theme={theme}>
<GlobalStyles />
<ParallaxProvider>
<Header />
{isLoading && 'STRING OR LOADING COMPONENT HERE...'}
<Component {...pageProps} key={router.route} />
<LazyLoad offsetVertical={500}>
<Footer />
</LazyLoad>
</ParallaxProvider>
<DarkModeToggle />
</ThemeProvider>
</Loaded>
);
}
}
index.js
import { color } from "styled-system";
import { OffWhite } from "../util/tokens";
import Hero from "../components/Hero";
import Banner from '../components/Banner'
import TitleText from '../components/TitleText'
import HomeTitleCopyScene from '../components/HomeTitleCopyScene'
import TwoCards from '../components/TwoCards'
import LazyLoad from 'react-lazy-load';
function Home(): JSX.Element {
return (
<div className="container">
<Banner />
<HomeTitleCopyScene />
<LazyLoad offsetVertical={1000}>
<TwoCards />
</LazyLoad>
</div>
);
}
export default Home;
As some might see I have tried multiple implementations and am just a bit confused as to what it could be at this stage..
Any help appreciated and I can provide more information upon request.. Many thanks
I found two solutions which helped -->
Was to hard style opacity:0 into the JSX and then upon styling injecting into DOM applying opacity: 1 !important onto any of the components displayed..
<section className="cards-block" style={{opacity:0}}>
Whilst this was effective this morning I discovered at some point during my development I had incorrectly imported Head from next/head and used this in my _document.js rather than using the correct Head from next/documents..
// import Head from "next/head"; --> incorrect
import { ServerStyleSheet } from "styled-components";
import Document, { Head, Main, NextScript } from "next/document";
Ergo -> a correctly rendering and injected element with no FOUC
Hope this helps someone out there
I've found a workaround for my small portfolio project:
just included this inline css to a <head> of my custom _document.js:
{<style dangerouslySetInnerHTML={{__html: `
html {background: #333}
body #__next div {visibility: hidden}
body.loaded #__next div {visibility: visible}
`}}></style>}
The "loaded" class is added to the body in _app.js:
if (process.browser) {
document.body.classList.add("loaded")
}
I'm hot sure if it's a good solution, any advice would be appreciated :)
I'm trying to use some variables exported from global .scss file to create Material-UI theme. Problems occurs because those variables are undefined on server (I'm using next.js so pages are pre-rendered). How to make this work?
Error that I get (beacuse all colors are undefined):
Error: Material-UI: The color provided to augmentColor(color) is invalid.
The color object needs to have a `main` property or a `500` property.
Here are the important parts of my application.
_app.jsx
// General imports
import React from 'react';
// Components import
import App from 'next/app';
import { createMuiTheme, ThemeProvider, CssBaseline, StylesProvider, jssPreset } from '#material-ui/core';
import { create } from 'jss';
// Global styles and colors import
import '../styles/global.scss';
import colors from '../styles/colors.scss';
if (typeof document !== 'undefined') {
const styleNode = document.createComment('jss-insertion-point');
document.head.insertBefore(styleNode, document.head.firstChild);
}
/**
* Material-UI theme.
*
* #type {Object}
*/
const theme = createMuiTheme({
palette: {
primary: {
main: colors.primary,
contrastText: colors.primaryText
},
secondary: {
main: colors.secondary,
contrastText: colors.secondaryText
}
}
});
class CustomApp extends App {
/**
* Handles the componentDidMount lifecycle event.
*
* #memberof MyApp
*/
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}
/**
* Renders the component.
*
* #returns component's elements
* #memberof CustomApp
*/
render() {
const {
Component,
pageProps
} = this.props;
return (
<>
<StylesProvider
jss={
create({
...jssPreset(),
// Define a custom insertion point that JSS will look for when injecting the styles into the DOM.
insertionPoint: 'jss-insertion-point'
})
}
>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</StylesProvider>
</>
);
}
}
export default CustomApp;
_document.jsx
// General imports
import React from 'react';
// Components import
import Document, { Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '#material-ui/core';
/**
*
*
* #class CustomDocument
* #extends {Document}
*/
class CustomDocument extends Document {
render() {
return (
<html lang="en">
<Head>
<meta charSet="utf-8" />
<link rel="icon" href="/favicon.ico" />
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
CustomDocument.getInitialProps = async (ctx) => {
// 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 isServer {...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()],
isServer: false
};
};
export default CustomDocument;
colors.scss
$color-primary: #2aa876;
$color-primary-text: #ffffff;
$color-secondary: #ffd265;
$color-secondary-text: #7e621d;
:export {
primary: $color-primary;
primaryText: $color-primary-text;
secondary: $color-secondary;
secondaryText: $color-secondary-text;
}
Material UI uses a javascript based style solution (JSS) instead of a CSS pre-processor like SCSS (see style solution).
This means it's not possible to customize the Material UI theme via SCSS variables. Nor is it possible to access the theme in your CSS/SCSS styles.
If you want to use SCSS for styling/theming, you might consider Material Web Components instead.
In colors.scss modify as below:
$theme-colors: (
'primary': #2aa876,
'primaryText': #ffffff,
'secondary': #ffd265,
'secondaryText': #7e621d,
);
#each $color, $value in $theme-colors {
:export{
$color: $value;
}
}
import it as you're doing.
import colors from '../styles/colors.scss';
# colors.primary and other variable should work now.
I have just upgraded the packages #material-ui/core*(4.4.1 => 4.8.3)* & #material/styles*(4.4.1 = 4.8.2)*
Now everything where the theme is used does not apply the correct colors & margins.
Are there any breaking changes in the package for the theme which I am not aware off ?
I am using Next.js for server rendering. Important note: Before upgrading the package everything worked fine. Downgrading is not an option as I need access to one of the components released in the newer version. Also I would not like to lock myself on a lower version because of that.
EDIT: Code for clarity
_app.js
<Provider store={store}>
<PersistGate persistor={store.__PERSISTOR} loading={null}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} user={this.state.user} />
</ThemeProvider>
</PersistGate>
</Provider>
_document.js
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.Fragment key="styles">
{initialProps.styles}
{sheets.getStyleElement()}
</React.Fragment>
]
};
This is all kept very much like in the example provided be Material-UI. And I did not see any changes in their git repo for the nextjs implementation.
I am not sure this will help your particular issue but I can share some pits I have fallen into while working with Material UI package for almost two years that all cause the behavior you have explained.
Make sure your project dependencies include only one version of each of #material-ui packages. styles wont apply well if there is more than one styles version present in the project.
Make sure all #material-ui packages you use are updated to latest version. They might not behave well together, if you miss an upgrade.
Try along with <ThemeProvider>using <MuiThemeProvider> from #material-ui/core/styles (or replace it with). I bumped into this issue some time ago while being on version 4. If I recall correctly it was because my project used both class and functional components.
I am using Nextjs v9.3 and config material-ui like as follows
_app.js
import React, { useEffect } from "react";
import { ThemeProvider } from "#material-ui/core/styles";
import { CssBaseline } from "#material-ui/core";
import { theme } from "../lib/theme";
function MyApp(props) {
useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles && jssStyles.parentNode)
jssStyles.parentNode.removeChild(jssStyles);
}, []);
const { Component, pageProps } = props;
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
);
}
export default MyApp;
_document.js
import React from "react";
import NextDocument from "next/document";
import { ServerStyleSheets as MaterialUiServerStyleSheets } from "#material-ui/core/styles";
import flush from "styled-jsx/server";
export default class Document extends NextDocument {
static async getInitialProps(ctx) {
const materialUiSheets = new MaterialUiServerStyleSheets();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props =>
materialUiSheets.collect(<App {...props} />)
});
const initialProps = await NextDocument.getInitialProps(ctx);
return {
...initialProps,
styles: [
<React.Fragment key="styles">
{initialProps.styles}
{materialUiSheets.getStyleElement()}
</React.Fragment>
]
};
} finally {
flush();
}
}
}
I have recently upgraded to Next.js 8.0.3 from 6.1.1 and I am now encountering a very intense flash of un-styled content (FOUC) for my header content which is using styled-jsx. It loaded just fine before updating Next.js.
The header code that is flashing is a custom built npm module that uses styled-jsx (but not next) and is being imported and placed into a layout page that is rendered with every next page.
This was the implementation in the _document.js file before updating next and it was working:
import Document, { Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet, injectGlobal } from 'styled-components'
import styledNormalize from 'styled-normalize'
import flush from 'styled-jsx/server'
injectGlobal`
${styledNormalize}
`
export default class MyDocument extends Document {
static getInitialProps({ renderPage }) {
const sheet = new ServerStyleSheet()
const page = renderPage(App => props =>
sheet.collectStyles(<App {...props} />)
)
const styleTags = sheet.getStyleElement()
const styles = flush()
return { ...page, styleTags, styles }
}
render() {
return (
<html>
<Head>
{this.props.styleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}
Based on the docs I have also tried this (where I wait for the initial props):
import Document, { Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet, injectGlobal } from 'styled-components'
import styledNormalize from 'styled-normalize'
import flush from 'styled-jsx/server'
injectGlobal`
${styledNormalize}
`
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const page = ctx.renderPage(App => props =>
sheet.collectStyles(<App {...props} />)
)
const initialProps = await Document.getInitialProps(ctx)
const styleTags = sheet.getStyleElement()
const styles = flush()
return { ...initialProps, ...page, styles, styleTags }
}
render() {
return (
<html>
<Head>
{this.props.styleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}
The flash might be a result of where I am implementing the module but not sure.
It seems like the code coming in from the module is not being properly bundled with the rest of the pages and thus giving the page flash. Any thoughts or feedback would be appreciated.
I ended up fixing the issue by refactoring the custom npm module to not use styled-jsx but instead use styled-components. Not really a fix but more of a work around