Tailwind パフォーマンス最適化:JIT、content設定、本番バンドルサイズ管理
午前3時。Chrome DevToolsで3.5MBのCSSファイルを見つめながら、完全に凍りついていました。
リリースした新機能ページ、読み込み時間が800msから3.2秒に増えていました。数時間の調査の後、原因を見つけました:そのCSSファイルには、使っていないTailwindクラスが山ほど詰め込まれていました。
正直、かなり落ち込んでいました。Tailwindは「パフォーマンスフレンドリー」と言われているのに、逆に足を引っ張っている?
結局、問題はTailwind自身ではなく設定でした。JITモードがどう動くのか理解して、content設定を正しく調整し、本番ビルドの最適化を適用すれば、CSSファイルはMBレベルからKBレベルに激減します。Netflixのサイトは6.5KBしか使っていません。
私が踏んだ落とし穴を一緒に見ていきましょう。
1. JITモード:Tailwindのパフォーマンス革命
1.1 従来モードの問題点
3.5MBのCSSファイルを見たその夜まで、Tailwindの理解は「utility-first CSSフレームワーク」という层面止まっていました。
その後、Tailwind v2以前の「従来モード」はすべての可能なクラス組み合わせを事前生成することを知りました。すべての色、すべての間隔、すべてのvariant(hover、focus、disabledなど)。中程度の複雑さのプロジェクトで、開発環境のCSSファイルは10MB以上になることがあります。
10MBのCSSファイルはローカル開発では害がないように見えます。带宽は問題ではないから。でもブラウザは依然としてその巨大なスタイルシートを解析する必要があり、メモリとDevToolsのパフォーマンスに影響します。
Firefoxでデバッグ時にこの問題に遭遇しました:1つのクラス名を変更すると、DevToolsが数秒間凍りつきます。ページをリフレッシュするのは痛苦ほど遅い。
さらに困るのは、従来モードでは多くの設定変更を躊躇しました。新しいブレイクポイントを追加したり、focus-visible variantを有効化したりすると、「これでクラス組み合わせが何個増えるか」を計算しなければなりません。チームはパフォーマンスと柔軟性の間で選択を迫られました。
1.2 JITは実際どう動くのか
JIT(Just-in-Time)モードはTailwind v2.1で導入され、v3+ではデフォルトで有効化されています。簡単に言えば:オンデマンド生成。
従来モードはビルド時にすべての可能なクラス組み合わせを事前生成します、使わないものも含めて。JITは逆です。まずテンプレートファイル(HTML、JSX、Vueなど)をスキャンして、実際に使っているクラスを見つけ、そのスタイルだけを生成します。
例を見てみましょう。従来モードはこのようなCSSを生成します:
.bg-black { background-color: #000 }
.hover\:bg-black:hover { background-color: #000 }
.focus\:bg-black:focus { background-color: #000 }
.disabled\:bg-black:disabled { background-color: #000 }
/* ... さらに数十個のvariant組み合わせ */
プロジェクトでbg-blackだけを使っていても、他の数十個のvariantスタイルも生成されます。
JITモードは違います。テンプレートをスキャンして、bg-blackとhover:bg-blackだけを使っていると見つけ、この2つだけを生成します:
.bg-black { background-color: #000 }
.hover\:bg-black:hover { background-color: #000 }
こうすると、開発環境のCSSファイルは10MBレベルからKBレベルに直ちに低下します。而且本番環境のサイズと同じです。
1.3 JITの実際のメリット
プロジェクトで初めてJITを有効化した時、ページのリフレッシュ速度が非現実的ほど速く感じました。以前は数秒待つ必要がありました、今はほぼ瞬時です。
ビルド速度:完全なビルドは以前2-3分待つ必要がありました、今は数秒で完了します。JITはすべてのスタイルを事前生成する必要がなく、テンプレートファイルをスキャンしてクラス名を抽出するだけです。
開発体験:DevToolsが凍りつかなくなりました。以前、クラスを変更すると、ブラウザがその10MBのスタイルシートを再解析する待つ必要がありました。今、スタイルシートは数KBだけ、変更はほぼ即座に有効になります。
任意値サポート:これは嬉しいサプライズでした。従来モードでtext-[#facc15]のような任意値を使うには、設定でsafelistを有効化する必要がありました。JITは直接サポートします:
// 設定で事前定義不要、直接使える
<h1 class="text-[2.5rem] mt-[1.35rem] text-[#facc15]">
JITですべてが簡単になる
</h1>
動的クラス名:従来モードで'text-' + colorのような連結クラス名は検出できませんでした。JITも制限がありますが、safelistと組み合わせれば動的シナリオをより柔軟に処理できます。
これほど話して、JITモードをどう有効化するのかと思うかもしれません。実は、Tailwind v3+ではデフォルトでJITです、追加設定不要です。まだv2を使っている場合、このように有効化します:
// tailwind.config.js
module.exports = {
mode: 'jit', // v2は手動有効化必要
content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
// ...
}
2. content設定:精密スキャンへの鍵
JITモードは便利ですが、content設定が正しいことが前提です。
contentはTailwindがどのファイルをスキャンしてクラス名を抽出するかを決定します。設定ミスは、スタイル消失(スキャン範囲が狭すぎる)かCSS肥大化(範囲が広すぎる)を引き起こします。
2.1 content設定の基礎
基本的な構文は簡単です:
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{html,js,jsx,ts,tsx}', // srcディレクトリ内のすべてのテンプレートファイルをスキャン
],
// ...
}
ここで**は任意の階層のディレクトリを意味し、*.{html,js,jsx,ts,tsx}はこれらの拡張子を持つファイルをマッチします。
特定のフレームワークを使っている場合、パスを調整する必要があります:
// Next.jsプロジェクト
content: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./app/**/*.{js,ts,jsx,tsx}', // App Router
]
// Astroプロジェクト
content: [
'./src/**/*.{astro,html,js,jsx,ts,tsx}',
]
鍵は:Tailwindクラス名を使っているすべてのファイルをカバーすること。1つのディレクトリを見逃すと、そのディレクトリのスタイルは生成されません。
2.2 私が踏んだ落とし穴
落とし穴1:過度に広いglobパターン
「簡単な」設定を求めて、このような書き方を使いました:
content: [
'./**/*.js', // これはnode_modulesをスキャン!
]
結果:Tailwindがnode_modules内のすべてのJSファイルをスキャンし、ビルド時間が激増し、奇妙なスタイルが山ほど生成されました。
正しい方法は範囲を限定することです:
content: [
'./src/**/*.js', // srcディレクトリだけスキャン
'./components/**/*.js', // componentsディレクトリだけスキャン
]
落とし穴2:コンポーネントディレクトリの見逃し
あるリファクタリング時、コンポーネントを./lib/components/に移動しました。content設定を更新するのを忘れて、新しい場所のコンポーネントスタイルが完全に消失しました。
数時間調査して問題を見つけました。教訓:プロジェクト構造を変更する時、必ずcontent設定を同期すること。
落とし穴3:動的に構築されるクラス名
このような書き方です:
const color = 'red';
const className = `text-${color}-500`; // JITは検出できない
JITはスキャン時にテンプレート文字列を見ます、完全なクラス名ではない。結果:本番ビルド時にこのスタイルが削除されます。
解決策:safelistを使う(後で説明)かオブジェクト構文に変更:
const colors = {
red: 'text-red-500',
blue: 'text-blue-500',
};
const className = colors[color]; // 完全なクラス名、検出可能
2.3 safelistと動的クラス名
一部のシナリオでは本当に動的クラス名が必要です。そこでsafelistが役立ちます。
// tailwind.config.js
module.exports = {
safelist: [
'text-red-500',
'text-blue-500',
'bg-red-500',
// または正規表現でクラス名グループをマッチ
{
pattern: /text-(red|blue|green)-(500|600)/,
variants: ['hover', 'focus'], // 同時にvariantを保持
},
],
}
safelistはTailwindに強制的にこれらのスタイルを生成させます、テンプレートファイルに直接出現しない場合も。
でも注意:safelistが多い = CSSファイルが大きい。必要なシナリオだけで使用、すべての可能なクラス名を詰め込む「安全箱」としては使わない。
3. 本番バンドルサイズ管理:4層最適化戦略
JITは小さな開発CSSを可能にしますが、本番ビルドはさらに最適化が必要です。
私は4層最適化戦略を整理しました、設定から圧縮まで、各層が前の層に基づきます。
3.1 第1層:精密content設定
これは基礎です、前に説明しました。
核心原則:Tailwindクラスを実際に使っているファイルだけスキャン。より精密な範囲 = 速いビルド = 小さいCSS。
// 良い:精密範囲
content: [
'./src/components/**/*.jsx',
'./src/pages/**/*.tsx',
]
// 悪い:範囲が広すぎる
content: [
'./**/*.js', // node_modulesをスキャン
]
3.2 第2層:PurgeCSS自動削除
Tailwind v3+は本番ビルド時に自動的にPurgeCSSを有効化し、未使用スタイルを削除します。
鍵はビルドコマンドが開発/本番環境を正しく区別することです:
# 開発ビルド(未使用スタイルを削除しない)
npm run dev
# 本番ビルド(自動PurgeCSS)
npm run build
PostCSSを使っている場合、設定で明示的に制御できます:
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
},
}
3.3 第3層:cssnano Minify
PurgeCSSが未使用スタイルを削除した後、cssnanoはさらにCSSを圧縮します。
圧縮には:コメント削除、重複ルールの統合、セレクタの簡略化、値の圧縮などが含まれます。
# Tailwind CLIで直接minify
npx tailwindcss -i ./src/input.css -o ./dist/output.css --minify
# またはPostCSS経由(上に示した)
私のプロジェクトの実データ:150KB(PurgeCSS後)から45KB(minify後)に低下しました。
3.4 第4層:Brotli/Gzipネット圧縮
この層はCSS自体の最適化ではなく、ネット転送层面です。
サーバーでBrotliまたはGzipを使って静的資源を圧縮すると、CSSファイルをさらに60-80%縮小できます。
# Nginx Brotli設定(ngx_brotliモジュール必要)
brotli on;
brotli_comp_level 6;
brotli_types text/css application/javascript;
# またはGzip(Nginxデフォルトサポート)
gzip on;
gzip_comp_level 6;
gzip_types text/css application/javascript;
比較データ:45KBのCSSファイルはBrotli圧縮後、転送サイズは約8KBになります。
NetflixがCSSは6.5KBと言っているのは、Brotli圧縮後の転送サイズです、元のファイルサイズではない。
4. 実践事例とデータ比較
4.1 私の最適化前後比較
正直、このデータ比較を見た時、私自身も驚きました。
4.2 一般的な問題トラブルシューティング
スタイルが消失した?
第一:content設定を確認、Tailwindクラスを使っているすべてのファイルがスキャン範囲内にあることを確認。
第二:動的クラス名がある場合、safelistが必要か確認。
第三:ビルドコマンドを確認、本番ビルド(npm run build)を使っていることを確認、開発ビルドではない。
CSSがまだ大きい?
safelistが過度か確認。第三方ライブラリのCSSがTailwindビルド出力に混入しているか確認。content範囲が広すぎるか確認。
DevToolsが凍りつく?
Tailwindバージョン≥3を確認(JITデフォルト有効)。content設定が精密か確認。まだv2を使っている場合、アップグレードまたは手動でJITモードを有効化。
5. Tailwind v4新機能プレビュー
現在の最適化をカバーした後、Tailwind v4(2024年末リリース)の変化を見てみましょう。
5.1 Oxideエンジン
これは最もエキサイティングな更新です:Tailwindは基盤エンジンをRustで書き直しました。
公式データ:インクリメンタルビルド速度が182倍向上しました。基本的に、以前クラスを変更すると再コンパイルを数秒待つ必要がありました、今はミリ秒応答です。
原理:Oxideエンジンはファイルスキャン結果をキャッシュし、変更部分だけを再処理します。これは大型プロジェクトに特に有用です。200以上のコンポーネントファイルのあるプロジェクトがあります、以前各ビルドは約10秒待つ必要がありました、今は基本的に瞬時です。
5.2 ゼロ設定目標
Tailwind v4は新しい哲学を推しています:多くのプロジェクトはtailwind.config.jsを必要としない。
デフォルト設定は一般的な需求をカバーしています:現代CSS機能、コンテナクエリ、合理的なブレイクポイント、完全な色システム。深いカスタマイズが必要な時だけ設定ファイルを書きます。
これは新規プロジェクトの開始が速く、設定ミスの機会が減ります。
v4移行の注意:設定構文が変化しました。例えば、以前のtheme.extend.colorsは今CSSカスタムプロパティで書く必要があります。移行前に公式アップグレードガイドを一読すること。
まとめ
午前3時の挫折後、私はプロジェクトのTailwind設定を根本から整理しました。
JITモードは開発と本番で同じ小さいCSSを使い、content設定はスキャン範囲を決定し、4層最適化はCSSをMBからKBに圧縮します。さらにTailwind v4のOxideエンジンで、ビルド速度はボトルネックではなくなりました。
プロジェクトがまだ従来モードを使っている場合、またはcontent設定に疑問がある場合、まずこれらのポイントを確認:
- Tailwindバージョン≥3か(JITデフォルト有効)
- content設定がすべてのテンプレートファイルを精密にカバーしているか
- 本番ビルドでPurgeCSSとcssnanoが有効か
- サーバーでBrotli/Gzip圧縮が設定されているか
これらを変更すれば、明らかな性能向上が見られるはずです。
質問あればコメントください、Tailwind公式ドキュメントのJITと本番最適化章も見てください。かなり明確です。
Tailwind CSS パフォーマンス最適化設定
JITモード有効化から本番バンドルサイズ管理までの完全設定フロー
⏱️ 目安時間: 30 分
- 1
ステップ1: Tailwindバージョン確認
プロジェクトのTailwind CSSバージョンを確認:
• Tailwind v3+はJITモードがデフォルトで有効
• Tailwind v2は設定にmode: 'jit'を手動追加必要
• v3+へのアップグレード推奨で最良の性能 - 2
ステップ2: contentスキャンパス設定
tailwind.config.jsで精密なスキャンパスを設定:
• Next.js: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}']
• Astro: ['./src/**/*.{astro,html,js,jsx,ts,tsx}']
• './**/*.js'のような過度に広いglobパターンを避ける - 3
ステップ3: 動的クラス名処理
動的に構築されるクラス名に対して、safelistで強制生成:
• safelist配列に完全なクラス名を追加
• pattern正規表現でクラス名グループをマッチ
• variantsと組み合わせてhover、focus等を保持 - 4
ステップ4: 本番ビルド最適化設定
PostCSS設定にcssnanoを追加:
• 開発環境ではcssnanoを有効化しない(ビルド速い)
• 本番環境ではcssnano minifyを自動有効化
• process.env.NODE_ENVで有効条件を制御 - 5
ステップ5: サーバー圧縮設定
NginxでBrotliまたはGzip圧縮を設定:
• Brotli: brotli on; brotli_comp_level 6;
• Gzip: gzip on; gzip_comp_level 6;
• 両方でtext/css application/javascriptタイプを設定
FAQ
Tailwind v3でJITモードをどう有効化する?
content設定でファイルを見逃したらどうなる?
動的クラス名がJITで見逃されるのはなぜ?
Netflixの6.5KB CSSは元のサイズか圧縮後?
Tailwind v4 Oxideエンジンはどのような改善がある?
safelist設定が過度の場合の影響は?
参考資料
- Tailwind CSS Just-in-Time Mode Docs
- Optimizing for Production Docs
- Just-In-Time: The Next Generation of Tailwind CSS
- Tailwind CSS v4.0 Release
- Tailwind CSS Best Practices for Performance Optimization
7 min read · 公開日: 2026年3月30日 · 更新日: 2026年3月30日
関連記事
Nginx リバースプロキシ完全ガイド:upstream、バッファ、タイムアウト
Nginx リバースプロキシ完全ガイド:upstream、バッファ、タイムアウト
shadcn/uiとRadix:コンポーネントをカスタマイズする際のアクセシビリティ維持方法
shadcn/uiとRadix:コンポーネントをカスタマイズする際のアクセシビリティ維持方法
Dialog、Sheet、Popover:オーバーレイコンポーネントのアクセシビリティとフォーカス管理

コメント
GitHubアカウントでログインしてコメントできます