みなさんこんにちは!ヒロポンです!!
開発機ではちゃんと当たってた CSS が、本番にデプロイした瞬間レイアウトごと崩れる。
これ、業務系の ASP.NET MVC で一回はやるやつですよね。
しかも厄介なのが、開発機では一切再現しないこと。ローカルでは完璧。本番だけ死ぬ。
「ローカルで動いたのに??」ってなって、デプロイ手順を疑い出す。あの遠回り、覚えがある人いるんじゃないですか。
💡 CSS が効かない時の基本(パス間違い・キャッシュ)は別記事 ASP.NET で CSS が効かない時に最初に見る場所 でまとめてます。この記事は、その一段深い bundling/minification 固有のハマり に絞った話です。
犯人の多くは、本番でだけ働く bundling と minification。
開発機(debug=true)は素の CSS/JS を個別に配信する。それが本番(debug=false)になると BundleConfig 経由のバンドル配信に切り替わる。この切り替わりで踏むワナを、切り分けやすい順に4箇所まとめます。実務で実際に見かけたやつベースです。
忙しい人向けに最初にまとめ
- 開発では効くのに本番で CSS が効かない。その犯人は、たいてい 本番でだけ有効になる bundling/minification
- 切り分けは4箇所を順番に。①BundleConfig の登録名ズレ → ②web.config の debug 設定 → ③minify でファイルが壊れる → ④CDN/ブラウザのキャッシュ残り
- 開発機で本番と同じ挙動を再現したいなら
BundleTable.EnableOptimizations = trueを一時的に入れる。これで本番でだけ起きるやつを手元で踏める - 全部「コピペで直せる設定」レベル。本番でレイアウト崩れて業務側に連絡する前に、上から順に潰せます
⏱ 4箇所の対処目安サマリ
先に時間の目安だけ。朝のデプロイ後に崩れてても、ここを見れば落ち着けます。
- BundleConfig の登録名ズレ(⏱確認5分)—
@Styles.Renderの引数と登録名の照合 - web.config の compilation debug(⏱確認3分)— 本番が
falseになってるか、なってないか両方ハマる - minify でファイルが壊れる(⏱切り分け20分)— セミコロン抜け・CSS の相対パス
- CDN/ブラウザのキャッシュ残り(⏱確認10分)— 旧バンドルが配信され続ける
下の早見表で全体像をつかんでから、各論にいきます。

