www有無でCORSエラーが発生する問題と、Amplify + S3 + CloudFrontによるリダイレクト構築
はじめに
本番環境で断続的に発生していたネットワークエラーを調査していたところ、www.example.com と example.com のOrigin不一致によるCORSエラーが原因のひとつであることが判明しました。
この記事では、www有無の問題がどのように発覚し、AWS Amplify Hostingの制約を踏まえてどのようにリダイレクトを構築したかを紹介します。
課題:同じサイトなのにAPIが使えないユーザーがいる
Sentryで TypeError: Load failed(Safari)や TypeError: Failed to fetch(Chrome)というエラーが3ヶ月間断続的に報告されていました。
診断ログを追加して詳細を分析したところ、エラーが発生しているイベントの url タグに注目すべき違いがありました。
# 正常なユーザー
url: https://example.com/page/xxx
# エラーが発生するユーザー
url: https://www.example.com/page/xxx
バックエンドのCORS設定は https://example.com のみを許可しており、https://www.example.com からのリクエストはプリフライトで拒否されていました。
# www なし → 200 OK
curl -X OPTIONS -H "Origin: https://example.com" https://api.example.com/api
# www あり → 400 Bad Request
curl -X OPTIONS -H "Origin: https://www.example.com" https://api.example.com/api
www有無が引き起こす問題
www.example.com と example.com はブラウザにとって別のOriginです。これにより以下の問題が同時に発生します。
| 問題 | 影響 |
|---|---|
| CORS | バックエンドAPIのプリフライトが拒否される |
| localStorage / sessionStorage | データが共有されず、ログイン状態が別空間になる |
| Cookie | スコープによっては一方にしか送信されない |
| Service Worker(PWA) | 別Originなので登録・キャッシュが独立する |
| SEO | Googleが重複コンテンツとしてインデックスする可能性 |
CORS設定に www を追加すれば暫定的にエラーは止まりますが、他の問題は残り続けます。根本的にはURLをどちらかに統一してリダイレクトする必要があります。
検討した方法と結果
方法1: Amplifyのカスタムリダイレクトルール
AWS Amplify Hostingにはリダイレクトルールの設定機能があります。
{
"source": "https://www.example.com/<*>",
"status": "301",
"target": "https://example.com/<*>"
}
結果: 効かなかった。
Amplifyのドメイン設定で www サブドメインがブランチに紐づいてコンテンツを配信する設定になっている場合、サブドメインの紐づけがカスタムリダイレクトルールより優先されるため、リダイレクトに到達しません。
方法2: Amplifyからwwwサブドメインを削除
サブドメインの紐づけが原因なら、削除すればリダイレクトが効くはずです。
aws amplify update-domain-association --app-id xxx --domain-name example.com \
--sub-domain-settings '[{"branchName":"main","prefix":""}]'
結果: SSL証明書エラーになった。
AmplifyがマネージドするCloudFrontのSSL証明書から www.example.com が外れるため、TLSハンドシェイクが失敗します。DNSは解決できてもHTTPS接続ができず、リダイレクトに到達できません。
方法3: S3 + CloudFrontでリダイレクト専用構成(採用)
Amplifyとは独立した、リダイレクト専用のインフラを構築する方法です。
採用したアーキテクチャ
ユーザー: https://www.example.com/page/xxx
→ Route53 CNAME → CloudFront(リダイレクト専用)
→ S3(RedirectAllRequestsTo)
→ 301 Location: https://example.com/page/xxx
構築手順
1. ACM証明書の発行(us-east-1)
CloudFrontで使用する証明書はus-east-1リージョンに作成する必要があります。
aws acm request-certificate \
--domain-name www.example.com \
--validation-method DNS \
--region us-east-1
DNS検証レコードをRoute53に追加して発行を待ちます。
2. S3バケットの作成(リダイレクト専用)
コンテンツは一切格納せず、全リクエストをリダイレクトする設定のみ行います。
aws s3api create-bucket --bucket www.example.com --region ap-northeast-1 \
--create-bucket-configuration LocationConstraint=ap-northeast-1
aws s3api put-bucket-website --bucket www.example.com \
--website-configuration '{"RedirectAllRequestsTo":{"HostName":"example.com","Protocol":"https"}}'
3. CloudFrontディストリビューションの作成
S3ウェブサイトエンドポイントをオリジンに設定します。ポイントは OriginProtocolPolicy: http-only です(S3ウェブサイトホスティングはHTTPSをサポートしないため)。
aws cloudfront create-distribution --distribution-config '{
"Aliases": {"Quantity": 1, "Items": ["www.example.com"]},
"ViewerCertificate": {
"ACMCertificateArn": "<証明書ARN>",
"SSLSupportMethod": "sni-only"
},
"Origins": {"Quantity": 1, "Items": [{
"Id": "S3-redirect",
"DomainName": "www.example.com.s3-website-ap-northeast-1.amazonaws.com",
"CustomOriginConfig": {
"HTTPPort": 80,
"OriginProtocolPolicy": "http-only"
}
}]},
"DefaultCacheBehavior": {
"TargetOriginId": "S3-redirect",
"ViewerProtocolPolicy": "redirect-to-https",
...
},
"IsIPV6Enabled": true
}'
4. Route53のCNAME更新
www.example.com を新しいCloudFrontディストリビューションに向けます。
# 旧CNAME(Amplify CloudFront)を削除してから
aws route53 change-resource-record-sets --change-batch '{
"Changes": [{"Action": "CREATE", "ResourceRecordSet": {
"Name": "www.example.com",
"Type": "CNAME",
"TTL": 300,
"ResourceRecords": [{"Value": "<新CloudFrontドメイン>"}]
}}]
}'
ハマりポイント
CloudFrontのCNAME競合
既存のCNAMEが別のCloudFrontディストリビューション(Amplify管理)を指している状態では、新しいディストリビューションにエイリアスを設定できません(CNAMEAlreadyExists エラー)。Route53のCNAMEを先に削除してからディストリビューションを作成する必要があります。
Amplifyのドメイン設定削除の副作用
Amplifyのドメイン設定からサブドメインを削除すると、Route53のCNAMEレコードも自動的に削除される場合があります。手動で設定したDNSレコードが消えていないか確認が必要です。
CloudFrontのデプロイ時間
Status: InProgress でも最寄りのエッジロケーション(例: 東京)では既に有効になっている場合があります。ブラウザで確認して動作していれば、全エッジへのデプロイ完了(Deployed)を待つ必要はありません。
まとめ
| 方法 | 結果 |
|---|---|
| Amplifyカスタムリダイレクトルール | サブドメイン紐づけが優先され効かない |
| Amplifyからwww削除 | SSL証明書から外れTLS失敗 |
| S3 + CloudFront独立構成 | 正常にリダイレクト動作 |
www/非wwwの混在は、CORSだけでなくlocalStorage・Cookie・PWAなど広範囲に影響する問題です。Amplify Hostingを使用している場合、リダイレクト設定に制約があるため、S3 + CloudFrontによる独立した構成が確実な解決策になります。
Discussion