Blazor WebAssembly を GitHub Pages にデプロイしたら「ローカルでは動くのに本番は真っ白&CORS エラーだらけ」という状態は、設定のポイントさえ押さえればきれいに解消できます。本記事では、実際に遭遇した manifest.webmanifest の CORS エラーと白画面問題を題材に、「なぜそうなるのか」「どう直すのか」を GitHub Pages 向けに具体的な手順とコード例つきで整理します。
Blazor WASM を GitHub Pages に出したらページが真っ白になる問題
今回の相談内容を整理すると、状況は次のようになります。
- ローカル(IIS Express)では Blazor WebAssembly(スタンドアロン)が正常動作している。
- 同じ成果物を GitHub Pages に置くと、表示されるのは真っ白なページだけでアプリが立ち上がらない。
- ブラウザの DevTools(開発者ツール)を見ると、コンソールに CORS エラーが出ている。
代表的なエラーメッセージは次の通りです。
Access to manifest at 'https://www.capitalprints.com/manifest.webmanifest'
from origin 'https://acqu.github.io' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
さらに、画像アップロードに使っていた API(例:imgbb)まわりのエラーも併発しており、axios に書き換えたところ画像アップロードだけは動いたものの、肝心の Blazor アプリ本体は真っ白なまま……という状況です。
この状態を一言でいうと、Blazor が必要とするファイルを「別オリジン」や「配信されないフォルダ」から読みに行ってしまい、ブラウザ側の制約と GitHub Pages の仕様に引っかかっている、ということになります。
CORS エラーと白画面の正体を整理する
まずは、出ている症状とその原因をざっくり表にしてみます。
| 症状 | 画面の見え方 | 主な原因 | 確認する場所 |
|---|---|---|---|
manifest.webmanifest の CORS エラー | 画面は真っ白、PWA としても動かない | <base href> のせいで別オリジンからマニフェストを取得しようとしている | DevTools > Console / Network(Manifest) |
_framework/blazor.webassembly.js が 404 / 配信されない | アプリ本体が起動せず真っ白 | GitHub Pages の Jekyll により _framework フォルダが無視されている | DevTools > Network(Status 404 or (failed)) |
| API 呼び出しで CORS エラー | 画面は出るが、API 利用時にエラー | 呼び出し先サーバが Access-Control-Allow-Origin を返していない | DevTools > Network(API リクエストのレスポンスヘッダー) |
| 深い URL に直接アクセスすると 404 | トップは表示されるが、リロードや直リンクで 404 | SPA ルーティング用の 404.html が無い / 不十分 | ブラウザで /counter などに直接アクセス |
ポイントは次の 3 つです。
- CORS は呼び出される側サーバの設定で決まる(フロント側からは基本的に回避できない)。
- <base href> が GitHub Pages の公開パスとずれていると、すべての相対パスが別オリジンに飛ばされる。
- GitHub Pages では _framework など「アンダースコア始まりのフォルダ」が Jekyll によって配信されない(.nojekyll が必要)。
今回のケースで実際に起きていること
<base href> が外部ドメインを向いている問題
問題の HTML(index.html)では、次のような記述が入っていたとします。
<base href="https://www.capitalprints.com/" />
この 1 行により、ページ内の すべての相対パス が https://www.capitalprints.com/ を起点として解決されます。つまり、
<link rel="manifest" href="manifest.webmanifest"><script src="_framework/blazor.webassembly.js"></script><link href="css/app.css" rel="stylesheet">
といった記述は、ブラウザから見るとそれぞれ
https://www.capitalprints.com/manifest.webmanifesthttps://www.capitalprints.com/_framework/blazor.webassembly.jshttps://www.capitalprints.com/css/app.css
として解決されます。
しかし、実際にページを開いている URL は https://acqu.github.io/www.capitalprints.com/ です。つまり、
- ページ自体のオリジン:
https://acqu.github.io - リソースを取りに行っているオリジン:
https://www.capitalprints.com
という「別オリジン」間通信になっています。その結果、
- マニフェストは CORS 制約 でブロック。
- Blazor ランタイム(
_framework/*)はそもそも存在しない or 別設定のサーバなので 404。 - アプリが起動する前にエラーで止まり、画面は真っ白。
という状態になります。
マニフェストの CORS エラーが示していること
Access to manifest at 'https://www.capitalprints.com/manifest.webmanifest'
from origin 'https://acqu.github.io' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
このメッセージを分解すると、次のような意味になります。
- 今開いているページのオリジンは
https://acqu.github.io。 - ブラウザは
https://www.capitalprints.com/manifest.webmanifestを取りに行った。 - 返ってきたレスポンスに
Access-Control-Allow-Origin: https://acqu.github.io(または*)が含まれていない。 - よって、ブラウザは「CORS ポリシー違反」と判断し、JavaScript からの利用を禁止した。
このエラー自体は PWA(インストール可能な Web アプリ)としての挙動に関わりますが、今回のように <base> が別オリジンを向いていると、マニフェスト以外のファイルも全部外部解決になり、結果としてアプリが起動不能になります。
GitHub Pages と Jekyll による _framework ブロック
Blazor WebAssembly では、ランタイムや DLL、圧縮されたアセットが _framework/ フォルダ以下に配置されます。しかし GitHub Pages では、既定で Jekyll が有効になっており、
- アンダースコア(
_)で始まるフォルダやファイルはサイト生成から除外する
というルールがあります。つまり、何も対策しないと _framework フォルダが配信されず、
_framework/blazor.webassembly.jsが 404(見つからない)。- Blazor アプリの起動前に JavaScript エラーで停止。
- 画面は真っ白なまま。
という症状になります。
GitHub Pages で Blazor WASM を正常に動かすための設定
ここからは、実際に何をどう直せばよいかを具体的に解説します。ポイントは次の 4 つです。
- <base href> を GitHub Pages の公開パスに合わせる
- .nojekyll を置いて _framework を配信できるようにする
- manifest.webmanifest を同一オリジンから配信する
- SPA ルーティング用の 404.html を用意する
1. GitHub Pages 用に <base href> を正しく設定する
まず、GitHub Pages の公開 URL を確認します。今回の例では次のような形です。
https://acqu.github.io/www.capitalprints.com/
この場合、リポジトリ名が www.capitalprints.com であり、GitHub Pages の公開パスは
/www.capitalprints.com/
となります。したがって、index.html の <base href> は次のように修正します。
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- ★ GitHub Pages の公開パスに合わせる -->
<base href="/www.capitalprints.com/" />
<!-- マニフェストは相対パスで同一オリジンから取得 -->
<link rel="manifest" href="manifest.webmanifest" />
<!-- CSS なども相対パスで OK(base が効く) -->
<link href="css/app.css" rel="stylesheet" />
</head>
<body>
<div id="app">Loading...</div>
<!-- Blazor WASM ランタイム -->
<script src="_framework/blazor.webassembly.js"></script>
</body>
こうすることで、ブラウザは
manifest.webmanifest→https://acqu.github.io/www.capitalprints.com/manifest.webmanifest_framework/blazor.webassembly.js→https://acqu.github.io/www.capitalprints.com/_framework/blazor.webassembly.js
というように、すべてのリソースを GitHub Pages 上の同一オリジンから取得します。CORS を気にする必要がなくなり、あとは GitHub Pages が正しくファイルを配信してくれれば OK です。
なお、将来的に GitHub Pages にカスタムドメイン(例:www.capitalprints.com)を割り当てる場合は、CNAME 設定が反映されたタイミングで <base href="/"> に戻して構いません。
2. .nojekyll を公開ブランチのルートに追加する
次に、Jekyll による _framework フォルダのブロックを回避します。方法は非常にシンプルで、公開ブランチ(通常は gh-pages または main)のルートに、空ファイル .nojekyll を置くだけです。
ローカルで作業している場合は、次のように作成できます。
# プロジェクトの公開用フォルダ(例: dist, docs, wwwroot)に移動
cd path/to/publish
# 空の .nojekyll ファイルを作成(Windows の PowerShell 例)
ni .nojekyll -ItemType File
# GitHub にコミット
git add .nojekyll
git commit -m "Add .nojekyll for Blazor WASM on GitHub Pages"
git push
これにより、GitHub Pages のビルド時に Jekyll が無効になり、_framework を含むすべての静的ファイルがそのまま配信されるようになります。
3. manifest.webmanifest を同一オリジンから配信する
PWA 対応をしている場合、Blazor のテンプレートは wwwroot 直下に manifest.webmanifest を生成します。これをそのまま公開し、HTML からは次のように相対パスで参照します。
<link rel="manifest" href="manifest.webmanifest" />
ポイントは、
- href に絶対 URL(https://〜)を直書きしないこと
- <base href> が正しい公開パスを向いていること
の 2 点です。これさえ守れば、マニフェストは常に「今開いているページと同じオリジン」から取得されるので、CORS エラーの心配はありません。
どうしても外部ドメインからマニフェストを配信したい場合は、そのサーバ側で
Access-Control-Allow-Origin: https://acqu.github.io
などの CORS ヘッダーを返す必要があります。しかし、多くのケースではそこまで外部に置くメリットはなく、Blazor アプリと同じ場所から配信する方がシンプルです。
4. SPA ルーティング用に 404.html を設置する
Blazor WebAssembly はクライアントサイドルーティング(SPA)を行うため、/counter のような深いパスに直接アクセスしたりリロードしたりすると、GitHub Pages 側では「そんなファイルは無い」と判断し 404 を返してしまいます。
これを防ぐために、SPA 用の 404.html を用意して、すべての 404 アクセスを index.html にリダイレクトします。最小構成の例は次のとおりです。
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="refresh" content="0; url=./index.html" />
<script>
// クエリ文字列やフラグメントも引き継いで index.html へ遷移
location.replace('./index.html' + location.search + location.hash);
</script>
</head>
<body></body>
</html>
このファイルを公開ブランチのルート(index.html と同じ階層)に置くことで、GitHub Pages 上でのクライアントルーティングが安定します。
5. デプロイ後に開発者ツールで確認すべきポイント
設定を変えたら、必ずブラウザの DevTools で動作を確認しましょう。チェックポイントを一覧にまとめます。
| 確認内容 | ツール | 期待する状態 |
|---|---|---|
_framework/blazor.webassembly.js の取得 | Network タブ | Status が 200(404 や (failed) でない) |
manifest.webmanifest の取得 | Network タブ / Application > Manifest | 同一オリジンから 200 で取得できている |
| Console のエラー | Console タブ | CORS エラーや 404 エラーが出ていない |
深い URL(例:/counter)への直アクセス | ブラウザのアドレスバー | 404 ページではなく Blazor アプリが表示される |
外部 API・画像アップロードと CORS の考え方
今回の相談では、画像アップロードに使っている API(例:imgbb)についても「fetch だとエラーだったが axios に変えたら動いた」といった経緯がありました。ここで整理しておきたいのは、
axios に変えたからといって CORS 自体を回避できるわけではない
という点です。
CORS はサーバ側のルールで決まる
CORS(Cross-Origin Resource Sharing)は、ブラウザが「別オリジンへのリクエストを許可してよいかどうか」を判断するための仕組みであり、そのルールは 呼び出される側のサーバがレスポンスヘッダーとして返す値で決まります。
例えば、GitHub Pages 上のページ(https://acqu.github.io)から外部 API(https://api.example.com)にアクセスする場合、API 側が次のようなヘッダーを返していればブラウザはアクセスを許可します。
Access-Control-Allow-Origin: https://acqu.github.io
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
逆に、このヘッダーが無い・またはオリジンが一致していない場合、ブラウザがリクエストそのものをブロックします。fetch であろうと axios であろうと、この仕組みは変わりません。
axios に書き換えたことで一時的に「動いた」ように見えるのは、
- デフォルトのヘッダーやリクエスト方式が変わり、プリフライト(OPTIONS)リクエストが発生しなくなった
- もともと API 側が緩めの CORS 設定になっていた
といった偶然の要素が重なっただけ、という可能性が高いです。根本的な CORS 制約そのものを回避したわけではない、という点は押さえておきましょう。
自分で制御できない API を安全に使うには
第三者の API(例:画像アップロードサービス、外部ストレージなど)をフロントエンドから直接叩く場合、次のいずれかの対策が現実的です。
- API 提供元が公式にサポートしている CORS 設定(ドキュメント)を確認し、その範囲で利用する。
- どうしても CORS が厳しくて使えない場合は、自前のプロキシサーバを用意してサーバサイドから API を叩く。
プロキシのイメージとして、Cloudflare Workers での簡易実装例を示します。
export default {
async fetch(req) {
const url = new URL(req.url);
const target = 'https://api.example.com' + url.pathname + url.search;
const resp = await fetch(target, {
method: req.method,
body: req.body,
headers: req.headers
});
const h = new Headers(resp.headers);
h.set('Access-Control-Allow-Origin', 'https://acqu.github.io');
h.set('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
h.set('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS') {
return new Response(null, { headers: h });
}
return new Response(resp.body, { status: resp.status, headers: h });
}
}
このようなプロキシを挟むことで、
- ブラウザ → プロキシ(同一オリジン or 許可済みオリジン)
- プロキシ → 外部 API(サーバサイド)
という形に分離でき、ブラウザ側の CORS 制約をコントロールしやすくなります。ただし、API キーの管理や利用制限など、セキュリティ面の考慮も必要です。
実際の修正手順をステップバイステップで整理
ここまでの内容を踏まえて、実際に GitHub Pages 上で Blazor WASM を動かすための手順をまとめます。
ステップ 1:Blazor アプリをビルド・発行する
- Visual Studio または CLI で、Blazor WebAssembly(スタンドアロン)アプリを Release ビルド。
dotnet publish -c Release -o ./publishのように、発行フォルダを作成。- 発行先フォルダには
index.html,_framework,wwwrootなどがまとまっている状態にする。
ステップ 2:index.html の <base href> と manifest の参照を修正
publish/index.htmlを開く。<base href="/">などになっている部分を、GitHub Pages の公開パスに合わせて変更。
例:https://acqu.github.io/www.capitalprints.com/ で公開する場合
<base href="/www.capitalprints.com/" />
<link rel="manifest" href="manifest.webmanifest">のように、マニフェストは相対パス参照にしておく。
ステップ 3:.nojekyll と 404.html を追加
- 発行フォルダ(GitHub にアップするフォルダ)のルートに
.nojekyllを作成。 - 同じくルートに、先ほど紹介した簡易リダイレクトの
404.htmlを配置。
ステップ 4:GitHub Pages 用のブランチにアップロード
- 発行フォルダの中身を、GitHub のリポジトリ(例:
gh-pagesブランチ)にコミット。 - GitHub のリポジトリ設定 > Pages で、公開対象ブランチとフォルダ(root または /docs)を指定。
ステップ 5:公開 URL にアクセスして動作確認
- 公開 URL(例:
https://acqu.github.io/www.capitalprints.com/)にアクセス。 - DevTools を開き、Network / Console を見ながら次を確認:
_framework/blazor.webassembly.jsが 200 で取得できている。manifest.webmanifestが CORS エラーなしで取得されている。- API 呼び出し時に CORS エラーが出ていない。
/counterなどのパスに直接アクセスしても 404 ではなくアプリが表示される。
再発防止用チェックリスト
最後に、同じ問題でハマらないためのチェックリストを整理しておきます。デプロイのたびにざっと確認すると安心です。
- [ ]
index.htmlの<base href>が GitHub Pages の公開パス(例:/www.capitalprints.com/)になっている。 - [ ] マニフェスト・CSS・JS を絶対 URL ではなく相対パスで参照している。
- [ ] 公開ブランチのルートに
.nojekyllが置いてある。 - [ ]
404.htmlを配置し、深い URL 直アクセスでも Blazor アプリに戻るようになっている。 - [ ] Network タブで
_framework/,_content/フォルダ配下の静的ファイルが 200 で配信されている。 - [ ] 外部 API を利用している場合、そのドメインに対する CORS 設定(またはプロキシ)が用意されている。
- [ ] カスタムドメインを設定する場合、CNAME 設定後に
<base href="/">に変更する手順を把握している。
よくある誤解・ハマりポイントまとめ
- 「axios に変えれば CORS を回避できる」
→ できません。CORS はサーバ側のレスポンスヘッダーで決まるため、クライアントライブラリで魔法のように無効化することはできません。 - 「GitHub Pages に上げたら勝手にうまくやってくれる」
→ Blazor WASM のように_frameworkフォルダを使うアプリでは、.nojekyllと<base href>の調整がほぼ必須です。 - 「PWA 用のマニフェストは別ドメインから配信しても大丈夫」
→ 理論上可能ですが、CORS 設定が必要になり、トラブルの原因になりがちです。基本はアプリと同じオリジンで配信しましょう。 - 「API キーをそのまま JavaScript に埋め込んでも問題ない」
→ 公開リポジトリにコミットされたキーは誰でも閲覧できます。利用制限(Referer 制限や IP 制限)をかけるか、サーバ側で隠す設計を検討しましょう。 - 「ローカルで動いているから本番も大丈夫」
→ ローカルでは同一オリジン扱いになってしまうケースが多く、本番の CORS やパスの問題が見えにくくなります。できればローカルでも GitHub Pages と似たパス構成でテストすると安心です。
まとめ:Blazor WASM × GitHub Pages の安定デプロイパターン
Blazor WebAssembly アプリを GitHub Pages に公開する際、
<base href>を公開パスに合わせる.nojekyllを配置して_frameworkフォルダを配信可能にするmanifest.webmanifestを同一オリジンから取得する- SPA 用の
404.htmlを用意する
という 4 点を押さえておけば、「ローカルでは動くのに GitHub Pages だと真っ白」「CORS エラーが大量発生」という状態はほぼ避けられます。外部 API を利用する場合も、CORS はサーバ側の設定であることを理解し、プロキシの活用や API キーの扱いを含めて設計することが重要です。
一度このパターンをテンプレートとして固めておけば、以降の Blazor プロジェクトでもほぼコピペで安定した GitHub Pages デプロイが実現できます。今回の手順をベースに、自分のプロジェクト向けにチェックリストやスクリプトを整備しておくと、今後の運用がぐっと楽になるはずです。

コメント