1. BundleConfig の登録名と @Styles.Render の引数がズレてる
最初に疑うのはここ。俺もこれで30分溶かしたことがあります。
開発機(debug=true)は CSS/JS を個別ファイルとしてそのまま配信する。だから BundleConfig の登録名が多少ズレてても、ブラウザは素の site.css を読みにいって普通に効く。
ところが本番(debug=false)は @Styles.Render("~/Content/css") の引数を「バンドルの仮想パス」として解決しにいく。
ここが BundleConfig の登録名と1文字でも違うと、バンドルが見つからず CSS が丸ごと 404 になります。
// App_Start/BundleConfig.cs
public static void RegisterBundles(BundleCollection bundles)
{
// ★この "~/Content/css" が @Styles.Render の引数と完全一致している必要がある
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/site.css",
"~/Content/layout.css"));
bundles.Add(new ScriptBundle("~/bundles/app").Include(
"~/Scripts/app.js",
"~/Scripts/util.js"));
}
@* Views/Shared/_Layout.cshtml — 引数は登録名と1文字も違えてはいけない *@
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/app")
教訓: 本番で CSS が 404 なら、まず @Styles.Render の引数と StyleBundle の登録名を声に出して照合する。~/Content/css と ~/content/css(大文字小文字)でも本番は死にます。地味だけど、これが罠の正体。
2. web.config の compilation debug が想定とズレてる
次に web.config。これが地味に「効いたり効かなかったり」の元凶になります。
ASP.NET は compilation debug の値で配信モードを切り替える。debug="true" なら個別配信、debug="false" ならバンドル+minify。
本番が debug=true のまま出てると、そもそもバンドルされない。「本番なのに開発挙動」になって、別のところで矛盾が出るやつです。
<!-- Web.config — 本番は debug="false"。これでバンドル+minify が有効になる -->
<system.web>
<compilation debug="false" targetFramework="4.7.2" />
</system.web>
逆に「本番でだけ起きるバンドル崩れ」を開発機で再現したい時は、debug を触らずにこれを入れます。
// BundleConfig.cs の末尾 — debug の値を無視してバンドル+minify を強制
BundleTable.EnableOptimizations = true;
これを入れると、ローカルでも本番と同じバンドル配信になる。
いい感じに本番のハマりを手元で先に踏めるので、デプロイ前の検証にめっちゃ効きます。
ん?じゃあ常時 true にしときゃよくない?って思いますよね。でも、それはそれで開発がやりづらくなる。使い分けがいいです(理由は最後の Q&A で)。
教訓: 本番だけの問題は、EnableOptimizations = true で開発機を本番モードに寄せて再現する。再現できれば半分勝ちです!!
3. minification で CSS/JS が壊れる
ここがいちばん厄介。素のファイルは壊れてないのに、minify した瞬間だけ壊れるパターンです。
よくあるのが2つ。
ひとつは CSS の相対パス。url(images/bg.png) みたいな相対指定は、バンドルすると「バンドルの仮想パス基準」で解決される。元の CSS の場所からズレて画像が消えるやつです。背景画像やアイコンだけ消える時はこれを疑う。
直し方はこんな感じで、CssRewriteUrlTransform を噛ませるだけ。
// 相対パスをバンドル基準に書き換えてくれる変換を追加する
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/site.css", new CssRewriteUrlTransform()));
もうひとつは JS のセミコロン抜け。改行で文末を区切ってた JS を minify で1行にまとめると、自動セミコロン挿入(ASI)の解釈が変わって構文が壊れることがある。本番だけ JS が動かない時はこれ。
教訓: minify 破壊は「一部だけ崩れる」のが特徴。全部消えるなら ①②、一部だけなら ③、と切り分けると速い。俺も背景画像だけ消えて、半日 url() を疑えずに遠回りしました。マジで未熟だった。
4. CDN/ブラウザのキャッシュに旧バンドルが残る
最後。直したはずなのに本番が古いまま、ってやつ。
「なんで反映されんの??」ってなる、あれです。
ASP.NET のバンドルURLには ?v= のハッシュが自動で付いて、中身が変わればハッシュも変わる仕組みになってる。だから普通はキャッシュが勝手に切り替わる。
でも CDN や中間プロキシがクエリ文字列を無視してキャッシュしてると、?v= が変わっても旧バンドルを返し続ける。これで「直したのに反映されない」が起きます。
配信される実URLは /Content/css?v=Xy3kP... のように ?v= ハッシュが付くので、これがデプロイ後に変わってるかが切り分けの起点になります。
教訓: まず F12 の Network タブでバンドルの ?v= ハッシュがデプロイ後に変わってるか見る。変わってるのに古いなら CDN のキャッシュキー設定(クエリ文字列を含めるか)を確認する。ここだけは設定がインフラ側にあることも多いので、一人で抱えずインフラ担当に振っていい。
まとめ・チートシート
本番で CSS/JS が消えたら、こんな感じで上から順にこれだけ見れば切り分かります。
- 登録名ズレ →
@Styles.Renderの引数 =StyleBundleの登録名(大文字小文字も) - debug 設定 → 本番
debug="false"/ 再現はEnableOptimizations = true - minify 破壊 → 一部だけ崩れるなら
CssRewriteUrlTransformと JS 構文 - キャッシュ残り → F12 で
?v=ハッシュ、ダメなら CDN キャッシュキー
全部が「本番だけ起きる」のは、bundling と minify が本番でだけ働くから。逆に言えば、開発機で EnableOptimizations = true を一回入れておけば、この4つはデプロイ前にほぼ潰せます。
本番でレイアウトが崩れて、業務側から「画面おかしいんだけど」と連絡が来る、あの一番きつい瞬間。あれを避けられるだけで、だいぶ気が楽になりますよね。
よくある質問
ASP.NET Core でも同じハマりは起きますか?
仕組みは変わってます。ASP.NET Core は BundleConfig ではなく、ビルド時バンドラ(webpack や bundleconfig.json + 拡張)や TagHelper の asp-append-version でやる。この記事は .NET Framework の MVC5(System.Web.Optimization)前提です。Core 移行済みなら別の切り分けになります。
なぜ最初から EnableOptimizations = true を常時入れておかないんですか?
開発のたびに minify されると、ブラウザのデバッガで元の CSS/JS が読めなくなって開発効率が落ちるからです。普段は素の個別配信で開発して、デプロイ前の検証時だけ true にして本番挙動を確認する、の使い分けがちょうどいい。
ここまで、本番だけで起きる配信のワナを切り分け順にまとめました。
こういう「開発と本番の差分」を一人で切り分けられる業務SE って、現場だと地味に重宝されるんですよね。その積み重ねが市場価値にもつながる話を、下の記事でしてます。
次に読むべき記事



以上!
執筆者
バイブス父さん — 業務 SE 7 年 (SIer 正社員 2 / フリーランス 5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SIer の正社員からフリーランスに転じ、複数のエージェント経由で案件を回してきた経験ベースで「業務 SE 視点」の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る
動作確認メモ: ここで載せた
BundleConfig/web.config/@Styles.Renderは ASP.NET MVC5(.NET Framework 4.7.2)+ IIS 前提の設定です。System.Web.Optimizationは Windows + IIS + Visual Studio 環境で動くため、Linux コンテナでの自動動作確認は行っていません。実環境(Windows + VS + IIS)でのバンドル挙動の確認を推奨します。


