GCS 静的ホスティングで「/ で index.html が表示されない」問題を独自ドメインで解決した
2026-01-23
Next.js で作った技術ブログを Google Cloud Storage(GCS)に静的ホスティングしていたのですが、storage.googleapis.com 直アクセスだと / で index.html が自動表示されず、URL に index.html を付けないとトップが開けない問題にハマりました。
そこで 独自ドメイン(例:yu-tech-blog.dev)を取得し、
**ドメイン名と同じバケット + Website 設定 + DNS(Cloudflare)**で / アクセスでもトップが開くようにしたので、手順をまとめます。
先に結論
https://<bucket>.storage.googleapis.com/は トップにアクセスしてもindex.htmlが出ず、状況によっては **XML の一覧(ListBucketResult)**が返るindex.htmlを/で出したいなら、**独自ドメイン + Website 設定(MainPageSuffix)**を使う- ドメイン名のバケット(例:
gs://yu-tech-blog.dev)を作る場合、**所有権確認(Search Console)**が必要
背景:なぜ index.html なしで表示されないのか
GCS の「静的ウェブサイト」機能(MainPageSuffix=index.html など)は、
カスタムドメイン(CNAME / A レコード経由)でアクセスした場合に適用されます。
一方で、storage.googleapis.com / <bucket>.storage.googleapis.com のような GCS ドメイン直アクセスでは、/ は “バケットそのもの” へのアクセス扱いになり、index.html の自動解決ではなく、
.../index.htmlを明示すると表示できる.../<bucket>だけだと XML の一覧が返る(公開設定次第)
…のような挙動になりがちです。
ゴール
- 独自ドメイン:
https://yu-tech-blog.dev/でトップが開く /posts/...などもそのまま開ける
全体構成
Browser
↓ HTTPS
Cloudflare(DNS/HTTPS終端)
↓ HTTP
GCS(静的ウェブサイト: MainPageSuffix=index.html)
.devは HTTPS 前提のため、Cloudflare で HTTPS を終端させると運用が楽です(固定費も抑えやすい)。
Step 1. Next.js を静的書き出し(復習)
next.config.js
// next.config.js
const nextConfig = {
output: "export",
};
module.exports = nextConfig;
npm run build
出力は out/ にまとまります。
out/
├── index.html
├── posts/
└── _next/
Step 2. ドメイン名のバケットを作る(ここが今回の本題)
例:ドメインが yu-tech-blog.dev なら、バケット名も 同じにします。
gsutil mb -l asia-northeast1 gs://yu-tech-blog.dev
ハマり:403(所有権確認が必要)
ドメイン名のバケットを作ろうとすると、
AccessDeniedException: 403 You must verify site or domain ownership ...
のようなエラーが出る場合があります。これは「そのドメインの所有権が確認できないと、同名のバケットは作れない」という仕様です。
この場合の解決手順は下記の通りです。
解決:Search Console でドメイン所有権を証明
- Google Search Console で
yu-tech-blog.devを追加(ドメイン推奨) - 指示された DNS TXT レコードをドメインのDNS(例:Cloudflare等)に追加
- Search Console で「確認」
- 確認できたら、再度
gsutil mb gs://yu-tech-blog.devを実行
- ドメインのDNS管理権限が必要です
- 所有権確認が済めば、バケット作成が可能になります
この手順で「AccessDeniedException: 403 You must verify site or domain ownership」のエラーは解消できます。
Step 3. Website 設定(index / 404)
/ で index.html を返すように Website 設定を入れます。
gsutil web set -m index.html -e 404.html gs://yu-tech-blog.dev
Step 4. 公開権限(allUsers)
まず動かす(簡単だが “一覧” が見えることがある)
gsutil iam ch allUsers:objectViewer gs://yu-tech-blog.dev
これで https://yu-tech-blog.dev/index.html などにアクセスできるようになります。
ただし objectViewer は環境によって **一覧(list)**が可能になり、
バケット直アクセスで XML の一覧が出ることがあります。
一覧を抑止したい場合(GET のみに寄せる)
# objectViewer を外す
gsutil iam ch -d allUsers:objectViewer gs://yu-tech-blog.dev
# GET だけ寄せる(一覧を出したくない場合)
gsutil iam ch allUsers:legacyObjectReader gs://yu-tech-blog.dev
公開要件(検索させたい / させたくない)で選びます。静的サイト用途なら GET だけでも十分なケースが多いです。
Step 5. out/ をアップロード(既存手順のまま)
gsutil -m rsync -r -d out gs://yu-tech-blog.dev
Step 6. Cloudflare DNS を設定
Cloudflare の DNS にレコードを追加します。
apex(yu-tech-blog.dev)
- Type: CNAME
- Name: @
- Target: c.storage.googleapis.com
- Proxy: ON(オレンジ雲) でOK(まずは ON 推奨)
www(任意)
www.yu-tech-blog.dev も使うなら、次のどちらか:
wwwを CNAME で同じくc.storage.googleapis.comに向ける- もしくは Cloudflare で www → apex に 301 リダイレクト(運用が楽)
Step 7. Cloudflare の SSL/TLS 設定(重要)
GCS の静的ウェブサイトは HTTP 前提になりやすいので、Cloudflare 側の暗号化モードはまずはこれが無難です。
- Cloudflare → SSL/TLS → Encryption mode: Flexible
これで
- ブラウザ ⇄ Cloudflare:HTTPS
- Cloudflare ⇄ GCS:HTTP
となり、.dev でも問題なく表示できます。
動作確認
https://yu-tech-blog.dev/→ トップ(index.html)https://yu-tech-blog.dev/posts/2025-12-05-cloudstorage.html→ 記事- 404 →
404.html(Not Found Page)
まとめ
- GCS ドメイン直アクセスでは
/がindex.htmlに解決されず、index.htmlを付けないとトップが開けないことがある - 独自ドメインを取って ドメイン名と同じバケットを作り、Website 設定を入れると
/でトップが開ける - ドメイン名バケット作成には Search Console での所有権確認が必要
- 公開ロールは、必要に応じて 一覧(list)を出す/出さないで選ぶ
補足:GCSドメイン直アクセスではindex.html自動表示は効かない(公式仕様)
*.storage.googleapis.com 直アクセスでは “index.html自動表示” は効きません。
GCS の Website 設定(MainPageSuffix/NotFoundPage)は、CNAME / A レコード(カスタムドメイン経由)で来たリクエストにだけ適用されます。
つまり、
https://yu-tech-blog.dev/などカスタムドメイン経由 → Website設定が効く(/でindex.htmlが出る)https://storage.googleapis.com/yu-tech-blog.dev/などGCSドメイン直アクセス → Website設定は効かず、index.html自動表示もされない
この仕様はGoogle公式ドキュメントにも明記されています:
MainPageSuffix と NotFoundPage のウェブサイト構成は、CNAME または A リダイレクトを介して Cloud Storage に送信されるリクエストにのみ使用されます。たとえば、www.example.com へのリクエストではインデックス ページが表示されますが、storage.googleapis.com/www.example.com への同等のリクエストでは表示されません。