.NET 8(ASP.NET Core)のRazor(cshtml)で <img src="..."> を書いたのに画像が出ない場合、コードやパスが間違っているとは限りません。多くのケースで原因は「配置場所」と「静的ファイル配信の仕組み」にあります。この記事では、なぜIDEで参照できるのにブラウザでは表示されないのかを整理し、最短で解決するための手順と、公開環境での落とし穴までまとめます。
IDEでパスが正しそうなのに、ブラウザでは画像が出ない理由
Razor(cshtml)に <img src="images/logo.png"> のように書き、プロジェクト内にも images フォルダが存在する。IDEでホバーするとファイルが見つかるので「パスは合っている」と感じます。
しかし、ブラウザが画像を表示できるかどうかは「プロジェクトにファイルが存在するか」ではなく、そのファイルがHTTP(URL)として配信されているかで決まります。つまり、ローカルにあるだけではダメで、https://localhost:xxxx/images/logo.png にアクセスしたときにサーバーが画像を返す状態になっている必要があります。
まずは“表示されない”を分解する
「画像が表示されない」は、内部的には次のどれかで起きています。
| 現象 | ブラウザ側の実態 | よくある原因 |
|---|---|---|
| アイコンが割れている(何も出ない) | 画像URLのリクエストが404/403などで失敗 | 静的ファイルとして公開されていない/パスが相対でズレている |
| 表示はされるが更新されない | キャッシュが効いて古い画像が出ている | ブラウザキャッシュ/CDNキャッシュ/ファイル名固定 |
| 表示が崩れる・一部だけ出ない | 一部リクエストのみ失敗、またはMIMEが想定外 | 大文字小文字の不一致(Linux)/拡張子のMIME未対応 |
結論:ASP.NET Coreは既定で “wwwroot配下” しか静的ファイルを配信しない
原因の本命はここです。ASP.NET Coreはセキュリティ上の理由から、サーバー上の任意のフォルダを勝手に公開しません。既定で静的ファイル(画像・CSS・JSなど)として配信されるのは wwwroot 配下だけです。
そのため、プロジェクト直下に images フォルダを作って画像を置いても、何も設定しなければブラウザからは取得できません。IDEが参照できるのは「ファイルが存在する」ことを示しているだけで、「Webとして公開されている」ことを保証しないのがポイントです。
“コンテンツルート”と“Webルート”を区別する
混乱しやすいので用語を整理しておきます。
| 用語 | だいたいの場所 | 役割 | ブラウザからアクセスできる? |
|---|---|---|---|
| Content Root(コンテンツルート) | プロジェクト直下(.csprojがある場所) | アプリの基点。設定ファイルやcshtml等の読み込みに使われる | 既定ではできない |
| Web Root(ウェブルート) | wwwroot | 静的ファイル配信の基点 | できる(UseStaticFiles有効時) |
最短の切り分け:画像URLを直接開いてステータスコードを見る
闇雲に移動や設定をする前に、まず「何が起きているか」を明確にすると解決が一気に早くなります。
- ブラウザでページを開き、開発者ツール(F12)を起動します。
- 「Network(ネットワーク)」タブで画像のリクエストを探します。
- ステータスコード(200/404/403など)を確認します。
- 該当の画像URLをコピーして、新しいタブで直接開きます。
多くの場合、404(Not Found) になっているはずです。これは「画像ファイルが存在しない」ではなく、そのURLに対してアプリが静的ファイルとして返す設定になっていないことが原因です。
推奨の解決策:imagesフォルダをwwwroot配下に移動する
結論として、画像を表示したいなら、まずは構成をASP.NET Coreの王道に寄せるのが最も安全で、後々の運用も楽になります。
推奨フォルダ構成
プロジェクト直下の images を wwwroot/images に移動します。
(プロジェクト)
├─ Pages
│ └─ Index.cshtml
├─ Program.cs
└─ wwwroot
├─ images
│ └─ logo.png
├─ css
└─ js
cshtml側の書き方(まずは絶対パスで確認)
相対パスはURL階層でズレやすいので、まずは先頭に / を付けた書き方で確実に確認します。
<img src="/images/logo.png" alt="ロゴ">
Razorでは ~/ を使う書き方も定番です。ホスティングのパス(アプリがサブディレクトリ配下で動く等)を意識するなら、こちらの方が安全です。
<img src="~/images/logo.png" alt="ロゴ">
キャッシュ対策もセットで入れる(更新が反映されない問題を防ぐ)
ロゴやアイコンは差し替えが起きやすい一方、ブラウザが強めにキャッシュします。RazorのTag Helperが有効なら、次のようにバージョン付きURLにしておくと運用が楽です。
<img src="~/images/logo.png" asp-append-version="true" alt="ロゴ">
asp-append-version はファイル内容に応じてクエリが変わるため、画像を差し替えてもクライアントが新しい画像を取りに行きやすくなります。
「UseStaticFilesが無い」ケースも必ず確認
テンプレートや自作の最小構成によっては、wwwroot に置いても配信されないことがあります。原因は app.UseStaticFiles(); を呼んでいないことです。
後述のサンプルのように、Program.cs に静的ファイルミドルウェアが入っているか確認してください。
別解:プロジェクト直下のimagesを“静的ファイルとして公開”する
どうしても wwwroot 配下に移したくない(既存構成の都合、生成物を置く場所が決まっている、外部ボリュームをマウントしたい等)場合は、images フォルダを静的ファイルとして配信する設定を追加できます。
ただし、サーバー上のフォルダを公開する行為なので、公開して良いものだけを置く・パスの指定を誤らないなど、運用面も含めて慎重に扱うのがコツです。
Program.csでimagesフォルダを /images として公開する例
images がプロジェクト直下にある前提です。
using Microsoft.Extensions.FileProviders;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
// まずは通常の wwwroot を配信(wwwroot がある場合)
app.UseStaticFiles();
// 次に、プロジェクト直下の images を /images として配信
var imagesPath = Path.Combine(app.Environment.ContentRootPath, "images");
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(imagesPath),
RequestPath = "/images"
});
app.MapRazorPages();
app.Run();
この設定を入れると、プロジェクト直下の images/logo.png が /images/logo.png で取得できるようになります。cshtml側は以下のように書けます。
<img src="/images/logo.png" alt="ロゴ">
重要:UseStaticFilesはルーティングより前に置く
静的ファイルは「ルーティングでページに到達する」より前に処理されるのが基本です。順序が逆だと、画像のリクエストがRazor PagesやMVC側に流れてしまい、404になったり、意図しない挙動になります。
| 配置 | 結果 | おすすめ |
|---|---|---|
app.UseStaticFiles() → app.MapRazorPages() | 静的ファイルが先に処理される | 推奨 |
app.MapRazorPages() → app.UseStaticFiles() | 静的ファイルがうまく配信されないことがある | 非推奨 |
公開環境で追加でハマりやすいポイント
ローカルでは動くのに、Publish後やサーバーに上げた途端に画像が出ないことがあります。ここでは現場でよく当たる落とし穴をまとめます。
Publishしたらimagesフォルダが存在しない(wwwroot外に置いた場合)
wwwroot 配下は通常、公開時に含まれやすい一方で、プロジェクト直下の任意フォルダは、Publish設定次第で出力先にコピーされないことがあります。
「開発環境では存在するけど、サーバーではフォルダごと無い」状態だと、当然404になります。
対策の考え方は次の通りです。
- 可能なら 公開したい静的ファイルは wwwroot に寄せる
- どうしても wwwroot 外なら、Publish時にコピーする設定を入れる
例として、images をPublishに含めたい場合、.csproj に次のような指定を入れることがあります(プロジェクト方針に合わせて調整してください)。
<ItemGroup>
<Content Include="images\**\*" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
アプリがサブパス配下で動いている(/myapp/ など)
IISの仮想ディレクトリ配下や、リバースプロキシで /myapp のようなサブパスにぶら下がっている場合、src="/images/logo.png" はドメイン直下を指します。意図通りでない環境では画像が404になりがちです。
この手の環境差に強くするには、Razorの ~/ を使う、または Url.Content() を使って、アプリのルートを意識したURL生成をすると安定します。
<img src="@(Url.Content("~/images/logo.png"))" alt="ロゴ">
Linux(Docker/Azure App Service Linux等)で大文字小文字が一致していない
Windows開発だと気づきにくいのがファイル名の大文字小文字です。Windowsは基本的にファイル名の大文字小文字を区別しませんが、Linuxは区別します。
たとえばファイルが logo.png なのに、HTMLで Logo.png と書くと、Windowsでは動くのにLinuxで404になります。
| ファイル名(実体) | src指定 | Windows | Linux |
|---|---|---|---|
| logo.png | /images/logo.png | 表示される | 表示される |
| logo.png | /images/Logo.png | 表示されることが多い | 404になりやすい |
画像形式が新しすぎてMIMEが想定外(AVIF/WebP等)
最近は .webp や .avif を使う場面も増えています。拡張子によってはサーバー側のMIME(Content-Type)設定が不足していて、ブラウザが正しく扱えないケースがあります。
この場合、「404ではないのに表示されない」「ダウンロードになってしまう」など、症状が変わります。Networkタブでレスポンスヘッダーの Content-Type を確認し、必要に応じて FileExtensionContentTypeProvider を追加設定します。
ただし、今回の主題である「プロジェクト直下のimagesに置いたら出ない」とは別系統の問題なので、まずは404かどうかで分岐して考えるのが近道です。
ブラウザキャッシュで“直したのに直っていない”ように見える
画像はキャッシュされやすく、差し替え直後に「変わらない」と感じることがあります。まずは次を試してください。
- スーパーリロード(Ctrl+F5 / Shift+Reload)
- 画像URLを新しいタブで開き、更新して差し替わるか確認
asp-append-version="true"を使ってURLを変える
よくある書き方のミス:相対パスで参照先がズレる
もうひとつ非常に多いのが、相対パスによるズレです。例えば次のように書いた場合を考えます。
<img src="images/logo.png" alt="ロゴ">
この書き方は「今表示しているページのURLを基準にして images/logo.png を取りに行く」という意味になります。ページURLによって、ブラウザが取りに行く先が変わります。
| ページURL | src=”images/logo.png” の解釈 | 実際にリクエストされるURL |
|---|---|---|
| / | ルート直下のimages | /images/logo.png |
| /Home/About | /Home/ 配下のimages | /Home/images/logo.png |
| /Products/Detail/10 | /Products/Detail/ 配下のimages | /Products/Detail/images/logo.png |
そのため、まずは切り分けとして ルート相対(先頭に /) で確認するのが定石です。
<img src="/images/logo.png" alt="ロゴ">
Razorでアプリのベースパスを吸収したいなら ~/ も候補になります。
<img src="~/images/logo.png" alt="ロゴ">
Razor(cshtml)での実践ポイント:画像は“表示できる”だけで終わらせない
画像が表示できたらゴール、ではなく、運用・速度・SEOまで含めて整えると、あとから効いてきます。
alt属性は必ず入れる
アクセシビリティのためだけではなく、画像が表示できないときの代替情報にもなります。検索エンジンが画像の意味を理解する助けにもなるため、適切なaltはSEO面でも無駄になりません。
<img src="~/images/product-a.png" alt="製品Aの外観">
レイアウト崩れ(CLS)対策にwidth/heightを付ける
大きめの画像を使うページでは、画像読み込み前後でレイアウトがガタつくことがあります。可能なら width と height を指定して、表示領域を先に確保します。
<img src="~/images/banner.png" alt="キャンペーンバナー" width="1200" height="400">
一覧ページは遅延読み込みも有効
画像が大量にある一覧では、ファーストビュー以外を遅延読み込みすると体感速度が改善します。
<img src="~/images/item-01.png" alt="商品画像" loading="lazy">
.NET 8 Razor PagesのProgram.cs例(静的ファイルが確実に効く形)
「wwwrootに置いたのにまだ出ない」ケースでは、Program.csの構成が原因になっていることがあります。Razor Pagesの基本形を載せます。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
// これが無いと wwwroot 配下でも配信されない
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.Run();
この形に寄せたうえで、画像は wwwroot/images に置き、cshtmlで ~/images/xxx.png 参照に統一すると、トラブルが激減します。
チェックリスト:画像が出ないときに見る順番
最後に、現場での確認順をまとめます。ここから順に潰すと、無駄な試行錯誤を減らせます。
| 確認項目 | 見る場所 | OKの目安 | NGならどうする |
|---|---|---|---|
| 画像リクエストが404になっていないか | DevTools Network | 200で取得できる | wwwroot配下に移動/UseStaticFiles追加/RequestPathマッピング |
| srcが相対パスでズレていないか | HTMLのsrcと現在URL | /images/.. か ~/images/.. | ルート相対または~/に変更 |
| Program.csにUseStaticFilesがあるか | Program.cs | MapRazorPages等より前にある | 追加・順序修正 |
| 公開環境でファイルが存在するか | Publish出力/サーバー上 | 画像ファイルが配置されている | wwwrootへ寄せる/CopyToPublishDirectory設定 |
| 大文字小文字が一致しているか | ファイル名とsrc | 完全一致 | 表記統一(特にLinux) |
| キャッシュで古い画像を見ていないか | ブラウザ | 更新で反映される | asp-append-version、ファイル名変更、スーパーリロード |
まとめ:迷ったら「wwwrootに置く」「UseStaticFilesを入れる」「絶対パスで確認」
.NET 8(ASP.NET Core)のRazor(cshtml)で画像が表示されないとき、最も多い原因は「プロジェクト直下のimagesは公開されない」という仕様です。解決の近道は、公開したい静的ファイルを wwwroot 配下に寄せ、app.UseStaticFiles() を正しい位置に置き、まずは /images/... で直接アクセスして200が返る状態を作ることです。
それでも構成上 wwwroot に置けないなら、StaticFileOptions で安全にマッピングし、Publish時にファイルが確実に含まれるように整備しましょう。ここまで押さえておけば、ローカルでも本番でも「画像だけ出ない」を安定して潰せるようになります。

コメント