32

I have a website built with Nextjs that break styles on page refresh or when a user visits the website directly to a specific route and not the root route. Eg https://vinnieography.web.app/contacts (The site link if it looks ok, try to refresh and see)

The website is hosted on Firebase Functions and uses Nextjs and Ant design components.

Screenshot of the site before a refresh

Screenshot of the site before a refresh

Screenshot of the site after a refresh (Notice the missing Nav)

The Nav is not completely missing but it became a mobile nav whose icon is not shown but you get a dropdown with Nav links when hovering around the Nav area.

Screenshot of the site after a refresh

My next.config.js

const withCss = require('@zeit/next-css')

module.exports = withCss({
  webpack: (config, { isServer }) => {
    if (isServer) {
      const antStyles = /antd\/.*?\/style\/css.*?/
      const origExternals = [...config.externals]
      config.externals = [
        (context, request, callback) => {
          if (request.match(antStyles)) return callback()
          if (typeof origExternals[0] === 'function') {
            origExternals[0](context, request, callback)
          } else {
            callback()
          }
        },
        ...(typeof origExternals[0] === 'function' ? [] : origExternals),
      ]

      config.module.rules.unshift({
        test: antStyles,
        use: 'null-loader',
      })
    }

    // Fixes npm packages that depend on `fs` module
    config.node = {
      fs: 'empty'
    }

    return config
  },
  distDir: "../../dist/client"
})

Versions of Nextjs, React & Antd.

"antd": "^3.24.2",
"next": "^9.0.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"@zeit/next-css": "^1.0.1",
ArchNoob
  • 3,946
  • 5
  • 32
  • 59
  • 1
    the homepage is broken aswell (try refresh), may this issue your problem? https://github.com/zeit/next.js/issues/4597 – Nico Feb 04 '20 at 16:38
  • I'm not sure but, everything loads on my part it's just the styles that fail to render correctly. – ArchNoob Feb 04 '20 at 21:57
  • 13
    You have hydration issues involving the styled-components package. In short, your server and client `classNames` don't match (please check your dev console, it's one of the first warnings to show up). Use this example to set up a custom `_document.js` file to configure `styled-components` properly: https://github.com/zeit/next.js/blob/master/examples/with-styled-components/pages/_document.js. In addition, you have a few other warnings that need to be addressed as well, but this config should at least fix the styling issues. – Matt Carlotta Feb 07 '20 at 01:28
  • @MattCarlotta indeed it was my server and client styles mismatching. Thank you very much! – ArchNoob Feb 07 '20 at 06:41
  • 1
    @ArchNoob Glad that was the problem. Simple fix. For the other warnings, please read this about using `useLayoutEffect` in SSR: https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85 – Matt Carlotta Feb 07 '20 at 07:13
  • @MattCarlotta **Simple fix !** I also used `useLayoutEffect` because the function it runs reads the user window's width. Is this use case valid for only `useLayoutEffect`? – ArchNoob Feb 07 '20 at 07:30
  • 3
    `useLayoutEffect` is good for pre-render, layout computations on the client, however, since the server doesn't have a `window`, the lifecycle and the window function won't work as expected. Instead, you should delay the execution until the client has been loaded. I'd recommend lazy loading the component. In other words, render block (see `conditional rendering` in the official React docs) what the user sees until the window has been calculated and the UI is ready to be **completely** rendered. – Matt Carlotta Feb 07 '20 at 08:41
  • I will go with lazily loading the component then. Dang, I want to give you these 50 reps man, you helped me a lot! – ArchNoob Feb 07 '20 at 08:45
  • 3
    @MattCarlotta, I think you should move your comment to an answer as it was helpful for the problem solution – Kenneth Clarkson Feb 13 '20 at 08:31
  • You could use `css`. If you are worried about **class name conflict**, `nextjs` has support for it. Visit here: https://nextjs.org/docs/basic-features/built-in-css-support –  Apr 13 '20 at 07:37
  • Thank you for the alternative, I prefer `styled-components` over `CSS` files due to the way you can extend styles & components in `styled-components` and it's flexibility with components (eg. using props values to alter `CSS` properties). – ArchNoob Apr 14 '20 at 15:04

8 Answers8

12

If modifying the app as suggested in Material-UI NextJS examples did not help, you can lazy load your component. This way you will force it to create styles only after the client-side is loaded.

Guide to disable SSR for a component: https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr

import dynamic from 'next/dynamic'

export const ComponentWithNoSSR = dynamic(() => import('./Component'), {
  ssr: false,
})

However, keep in mind that the component will lose all perks of SSR.

zilijonas
  • 3,285
  • 3
  • 26
  • 49
  • This works perfectly. I wish theres a way to "rehydrate" the react component on the client with SSR – C.OG Jan 10 '21 at 21:32
