Search code examples
reactjsnext.jsstyled-componentsnext-auth

Next.js Styled Components weird behaviour


So I'm working on a project, but my Sidebar Component is behaving kinda weird:

  • I (re-)start the server and the component looks like this
  • I click a link item / I visit another URL and it looks like this

I don't know much about Components, even less about styled-components, so I don't know where to even start.

components/Sidebar.ts

import React, { useState } from 'react';
import { SidebarData } from './SidebarData';
import styled from 'styled-components';
import * as Remix from 'react-icons/ri';
import Submenu from './Submenu';
import { useSession } from 'next-auth/react';


/* 
   --text-color: #ffffff;
  --text-secondary: #fefefe;
  --background-color: #091540;
  --background-secondary: #262c49;
  --background-third: #1a254d;
  --blue: #256EFF;
  --red: #FF495C;
  --green: #3DDC97;
*/

const Nav = styled.div`
   background: #091540;
   height: 8vh;
   display: flex;
   justify-content: flex-start;
   align-items: center;
   color: #ffffff;
`

const NavIcon = styled.div`
   margin-left: 2vw;
   font-size: 4vh;
   height: 8vh;
   display: flex;
   justify-content: flex-start;
   align-items: center;
   cursor: pointer;
`

const SidebarNav = styled.div`
   background: #091540;
   width: 25vh;
   height: 100%;
   display: flex;
   justify-content: center;
   position: fixed;
   top: 0;
   left: ${({ sidebar }) => (sidebar ? '0' : '-100%')};
   transition: 350ms;
   z-index: 10;
   color: #ffffff;
`

const SidebarWrap = styled.div`
   width: 100%;
`

const Sidebar = () => {

   const [sidebar, setSidebar] = useState(false);
   const toggleSidebar = () => setSidebar(!sidebar);
   const { data: session, status } = useSession();
   
   return (
      <div>
         <Nav>
            <NavIcon to="#">
               <Remix.RiMenuFill onClick={toggleSidebar} />
            </NavIcon>
         </Nav>
         <SidebarNav sidebar={sidebar}>
            <SidebarWrap>
            <NavIcon to="#">
               <Remix.RiMenuFoldFill onClick={toggleSidebar}/>
            </NavIcon>
            {SidebarData.map((item, index) => {
               if(session && item.disappearOnLogin === true && item.authRequired === false) return;
               if(!session && item.disappearOnLogin === false && item.authRequired === true) return;
               return <Submenu item={item} key={index}/>
            })}
            </SidebarWrap>
         </SidebarNav>
      </div>
   )
}

export default Sidebar;

Solution

  • So basically in order to use styled-components with server side rendering, you need to employ stylesheet rehydration. When your app renders on the server, you can create a ServerStyleSheet and add a provider to your application which accepts styles via context API.

    To do this, you want to install a package called babel-plugin-styled-components

    npm i babel-plugin-styled-components
    

    Then, you want to create a .babelrc file in the root directory of your project and add these lines to that file:

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

    Then you want to go to the pages folder and create a _document.js page. Add the following code to this file:

    import Document from "next/document";
    import { ServerStyleSheet } from "styled-components";
    
    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();
        }
      }
    }
    

    There is a way to do this with a .tsx file but I was not sure of that and using a .js file worked for me so I will leave you to figure out the Typescript version if you want.

    Restart your server and your styled components should get picked up just fine.