DEV Community

Cover image for Nextjs + typescript +styled-components
Renan Aguiar
Renan Aguiar

Posted on

Nextjs + typescript +styled-components

Summary

1 - Add Typescript
2 - Install styled-components
3 - Apply globalStyle
4 - Bonus 1 - Absolute imports
5 - Bonus 2 - SSR with stylesheet rehydration

In case you get lost, all the code is available on https://github.com/rffaguiar/setup-nextjs-typescript-styled-components

You can also reach me on twitter @rffaguiar.

Let's move on!

You have just started to learn Next.js on https://nextjs.org/learn/basics/create-nextjs-app. Now you want to start building your amazing apps on your own. The small tutorial didn't teach how to add styled-components, typescript, or global style. Don't worry, let me baby step on these.

Attention: the following package versions were used on this setup:

"dependencies": {
    "next": "9.4.4",
    "react": "16.13.1",
    "react-dom": "16.13.1",
    "styled-components": "^5.1.1"
  },
  "devDependencies": {
    "@types/node": "^14.0.9",
    "@types/react": "^16.9.35",
    "babel-plugin-styled-components": "^1.10.7",
    "typescript": "^3.9.3"
  }

Add Typescript

Rename any of your .js to .tsx . Go ahead and rename your index.js to index.tsx and try to start your server. You will receive an error on CLI that you're trying to use Typescript but you don't have the necessary packages. Run:

npm install --save-dev typescript @types/react @types/node

When you start the server after the ts packages, 2 files are created for you: next-env.d.ts and tsconfig.json.

  • next-env.d.ts: Nextjs type declaration file referencing its types inside of your node_modules/next/types/global
  • tsconfig.json: contains the compiler options required to compile the project and specifies the root.

Your typescript is ready.

Install styled-components

npm install --save styled-components

For testing purposes, make your index.tsx like this:

import styled from "styled-components";

const Title = styled.h1`
  color: red;
`;

const Home = () => {
  return (
    <div>
      <p>hello</p>
      <Title>Title</Title>
    </div>
  );
};

export default Home;

Go to the browser and inspect the Title (h1) element.

Title element with weird className

See how terrible that className is? .sc-AxjAm.gxygnu certainly isn't descriptive!

That's why it's recommended to install the babel plugin together.

npm install --save-dev babel-plugin-styled-components

Create a file .babelrc at the root of your project with the following:

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

Restart the server and inspect the element again.

Title element with a better className

Pretty cool, right? Now the className is a lot more descriptive.
The babel plugin gives more power-ups to styled-components + Nextjs:

  • Smaller bundles
  • Server-side rendering compatibility
  • Better debugging
  • Minification
  • Dead code elimination

Apply globalStyle

Cool! Now you have an incredible JS framework with a powerful style system. But...wait, what if you wanted to reset and/or share styles across all of your pages? Here we go with styled-components' globalStyle.

First, let's start with a Layout component. This is going to wrap every page and has all the shared styles.

Outside the /pages directory, create another folder /layout with Basic.tsx inside:

# /layout
# --/Basic.tsx
# /pages
# --/index.tsx

Inside of Basic.tsx you include and return your shared styles. The trick here is to include the createGlobalStyle and return it on Basic.tsx render.

import { createGlobalStyle } from "styled-components";

export const GlobalStyle = createGlobalStyle`
    // this is the shared style
  html {
    box-sizing: border-box;
  }

  *,
  *::before,
  *::after {
    box-sizing: inherit;
  }

h1 {
    color: yellow !important; // the important is just to show that the style works!
}

  // anything else you would like to include
`;

const BasicLayout = ({ children }: { children: any }) => {
  return (
    <>
      <GlobalStyle />
      {children}
    </>
  );
};

export default BasicLayout;

Returning to pages/index.tsx. Import the newly created BasicLayout component and wrap the Home returned React element with BasicLayout.

import styled from "styled-components";
import BasicLayout from "../layout/Basic";

const Title = styled.h1`
  color: red;
`;

const Home = () => {
  return (
    <BasicLayout>
      <p>hello</p>
      <Title>Title</Title>
    </BasicLayout>
  );
};

export default Home;