12

I got this issue when working with NextJS 9.4 with MaterialUI.

I got this warning in console whenever styles are broken

Warning: Prop className did not match. Server: "MuiBox-root MuiBox-root-5" Client: "MuiBox-root MuiBox-root-1" in span (created by Styled(MuiBox))

enter image description here

This is how I fixed.

We need custom document.js and custom app.js as mentioned in MaterialUI NextJS example

Copy _document.js and _app.js from here https://github.com/mui-org/material-ui/tree/master/examples/nextjs/pages

enter image description here

to pages folder

enter image description here

Copy theme.js from https://github.com/mui-org/material-ui/tree/master/examples/nextjs/src and place it somewhere and update the import links in _document.js and _app.js

The styles should work now.

kiranvj
  • 32,342
  • 7
  • 71
  • 76
  • I am using Material UI 4 and for that I have already implemented custom `_app.js` and `_document.js` but that didn't work. Here is my [stackblitz example](https://stackblitz.com/edit/nextjs-rmnsi5?file=pages/_app.js) – fahad shaikh Jun 20 '22 at 04:47
  • @fahadshaikh your `_app.js` and `_document.js` is in `api` directory. Move these files directly under `pages` directory, restart server and check. – kiranvj Jun 20 '22 at 07:07
5

TLDR: You cannot use conditional rendering based on mobile breakpoint with Next.js since server side rendering does not have access to the dimensions of your browser. This explains the visual distortions on refresh. The only robust way to detect breakpoints with server side rendering is by using CSS media queries for hiding/unhiding mobile components.

According to this blog:

The server doesn’t recognize the window neither document. This means that the device, in other words, cannot detect obligatory properties (such as the viewport dimensions of the client) - thereby it needs to infer them someway, which means, a pretty limited and non-accurate way to respond.

For instance, imagine we’ve an application that uses matchMedia (which, as you probably know, is a Web API that arrives on top of thewindow) to render components conditionally based on the viewport dimensions. How would you expect the server to render the markup without thewindow, and even if it’s hypothetically polyfilled somehow, what about the dimensions? How would it respond once the initial render contains a responsive component that conditionally affected by a breakpoint?

Put it simply - this might cause the server to render our application incorrectly, which eventually, leads to partial hydration that patches the mismatches (namely potential bugs?).

Embedded_Mugs
  • 2,132
  • 3
  • 21
  • 33
1

In order to reliably perform server-side rendering and have the client-side bundle pick up without issues, you'll need to use our babel plugin. It prevents checksum mismatches by adding a deterministic ID to each styled component. Refer to the tooling documentation for more information.

{
 "plugins": [
   [
     "babel-plugin-styled-components",
    {
     "ssr": false
    }
  ]
 ]
}
ObinasBaba
  • 478
  • 2
  • 8
1

I also had the same issue. But I had to import multiple components and return it based on condition. This was my approach. And I use .then since dynamic is a promise.

import React, { useState, useEffect } from 'react';
import useDataStore from 'store';

const DynamicComponent = ({ componentName }) => {
  const [Component, setComponent] = useState(null);

  useEffect(() => {
    import(`./modules/${componentName}`)
      .then((mod) => {
        setComponent(mod.default);
      })
      .catch((error) => {
        console.error(error);
      });
  }, [componentName]);

  return Component ? <Component /> : <div>Loading...</div>;
};

function Rules() {
  const selectedTab = useDataStore((state) => state.selectedTab);

  let componentName;
  if (selectedTab === 'steak') {
    componentName = 'steak';
  } else if (selectedTab === 'mutton') {
    componentName = 'mutton';
  } else(selectedTab === 'default') {
    componentName = 'default';
  }

  return <DynamicComponent componentName={componentName} />;
}

export default Rules;
1

I had the same issue while using styled-components. try this :

install the babel plugin using :

npm i --save-dev "babel-plugin-styled-components"

then make a file named as .babelrc and paste the following code

{
    "presets" : ["next/babel"],
    "plugins" : [
        [
            "styled-components", {"ssr" : true}
        ]
    ]
}

now run the code!

0

Use dynamic import ssr:false to import the component will solve this problem

import dynamic from 'next/dynamic'

const DynamicComponentWithNoSSR = dynamic(
  () => import('../components/hello3'),
  { ssr: false }
)
Him Ho
  • 79
  • 1
  • 6
0

This is because the server-side rendering does not fetch the styles before rendering the page. The Styled-Components documentation mentions this very briefly and it is easy to miss. What we actually have to do is inject server-side rendered styles to the head so it can render the page and its styles correctly. Next.js has the exact file you'll need to make this possible. Within the Next.js Github repo you can find _document.js.

I met a similar issue with using styled-Components. Registering styled—components into _document.js solves my problem.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 05 '22 at 07:31