CSS modules in next.js
Fredrik BergqvistOn my previous iteration of bergqvist.it, I used styled jsx for styling my components. I preferred that to other css-in-js frameworks (like JSS) because of it actually using CSS syntax instead of JavaScript objects.
// styled jsx example with good old CSS
<style jsx>{`
.label { color: red; font-style: italic; }
.article { padding: 0; }
`}
</style>
//JSS example with CSS as a JS object
const useStyles = createUseStyles({
label: {
color: 'red'
fontStyle: 'italic'
},
article: {
padding: 0
}
})
I like Styled jsx, but it has had an issue with FOUC in the last few versions of Next.js though, and with Next 12 I decided to try something new and migrate to CSS Modules instead.
What are CSS Modules?
A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.
So the benefit are the same as css-in-js-frameworks, but pure CSS (or in my case, scss) files are used instead of keeping the styling in the JavaScript files.
Read more about CSS Modules here
Why CSS Modules?
I may be old-school, but I actually prefer to keep the CSS and JavaScript separated from each other. I can see the benefits of keeping them tightly coupled and can agree that simple, self-contained components probably benefit from this approach, but it gets messy when the component has many styles with media queries.
I also want to use SASS, which is fully supported with CSS Modules in Next.js.
Migrating from styled jsx to CSS Modules
Since Styled jsx use regular CSS it’s actually just a matter of creating the <component>.module.scss
-file, importing it into the component and changing the classes
//styled jsx
export default function MyComponent(){
return (<div className={"article"}>
<span className={"label"}>...</span>
<style jsx>{`
.label { color: red; font-style: italic; }
.article { padding: 0; }
`}</style>
</div>)
}
//CSS Modules
import styles from "./MyComponent.module.scss";
export default function MyComponent(){
return (<div className={styles.article}>
<span className={styles.label}>...</span>
</div>)
}
Using multiple modules in one component
For reusability, you might want to use a CSS module in more than one component
import styles from "./MyComponent.module.scss";
import * as secondaryStyles from "./secondary.module.scss";
export default function MyComponent(){
return (<div className={styles.article}>
<span className={secondaryStyles.label}>...</span>
</div>)
}
If you are using TypeScript this approach probably cause an error: TS2339: Property 'label' does not exist on type 'typeof import("*.module.scss")'.
The error can be mitigated by adding a typings.d.ts
-file to the root of your project with the following content
// typings.d.ts
declare module "*.module.scss" {
interface IClassNames {
[className: string]: string;
}
const classNames: IClassNames;
export = classNames;
}
Composition
Instead of importing several different modules, it’s possible to compose new classes from existing classes.
// secondary.module.scss
.label {
color: red;
font-style: italic;
}
// MyComponent.module.scss
.article {
padding: 0;
}
.label {
composes: label from "./secondary.module.scss";
}
// MyComponent.tsx
import styles from "./MyComponent.module.scss";
export default function MyComponent(){
return (<div className={styles.article}>
<span className={styles.label}>...</span>
</div>)
}
Global styles
As I already had a global css-file that I imported into my _app.tsx
, I really did not have to do anything to get my global classes working.
If you want to add a global class in a component file you can add it by using :global()
on the class.
:global(.label) {
color: red;
font-style: italic;
}
Parting words
I’m quite happy with CSS Modules, the site no longer gets FOUC, and it looks great with JavaScript disabled as well!
Hope this could be of any help to someone looking into CSS Modules.