Next.jsプロジェクトを始めるとき、必ず直面するのがCSS設計の選択だ。
styled-componentsかCSS Modulesか、はたまたTailwind CSSか。この選択はプロジェクト全体のDXと保守性に影響するにもかかわらず、「なんとなく」で決めているケースが多い。実務での使い分けをまとめておく。
styled-components とは
コンポーネントと同じファイルにCSS-in-JSとして書くスタイル。
import styled from 'styled-components';
const Button = styled.button<{ $primary?: boolean }>`
padding: 0.5rem 1rem;
background: ${(props) => (props.$primary ? '#7c6af7' : 'transparent')};
border: 2px solid #7c6af7;
border-radius: 6px;
color: ${(props) => (props.$primary ? '#fff' : '#7c6af7')};
cursor: pointer;
&:hover {
opacity: 0.8;
}
`;
// 使い方
<Button $primary>送信</Button>
<Button>キャンセル</Button>
メリット:
- PropsでスタイルをJSで動的に切り替えられる
- コンポーネントとスタイルが1ファイルに収まり、削除漏れが起きにくい
- TypeScriptの型補完がPropsに効く
デメリット:
- ランタイムでCSSを生成するためSSRが重くなる(Next.js App Routerとの相性が悪い)
- バンドルサイズが増える
- styled-componentsを知らないエンジニアには学習コストがある
CSS Modules とは
.module.cssファイルを作り、クラス名を自動スコープ化するアプローチ。
// Button.module.css
.button {
padding: 0.5rem 1rem;
border: 2px solid #7c6af7;
border-radius: 6px;
cursor: pointer;
}
.primary {
background: #7c6af7;
color: #fff;
}
.outline {
background: transparent;
color: #7c6af7;
}
// Button.tsx
import styles from './Button.module.css';
type Props = {
variant?: 'primary' | 'outline';
children: React.ReactNode;
onClick?: () => void;
};
export function Button({ variant = 'outline', children, onClick }: Props) {
return (
<button
className={`${styles.button} ${variant === 'primary' ? styles.primary : styles.outline}`}
onClick={onClick}
>
{children}
</button>
);
}
メリット:
- ビルド時に静的なCSSを生成するので、SSR・SSGで高速
- Next.js App Routerと完全互換
- 通常のCSSなので学習コストが低い
- バンドルサイズへの影響が小さい
デメリット:
- 動的なスタイル切り替えはclassNameの組み合わせで対応するため冗長になることがある
- ファイルが増えてコンポーネントと分離する
比較表
| 観点 | styled-components | CSS Modules |
|---|---|---|
| 動的スタイル | ◎ Propsで自然に書ける | △ classNameで切り替え |
| パフォーマンス(SSR) | △ ランタイム生成 | ◎ ビルド時静的生成 |
| App Router対応 | △ Server Componentでは使えない | ◎ 完全対応 |
| 学習コスト | やや高い | 低い |
| バンドルサイズ | やや大きい | 小さい |
| TypeScript連携 | ◎ Propsに型が効く | △ classNameは文字列 |
| スコープ分離 | ◎ 自動 | ◎ 自動 |
実務での選択基準
styled-componentsを選ぶとき
- Pages Router前提のプロジェクト(レガシー継続)
- テーマ・カラーパレットのPropsによる動的切り替えが多い
- デザインシステムとして配布するコンポーネントライブラリを作る
CSS Modulesを選ぶとき
- App Router(新規プロジェクト) → 迷わずCSS Modulesかゼロランタイムのpandaなど
- チームにCSSの知識はあるがReact初心者がいる
- パフォーマンスを優先したい
2026年時点の現実
正直なところ、新規プロジェクトでstyled-componentsを選ぶ理由は薄くなっている。
Next.js App RouterはServer Componentsを前提としており、ランタイムでCSSを生成するstyled-componentsはServer Component内では動作しない。'use client'を付けた Client Componentでしか使えず、App Routerのメリットが活かしにくくなる。
Tailwind CSS、CSS Modules、vanilla-extractなどのゼロランタイムアプローチが主流になってきている。
チームへの推薦
私のチームではCSS ModulesをベースにしてグローバルなCSS変数をglobal.cssに定義する構成を採用している。動的なスタイル切り替えはclassNameとCSSカスタムプロパティで対応し、styled-componentsには依存していない。
このアプローチはシンプルで、エンジニアの入れ替わりがあっても引き継ぎやすい。
まとめ
- 既存プロジェクト(Pages Router)でstyled-componentsが使われている → そのまま継続でOK
- 新規プロジェクトでApp Router → CSS Modules、またはTailwind CSS
- 動的テーマ切り替えが重要 → CSS変数 + CSS Modulesで対応できることが多い
CSS設計の選択は「流行」より「チームの技術水準」と「Next.jsのバージョン・構成」で決めるべきだ。
このトピックはTypeScriptとReact/Next.js実践本の4章で詳しく扱われている。入門として読んだ後に実務で判断する基準を自分なりに更新したのがこの記事だ。