GCS 静的ホスティングで「/ で index.html が表示されない」問題を独自ドメインで解決した

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 でドメイン所有権を証明

  1. Google Search Consoleyu-tech-blog.dev を追加(ドメイン推奨)
  2. 指示された DNS TXT レコードをドメインのDNS(例:Cloudflare等)に追加
  3. Search Console で「確認」
  4. 確認できたら、再度 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 への同等のリクエストでは表示されません。