How to set up Styled Components in NextJS
Introduction
Styled Components is a library that allows you to write CSS in your JavaScript/TypeScript code with many features like theming, nesting, and media queries. It's a great way to write CSS in React.
There are other options as well, but this is one of the best options out there because it's easy to use and has many features. It's also trendy because it's closely related to raw CSS and easy to understand.
If you are only interested in the project, you can find it on Github
Create a boilerplate project
You can skip this step if you already have a NextJS application up and running. Otherwise, let’s create a boilerplate NextJS application first.
npx create-next-app@latest
You can run the dev server by running the following command.
npm run dev
Now Visit http://localhost:3000 to see your application up and running.
Installation
First, install the dependencies:
npm install --save styled-components
npm install --save-dev @types/styled-components
If you are using yarn, it's highly recommended the following resolution to avoid breaking changes.
"resolutions": {
"styled-components": "^5"
}
It will save you from a lot of headaches.
Setup
Next, create a _app.tsx
file in the pages
directory. This special file in NextJS allows you to customize the app. You can read more about it in the documentation.
import { AppProps } from "next/app";
import { ThemeProvider } from "styled-components";
import React, { useState, useEffect } from "react";
const theme = {
colors: {
primary: "#0070f3",
},
};
export default function MyApp({ Component, pageProps }: AppProps) {
const [showChild, setShowChild] = useState(false);
useEffect(() => {
setShowChild(true);
}, []);
if (!showChild) {
return null;
}
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
);
}
Notice that we are using the ThemeProvider
component from styled-components
, and we are passing the theme
object as a prop. This is how we can use the theme in our components.
You will also notice that we use the useState
and useEffect
hooks. We want to show the child component only after the theme is loaded.
Doing this is not mandatory, but it's good practice.
Usage
Now, you can use the theme in your components. For example, you can create a Button
component:
import styled from "styled-components";
const StyledButton = styled.button`
color: ${(props) => props.theme.colors.primary};
`;
export default StyledButton;
Using the theme
prop, we can access the theme object and use its colors.
But what if we want to use the theme in a non-styled component? For example, we want to use the theme in a div
element. We can use the useTheme
hook:
import { useTheme } from "styled-components";
const MyComponent = () => {
const theme = useTheme();
return <div style={{ color: theme.colors.primary }}>Hello World</div>;
};
Now, your components will generate the CSS class names automatically. You can read more about it in the documentation
and render the button on the root page.
import StyledButton from "../components/button";
import styles from "../styles/Home.module.css";
export default function Home() {
return (
<div className={styles.container}>
<StyledButton> This is a button</StyledButton>
</div>
);
}
The problem
You will notice that the tag names are not being generated. For example, if you inspect the element, you will see that the tag name is div
instead of sc-div
.
This is because NextJS uses server-side rendering, and the styles are generated on the server. This is not a problem in development, but it's a problem in production.
Also, the class
name is not meaningful. Let’s fix these two problems.
Solution
We need to use the styled-components
babel plugin to fix this. First, install the plugin:
npm install --save-dev babel-plugin-styled-components
Then, add the plugin to the .babelrc
file:
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true, "displayName": true }]]
}
Notice that we have added the following configurations
ssr: true -> This means we are enabling the server-side rendering
displayName: true -> It will set some meaningful class names
Alternatively, you can add the following to your next.config.js
file:
const withPlugins = require("next-compose-plugins");
const withTM = require("next-transpile-modules")(["styled-components"]);
module.exports = withPlugins([withTM], {
webpack(config, options) {
return config;
},
});
Now restart the server, and you will see that the button className will be button__StyledButton-sc-egbi27-0 exllgz
This makes it easier to understand.
Handle server-side styles
The next step is to handle the server-side styles. We need to add the ServerStyleSheet
from styled-components
to the _document.tsx
file:
import Document, { Html, Head, Main, NextScript } 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();
}
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
Here we are using the ServerStyleSheet
to collect the styles from the server, and then we are adding them to the styles
prop.
Conclusion
This was a straightforward example, but you can use the styled-components
library to create complex components with many features.
To learn more about the library, you can read the documentation.
GitHub Repo:
https://github.com/Mohammad-Faisal/styled-components-nextjs-integration-demo