Next.js 9+ FOUC (Flash or Unstyled Content) with Styled Components - reactjs

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 :)

Related

How to both dynamically import and render a component on button click in nextjs?

I know that there are several nextjs and reactjs questions out there in this topic but I think this is specific enough to get an own thread. So I have a NextJS project where I want a button to disappear on click and then import a component dynamically and render it.
It is crucial to import the component dynamically because of 2 things.
because we work with a library in the component which relies on 'window'
because the component affects load time and we only need it when the 'init button' is clicked
Here is my index.js code so far:
import Head from "next/head";
import Image from "next/image";
import Link from "next/link";
import dynamic from "next/dynamic"
import React, { useEffect, useState } from "react";
import { Inter } from "#next/font/google";
const inter = Inter({ subsets: ["latin"] });
export default function Home() {
return (
<>
<Head>
<title>My page</title>
<meta
name='description'
content="Some desc. content"
/>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<link rel='icon' href='/favicon.ico' />
</Head>
<button onClick={initSurvey()}>Initialize!</button>
</>
)
}
function initSurvey(){
console.log("The button is working!")
const Survey = dynamic(() => import("../components/SurveyComponent"), {
ssr: false,
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Please keep in mind this is not all I could do I just cleaned up the failed attempts to deliver you a clean, readable and understandable snippet.
Thank you in advance!
By checking the React.lazy documentation and this guide, I managed to get to this solution:
import { lazy, Suspense, useState } from "react";
const dynamicComponent = lazy(() => import('../components/MyDynamicComponent');
export default function Home() {
const [dynComp, setDynComp] = useState(<div/>);
const loadDynamicComponent = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<dynamicComponent/>
</Suspense>
)
}
return (
<>
<button onClick={ () => setDynComp(<div>{loadDynamicComponent()}</div>)}>Initialize!</button>
{ dynState }
</>
);
}
Test it and let me know if it helps you!

Why material UI is broken after a hard refresh in Nextjs

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

How can I use Keycloak in Next.js?

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: {
}
}
}

Next.js and Material-UI leading to invalid hook call?

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

Intense FOUC when using Next.js 8 and styled-jsx

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

Resources