reactjsnext.jsstyled-componentsfouc

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


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


Solution

  • I found two solutions which helped -->

    1. 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}}>

    1. 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