Using Styled-Components with Next.js v13 (TypeScript)
Tags: nextjs, typescript, webdev, tutorial
Published: 2023-08-07 01:57:22

Table of Contents
- 1. [Install Next.js (with TypeScript)](#1)
- 2. [Install the `vscode-styled-components` plugin](#2)
- 3. [Install the `styled-components` package](#3)
- 4. [Add SSR support](#4)
- 5. [Add global styles](#5)
- 6. [Add formatting to global styles](#6)
- 7. [Add a theme](#7)
- 8. [Add the `theme` type](#8)
- 9. [Move providers into a separate file (optional)](#9)

I recently started a Next.js v13 project with [styled-components](https://www.styled-components.com) and had to jump through many hoops to have a working setup. In this article, I will go over the process step by step.

## <a name="1"></a>1. Install Next.js (with TypeScript)
```
npx create-next-app@latest
```

## <a name="2"></a>2. Install the `vscode-styled-components` plugin

![vscode-styled-components plugin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cn9xkcctl2zz2uqhm4c3.png)

## <a name="3"></a>3. Install the `styled-components` package

```
npm install styled-components
```

## <a name="4"></a>4. Add SSR support

Next.js renders the components on the server and [hydrates](https://en.wikipedia.org/wiki/Hydration_(web_development)) 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`</h3>

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

```js
const nextConfig = {
  compiler: {
    styledComponents: true,
  ...
  },
};
```
[Source](https://nextjs.org/docs/architecture/nextjs-compiler#styled-components)

### B. Global style registry

Next.js suggests implementing a [global style registry component](https://nextjs.org/docs/app/building-your-application/styling/css-in-js#styled-components) that collects all the styles and applies them to the `<head>` tag.

- Create the `lib/registry.tsx` file and add the following to it:

```ts
'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>
  )
}
```

- In your root `layout.tsx` file import `StyledComponentsRegistry` and wrap `children` in it:

```ts
import StyledComponentsRegistry from './lib/registry'
 
export default function RootLayout(props: React.PropsWithChildren) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>
          {props.children}
        </StyledComponentsRegistry>
      </body>
    </html>
  )
}
```

### C. The `styled-components` Babel plugin

`styled-components` provides a [Babel plugin](https://styled-components.com/docs/tooling#babel-plugin) you can use for adding [SSR](https://nextjs.org/learn/foundations/how-nextjs-works/rendering) support. To use it:

- Install it using the following command:

```
npm i -D babel-plugin-styled-components
```

- Create the `.babelrc` file in the root of your project and add the following to it:

```
{
  "presets": ["next/babel"],
  "plugins": ["babel-plugin-styled-components"]
}
```
Now, Next.js will automatically use [Babel](https://babeljs.io/) instead of [SWC](https://swc.rs/) to compile your code since a custom configuration is present.

However, there are cases where this might not work, for example [`next/font`](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) 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.
```

### D. The `styled-components` SWC plugin

You can also use the `styled-components` [SWC plugin](https://www.npmjs.com/package/@swc/plugin-styled-components):

- Install it using the following command:

```
npm i -D @swc/plugin-styled-components
```

- 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
          }
        ]
      ]
    }
  }
}

```

## <a name="5"></a>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()`](https://styled-components.com/docs/api#createglobalstyle) function:

- Create the `styles/GlobalStyles.ts` file and add to it:

```ts
import { createGlobalStyle } from 'styled-components';

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

export default GlobalStyles;
```

- In your root `layout.tsx` file, import and add the `GlobalStyles` component:

```ts
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>
  )
}
```

## <a name="6"></a>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:
```ts
import { createGlobalStyle, css } from 'styled-components'

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

const GlobalStyles = createGlobalStyle`
  ${styles}
`;

export default GlobalStyles;
```
[Source](https://github.com/prettier/prettier/pull/9025#issuecomment-678655928)

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

const styled = { createGlobalStyle }

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

export default GlobalStyles;
```
[Source](https://github.com/prettier/prettier/issues/11196#issuecomment-951878725)

## <a name="7"></a>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](https://styled-components.com/docs/advanced#theming) to do it:

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

export default theme;
```

- In your root `layout.tsx` file, import your `theme` as well as `ThemeProvider` and wrap `children` in it:

```ts
'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>
  )
}
```

- Access the `theme` in your components:

```ts
import { styled } from 'styled-components';

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

## <a name="8"></a>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:

```ts
import 'styled-components';
import { theme } from '../styles/theme';

type Theme = typeof theme;

declare module 'styled-components' {
  export interface DefaultTheme extends Theme {}
}
```
[Source](https://stackoverflow.com/a/72071824)

Now you have auto-complete with your theme:

![Styed-components theme auto-complete](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4g1yrcgw4mcsvnteqhif.png)

## <a name="9"></a>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:

```ts
'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
```
- Edit your `layout.tsx` file to look like this:

```ts
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>
  );
}
```
---

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