From now on, all the pages that include BasicLayout components are going to inherit the styles.

Congratulations!!! Now you have a proper Nextjs + Typescript + styled-component with global styles working!

Bonus 1 - Absolute imports

By default Nextjs allows you to use relative imports. You know, those never-ending imports ../../../../finally.tsx. If you want to use an absolute import, you have to change just one line on tsconfig.json: the baseUrl.

"compilerOptions": {
    // other options
    "baseUrl": "."
  },

Now, all absolute imports start at the same level as the tsconfig.json file. Using the previous pages/index.tsx import as an example, you can change A to B.

// A
import BasicLayout from "../layout/Basic";

// B
import BasicLayout from "layout/Basic";

Bonus 2 - SSR with stylesheet rehydration

The fancy term which means: serve the required styles for the first render within the HTML and then load the rest in the client.

You need to create a custom /pages/_document.tsx and copy the following logic into it. That's it.

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()
    }
  }
}

The code above was taken directly from the styled-components example on nextjs github repo.

Oldest comments (13)

Collapse
 
simonolsen profile image
Simon Olsen

Fantastic walkthrough! Your GlobalStyle setup is much better than the <style jsx global> you get OOTB with Next.js.

I was hoping for a bit more TypeScript config. I'm trying to apply some types to my styled-components. I followed the API reference for Typescript on the styled-components website but I couldn't get it working with my Next.js site. When I tried to apply some props from a DefaultTheme, example

color: ${props => props.theme.colors.main};
Enter fullscreen mode Exit fullscreen mode

I kept getting...

TypeError: Cannot read property 'main' of undefined
Enter fullscreen mode Exit fullscreen mode

I checked your example but there is no TypeScript checking in there.

Have you had any success with adding types to styled-components?

Collapse
 
rffaguiar profile image
Renan Aguiar

Hi Simon! I'm glad you liked it!
Sorry about the really long delay. I didn't receive a notification.

Did you solve the problem? I never tried to use types on my styles because they have a very dynamic nature, each one is so different from the other that I find counterproductive to create types for them.

About your error: you're trying to get a property main from another one that doesn't exist in the context, which is colors. Have you imported and set the colors property?

Collapse
 
keyes343 profile image
keyes343

Include a module file for themes, for styled components, at the root of your project. I found this solution on stackoverflow somewhere recently.

Collapse
 
clemoh profile image
Clement Oh

This was super helpful. Thank you! Was getting a SSR component className did not match error when using styled components. This setup fixed it for me.

Collapse
 
farynaio profile image
Adam Faryna • Edited

Isn't:

  html {
    box-sizing: border-box;
  }

  *,
  *::before,
  *::after {
    box-sizing: inherit;
  }
Enter fullscreen mode Exit fullscreen mode

should be simply write as:

* {
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

?

Collapse
 
8kigai profile image
8kigai
Collapse
 
nikodermus profile image
Nicolás M. Pardo

If you are using string TypeScript as I am, this is _document.tsx with all types included:

import Document, { DocumentContext, DocumentInitialProps } from 'next/document';
import { ServerStyleSheet } from 'styled-components';

export default class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    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();
    }
  }
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
ging3rmint profile image
Nate

This was super helpful but i keep getting JSX.element type error at

  return {
    ...initialProps,
    styles: (
      <>
        {initialProps.styles}
        {sheet.getStyleElement()}
      </>
    ),
  };
Enter fullscreen mode Exit fullscreen mode

is there any way to fix this?

Collapse
 
popchairat profile image
Chairat Ko

Chang from styles:(), to styles:[],

Thread Thread
 
nosovandrew profile image
Andrew Nosov

You've saved my time! Thanks!!!

Collapse
 
nikodermus profile image
Nicolás M. Pardo • Edited

If you are using strict TypeScript as I am, this is _document.tsx with all types included:

import Document, { DocumentContext, DocumentInitialProps } from 'next/document';
import { ServerStyleSheet } from 'styled-components';

export default class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    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();
    }
  }
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
alpfilho profile image
Alfredo Peres

thank you

Collapse
 
romulloleal profile image
Romullo Leal

I have been looking for this. Thank you my friend.