DEV Community

Cover image for Using Styled-Components with Next.js v13 (TypeScript)
Rashid Shamloo
Rashid Shamloo

Posted on • Updated on

Using Styled-Components with Next.js v13 (TypeScript)

Table of Contents

I recently started a Next.js v13 project with styled-components and had to jump through many hoops to have a working setup. In this article, I will go over the process step by step.

1. Install Next.js (with TypeScript)

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

2. Install the vscode-styled-components plugin

vscode-styled-components plugin

3. Install the styled-components package

npm install styled-components
Enter fullscreen mode Exit fullscreen mode

4. Add SSR support

Next.js renders the components on the server and hydrates them (adds the interactive parts) on the client. when using styled-components with Next.js, styles get applied on the client meaning the first render on the server will be without any styles and there will be a visible delay before the styles are applied.

There are multiple ways to add SSR support to styled-components with Next.js:

A. Enabling styled-components in next.config.mjs

You just need to edit your next.config.mjs file and add the following to it:

const nextConfig = {
  compiler: {
    styledComponents: true,
  ...
  },
};
Enter fullscreen mode Exit fullscreen mode

Source

B. Global style registry

Next.js suggests implementing a global style registry component that collects all the styles and applies them to the <head> tag.

  • Create the lib/registry.tsx file and add the following to it:
'use client'

import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

export default function StyledComponentsRegistry({
  children,
}: {
  children: React.ReactNode
}) {
  // Only create stylesheet once with lazy initial state
  // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
  const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())

  useServerInsertedHTML(() => {
    const styles = styledComponentsStyleSheet.getStyleElement()
    styledComponentsStyleSheet.instance.clearTag()
    return <>{styles}</>
  })

  if (typeof window !== 'undefined') return <>{children}</>

  return (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children}
    </StyleSheetManager>
  )
}
Enter fullscreen mode Exit fullscreen mode
  • In your root layout.tsx file import StyledComponentsRegistry and wrap children in it:
import StyledComponentsRegistry from './lib/registry'

export default function RootLayout(props: React.PropsWithChildren) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>
          {props.children}
        </StyledComponentsRegistry>
      </body>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode

C. The styled-components Babel plugin

styled-components provides a Babel plugin you can use for adding SSR support. To use it:

  • Install it using the following command:
npm i -D babel-plugin-styled-components
Enter fullscreen mode Exit fullscreen mode
  • Create the .babelrc file in the root of your project and add the following to it:
{
  "presets": ["next/babel"],
  "plugins": ["babel-plugin-styled-components"]
}
Enter fullscreen mode Exit fullscreen mode

Now, Next.js will automatically use Babel instead of SWC to compile your code since a custom configuration is present.

However, there are cases where this might not work, for example next/font requires SWC and you will get this error message if you're using it alongside the custom .babelrc configuration file:

Syntax error: "next/font" requires SWC although Babel is being used
due to a custom babel config being present.
Enter fullscreen mode Exit fullscreen mode

D. The styled-components SWC plugin

You can also use the styled-components SWC plugin:

  • Install it using the following command:
npm i -D @swc/plugin-styled-components
Enter fullscreen mode Exit fullscreen mode
  • Create the .swcrc file in the root of your project and add the following to it:
{
  "jsc": {
    "experimental": {
      "plugins": [
        [
          "@swc/plugin-styled-components",
          {
            "ssr": true
          }
        ]
      ]
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

5. Add global styles

Global styles is the place to add your fonts, CSS reset/normalize, and any other style you want applied globally.

You can achieve this using the createGlobalStyle() function:

  • Create the styles/GlobalStyles.ts file and add to it:
import { createGlobalStyle } from 'styled-components';

const GlobalStyles = createGlobalStyle`
  // your global styles
`;

export default GlobalStyles;
Enter fullscreen mode Exit fullscreen mode
  • In your root layout.tsx file, import and add the GlobalStyles component:
import StyledComponentsRegistry from './lib/registry'
import GlobalStyles from './styles/GlobalStyles';

export default function RootLayout(props: React.PropsWithChildren) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>
          <GlobalStyles />
          {props.children}
        </StyledComponentsRegistry>
      </body>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode

6. Add formatting to global styles

While writing your global styles you may have noticed that formatting doesn't work for it even though it works for other styled components. to fix the issue you can do one of the following in your styles/GlobalStyles.ts file:

  • Using the css helper function:
import { createGlobalStyle, css } from 'styled-components'

const styles = css`
  // your global styles
`;

const GlobalStyles = createGlobalStyle`
  ${styles}
`;

export default GlobalStyles;
Enter fullscreen mode Exit fullscreen mode

Source

  • Using an intermediate object:
import { createGlobalStyle } from 'styled-components'

const styled = { createGlobalStyle }

const GlobalStyles = styled.createGlobalStyle`
  // your global styles
`;

export default GlobalStyles;
Enter fullscreen mode Exit fullscreen mode

Source

7. Add a theme

You may have colors, font sizes, or other global variables that you need to access in multiple components. you can use a theme to do it:

  • Create the styles/theme.ts file and add your colors to it:
const theme = {
  colors: {
    colorName1: '#aabbcc',
    colorName2: 'hsla(50, 60%, 70%, 0.5)',
    ...
  },
};

export default theme;
Enter fullscreen mode Exit fullscreen mode
  • In your root layout.tsx file, import your theme as well as ThemeProvider and wrap children in it:
'use client';

import StyledComponentsRegistry from './lib/registry'
import GlobalStyles from './styles/GlobalStyles';
import { ThemeProvider } from 'styled-components';
import theme from './styles/theme';

export default function RootLayout(props: React.PropsWithChildren) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>
          <GlobalStyles />
          <ThemeProvider theme={theme}>
            {props.children}
          </ThemeProvider>
        </StyledComponentsRegistry>
      </body>
    </html>
  )
}
Enter fullscreen mode Exit fullscreen mode
  • Access the theme in your components:
import { styled } from 'styled-components';

export const MyDiv = styled.div`
  background-color: ${({ theme }) => theme.colors.colorName1};
`;
Enter fullscreen mode Exit fullscreen mode

8. Add the theme type

You may have noticed when using the theme in your components, you are not getting any IntelliSense/auto-complete with TypeScript. To fix it:

  • Create the types/styled.d.ts file and add the following:
import 'styled-components';
import { theme } from '../styles/theme';

type Theme = typeof theme;

declare module 'styled-components' {
  export interface DefaultTheme extends Theme {}
}
Enter fullscreen mode Exit fullscreen mode

Source

Now you have auto-complete with your theme:

Styed-components theme auto-complete

9. Move providers into a separate file (optional)

If you don't like having 'use client' in your layout.tsx file, and when you have multiple providers as we do now, you can move all of your providers to a Providers.tsx file and import and use it in your layout.tsx file instead:

  • Create the Providers.tsx file and add the following:
'use client';

import StyledComponentsRegistry from './styles/registry';
import { ThemeProvider } from 'styled-components';
import theme from './styles/theme';

const Providers = (props: React.PropsWithChildren) => {
  return (
    <StyledComponentsRegistry>
      <ThemeProvider theme={theme}>
        {props.children} 
      </ThemeProvider>
    </StyledComponentsRegistry>
  );
};

export default Providers
Enter fullscreen mode Exit fullscreen mode
  • Edit your layout.tsx file to look like this:
import Providers from './Providers';
import GlobalStyles from './styles/GlobalStyles';

export default function RootLayout(props: React.PropsWithChildren) {
  return (
    <html lang="en">
      <body>
        <Providers>
          <GlobalStyles />
          {props.children}
        </Providers>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

🎉 Congratulations! Now you can finally start making your styled-components with Next.js v13!

Top comments (7)

Collapse
 
chasebeadles profile image
Chase Beadles

Thanks! Very helpful! :)

I had to use both A and B of step 4 to get SSR working.

I used the .tsx extension for both:
lib/registry.tsx and Providers.tsx to get rid of linter errors.

<GlobalStyles /> also needed to be moved to the Providers file in order to remove "use client" from Layout.tsx and be able to export my metadata.

types/styled.d.ts needed to be in the src directory for my intelliSense to work. I ended up putting lib, styles, and types under src.

Collapse
 
rashidshamloo profile image
Rashid Shamloo • Edited

Thanks for sharing your steps and you're absolutely right,

  • registry and Providers should have the tsx extension. I've fixed it in the article.

  • I think I ended up adding 'use client' to my GlobalStyle.ts file itself in my own project but adding it to Providers works too.

  • I couldn't reproduce this. putting the types/styled.d.ts file outside worked for me.

Collapse
 
kirsantov profile image
kirsantov

This is very usual example! but GlobalStyle doesn't work in Nextjs 13 yet! I tired to listen it, looks like Nextjs try to avoid Styled-Components and promote everywhere only Tailwind. Just type in google - "most popular css framework for nextjs" and all will clear. t is a pity, like Vercel hosting. Nextjs disappointing me.

Collapse
 
rashidshamloo profile image
Rashid Shamloo

Can't say much about Vercel pushing Tailwind, but GlobalStyle does work in Next.js v13.
You can check this example: GitHub

Collapse
 
mayomi1 profile image
Mayomi Ayandiran

Thank you, this is very helpful

Collapse
 
douglasrcjames profile image
Douglas James

Attempted to implement this ThemeProvider usage for changeable user theme, following some of @chasebeadles point, but I keep getting a app-index.js:31 Warning: PropclassNamedid not match. Server: "text__P- error in the console. Didn't seem to break anything, but not sure if I want this consistently on my app just for theme mode flipping. Any idea how I might fix this error?

Collapse
 
kaankucukx profile image
Kaan KÜÇÜK

The article was looking good until I noticed that you moved the setup to another component using "use client". This raised some concerns for me. Since Providers.tsx, layout.tsx, and other files will be rendered to a single component, it seems like it's still doing the same thing as inlining providers to the layout. Could you please help me understand the difference?