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
Related
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.
I'm trying to authenticate in Keycloak in Next.js using #react-keycloak/nextjs but once I pass the login it keeps sending me back to the Keycloak server and returning to the main page until it gives me an error because my token key is expired.
This is my _app.js:
import { ApolloProvider } from '#apollo/react-hooks'
import CssBaseline from '#material-ui/core/CssBaseline'
import { ThemeProvider } from '#material-ui/core/styles'
import { Persistors, SSRKeycloakProvider } from '#react-keycloak/nextjs'
import Layout from 'components/layout/Layout'
import { useApollo } from 'lib/apolloClient'
import KeycloakLoading from 'components/KeycloakLoading'
import {
keycloakCfg,
keycloakProviderInitConfig,
onKeycloakLogout,
onKeycloakTokens,
} from 'lib/keycloak'
import Head from 'next/head'
import PropTypes from 'prop-types'
import React from 'react'
import theme from 'styles/theme'
import 'leaflet/dist/leaflet.css'
import 'react-leaflet-markercluster/dist/styles.min.css'
import 'styles/styles.css'
import 'styles/animations.css'
import '../styles/globals.css'
// export function reportWebVitals(metric) {
// // These metrics can be sent to any analytics service
// console.log(metric)
// }
export default function MyApp(props) {
const { Component, pageProps, cookies } = props
const apolloClient = useApollo(pageProps.initialApolloState)
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side')
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles)
}
}, [])
return (
<React.Fragment>
<Head>
<title>My page</title>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<SSRKeycloakProvider
keycloakConfig={keycloakCfg}
persistor={Persistors.Cookies(cookies)}
initConfig={keycloakProviderInitConfig}
onTokens={onKeycloakTokens}
onAuthLogout={onKeycloakLogout}
LoadingComponent={
<React.Fragment>
<KeycloakLoading />
</React.Fragment>
}
>
<ApolloProvider client={apolloClient}>
<Layout>
<Component {...pageProps} />
</Layout>
</ApolloProvider>
</SSRKeycloakProvider>
</ThemeProvider>
</React.Fragment>
)
}
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired,
cookies: PropTypes.any,
}
And this is my Keycloak configuration
export const keycloakCfg = {
realm: APP_CONSTANTS.KEYCLOAK_REALM,
url: `${APP_CONSTANTS.KEYCLOAK_HOST}/auth`,
clientId: APP_CONSTANTS.KEYCLOAK_CLIENT_ID,
}
export const keycloakProviderInitConfig = {
onLoad: 'login-required',
}
If anyone could point out what is wrong or alternatively tell me another way to use Keycloak in next.
React-keycloak/nextjs is deprecated in favor of react-keycloak/ssr. I followed the examples here: https://www.npmjs.com/package/#react-keycloak/ssr and was able to fully authenticate without issue.
I'm not sure what is causing your looping issues but it doesn't look like you are using the required cookie parser or passing them into the props via getInitialProps so that may be a good place to start.
An alternative would be:
Next-Auth has implemented a keycloak provider which would make your life much easier protecting both Frontend and Backend. Just specify keycloak options/configs in _app.js and you all set.
import {signOut, getSession, useSession } from 'next-auth/react';
export default function Home() {
let session = useSession();
console.log(session);
return (
<div >
<button onClick={() => {
console.log(session);
signOut();
console.log(session);
}}> Sign Out </button>
<h1>H1 test</h1>
</div>
)
}
export const getServerSideProps = async (ctx) => {
const backendSession = await getSession(ctx)
console.log(backendSession);
return {
props: {
}
}
}
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 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'm trying to use material-ui with next.js / react.js.
When applying a layout file through _app.js I'm getting an error.
This is using the example react.js template material-ui provides (so confusing why it wouldn't work simply - i get the impression that material-ui has issues with next.js generally?).
I've tried a few different approaches, changing document and app, still won't seem to work.
Here's the Layout.js file
import React from 'react';
import clsx from 'clsx';
import { makeStyles } from '#material-ui/core/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import Drawer from '#material-ui/core/Drawer';
import List from '#material-ui/core/List';
import Divider from '#material-ui/core/Divider';
import IconButton from '#material-ui/core/IconButton';
import Badge from '#material-ui/core/Badge';
import Container from '#material-ui/core/Container';
import Grid from '#material-ui/core/Grid';
import Paper from '#material-ui/core/Paper';
import Link from '#material-ui/core/Link';
import MenuIcon from '#material-ui/icons/Menu';
import ChevronLeftIcon from '#material-ui/icons/ChevronLeft';
import NotificationsIcon from '#material-ui/icons/Notifications';
import { mainListItems, secondaryListItems } from '../Sidebar/Sidebar';
import Chart from '../Charts/Chart';
import Deposits from '../Deposits';
import Orders from '../Orders';
import Menu from '../AppBar/Menu';
const drawerWidth = 240;
const useStyles = makeStyles(theme => ({
........
export default function Layout() {
const classes = useStyles();
const [open, setOpen] = React.useState(true);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight);
return (
<div className={classes.root}>
<CssBaseline />
<Menu />
<Drawer
variant="permanent"
classes={{
paper: clsx(classes.drawerPaper, !open && classes.drawerPaperClose),
}}
open={open}
>
<div className={classes.toolbarIcon}>
<IconButton onClick={handleDrawerClose}>
<ChevronLeftIcon />
</IconButton>
</div>
<Divider />
<List>{mainListItems}</List>
<Divider />
</Drawer>
{props.children}
</div>
);
}
//<List>{secondaryListItems}</List> later
Heres app.js
import React from 'react';
import App, { Container } from 'next/app';
import Head from 'next/head';
import { ThemeProvider } from '#material-ui/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import theme from '../src/theme';
import Layout from '../components/Layout/Layout';
class MyApp extends App {
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentNode.removeChild(jssStyles);
}
}
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Head>
<title>My page</title>
</Head>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Layout>
<Component {...pageProps} />
</ Layout>
</ThemeProvider>
</Container>
);
}
}
export default MyApp;
and document.js
import React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '#material-ui/styles';
import theme from '../src/theme';
class MyDocument extends Document {
render() {
return (
<html lang="en">
<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>
);
}
}
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.Fragment key="styles">
{initialProps.styles}
{sheets.getStyleElement()}
</React.Fragment>,
],
};
};
export default MyDocument;
And here is the error information.
×
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://xxxx-invalid-hook-call for tips about how to debug and fix this problem.
▶ 11 stack frames were collapsed.
ctx.renderPage
./pages/_document.js:60
57 | const sheets = new ServerStyleSheets();
58 | const originalRenderPage = ctx.renderPage;
59 |
> 60 | ctx.renderPage = () =>
| ^ 61 | originalRenderPage({
62 | enhanceApp: App => props => sheets.collect(<App {...props} />),
63 | });
View compiled
▶ 4 stack frames were collapsed.
This screen is visible only in development. It will not appear if the app crashes in production.
Open your browser’s developer console to further inspect this error.
As far as I am aware you cannot use hooks inside class components and your MyDocument is declared as a class. Maybe try converting it to a functional component.
I solve this problem installing npm i #emotion/react #emotion/styled