動くコード図鑑技術記事現場の渡り方キャリア論すべての記事About
技術記事

ASP.NET MVC で本番だけ CSS/JS が消える — bundling/minification の落とし穴4箇所

バイブス父さん
現役の業務SE
2026年6月24日12 min read
ASP.NET MVC で本番だけ CSS/JS が消える — bundling/minification の落とし穴4箇所

みなさんこんにちは!ヒロポンです!!

開発機ではちゃんと当たってた 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箇所の対処目安サマリ

先に時間の目安だけ。朝のデプロイ後に崩れてても、ここを見れば落ち着けます。

  1. BundleConfig の登録名ズレ(⏱確認5分)— @Styles.Render の引数と登録名の照合
  2. web.config の compilation debug(⏱確認3分)— 本番が false になってるか、なってないか両方ハマる
  3. minify でファイルが壊れる(⏱切り分け20分)— セミコロン抜け・CSS の相対パス
  4. CDN/ブラウザのキャッシュ残り(⏱確認10分)— 旧バンドルが配信され続ける

下の早見表で全体像をつかんでから、各論にいきます。

ASP.NET bundling の4つの落とし穴を、症状・開発で気づけない理由・対処で並べた比較表

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 が消えたら、こんな感じで上から順にこれだけ見れば切り分かります。

  1. 登録名ズレ@Styles.Render の引数 = StyleBundle の登録名(大文字小文字も)
  2. debug 設定 → 本番 debug="false" / 再現は EnableOptimizations = true
  3. minify 破壊 → 一部だけ崩れるなら CssRewriteUrlTransform と JS 構文
  4. キャッシュ残り → 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)でのバンドル挙動の確認を推奨します。


この記事のコードと手順は ぜんぶ動作検証済み。 安心して現場で試してくれ。
バイブス父さん

現役の業務SE。C# / SQL Server 保守の現場から、コードも人もキャリアも全部書く。 実体験ベース。

運営者について