はじめに
以前、在庫管理システムについて記事を書きました(物販の在庫管理ダッシュボードに「追加・削除」と「誤操作対策」を実装した話)。今回は在庫管理とは別に、お客さん向けの商品カタログを作った話です。
これまでは、商品の価格表を Canva で作り、画像として Facebook に投稿していました。Canva でも写真や説明を載せることはできますが、そうすると商品の数だけページ数が増えていきます。そのうえ、価格が変わるたびに Canva で作り直して投稿し直す必要があり、手間がかかっていました。
そこで、価格を手軽に変更できて、お客さんが商品の写真や説明も見られるカタログを作る必要がありました。
| Canva の価格表 | 今回のカタログ | |
|---|---|---|
| 商品の追加・削除、価格の変更 | そのたびに作り直して投稿し直す | スプレッドシートを直すだけ(1〜2分で反映) |
| セール | 開始時と終了時にそれぞれ投稿し直す | 終了日を入れておけば、自動で元の価格に戻る |
| 古い情報 | 過去の投稿が残り、古い価格を見た問い合わせが来る | URL は1つで、いつでも最新 |
| 写真・説明 | 載せられるが、商品の数だけページが増える | 商品ごとに写真・仕様・特徴を表示 |
| 検索・絞り込み | できない | 検索、カテゴリ・ブランドで絞り込み |
なお、開発は Claude Code と対話しながら進めました。
全体構成
ポイントは、サイト本体(HTML・CSS・JS・画像)は AWS、商品データは Googleスプレッドシートと役割を分けたことです。
- 商品の追加・値下げ・売り切れなど、毎日の更新はスプレッドシートだけで済み、AWS に触る必要がありません。
- ブラウザが公開 CSV を直接読みに行くので、サーバー(EC2 や Lambda)は1台もありません。
- AWS の構成はすべて Terraform で管理し、写真やデザインを変えたときは
npm run deployの1コマンドで公開します。
記事中のドメインは
example.comに置き換えています。スクリーンショットは、アカウント ID やドメイン名などを伏せています。
AWS 構成のポイント
S3:非公開にして CloudFront からだけ読ませる
S3 の静的ウェブサイトホスティングは使わず、パブリックアクセスをすべてブロックしています。CloudFront からは OAC(Origin Access Control) で読み取り、バケットポリシーでは自分のディストリビューションだけを許可しています。
Condition = {
StringEquals = { "AWS:SourceArn" = aws_cloudfront_distribution.site.arn }
}
CloudFront:料金クラスとキャッシュ
- 料金クラスは PriceClass_200:PriceClass_100(北米・欧州のみ)だと、アジアのお客さんが遠くの配信拠点につながってしまうためです。
- HTTP → HTTPS へのリダイレクト、HTTP/2・HTTP/3、圧縮を有効にしています。
ACM:CloudFront 用の証明書は us-east-1 で作る
CloudFront で使う証明書は、バージニア北部(us-east-1)で発行する必要があります。S3 はシンガポールに置いているので、Terraform ではプロバイダーを2つ用意しました。
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}
検証方法は DNS 検証で、検証用のレコードも Terraform で Route 53 に作っています。証明書は無料で、自動で更新されます。
Route 53:ドメインの取得と Alias レコード
ドメインの「登録」と「DNS」は別の機能で、別の会社でもかまいません。お名前.com などで取得して、DNS だけ Route 53 に向ける方法もよく使われています。今回は、次の理由で登録も Route 53 にしました。
- 登録と同時にホストゾーンが作られ、ネームサーバーの設定が不要
- ACM の DNS 検証や CloudFront への Alias を、Terraform でまとめて管理できる
- 請求が AWS にまとまる
一方で、AWS アカウントに何かあるとドメインも影響を受ける点、支払いが米ドルである点、.jp ドメインは割高な点には注意が必要です。
ドメインと www は、A(IPv4)と AAAA(IPv6)の Alias レコードで CloudFront に向けています。Alias ならドメインの頂点(example.com 自体)にも設定でき、CloudFront に向けた Alias への問い合わせ料金もかかりません。A と AAAA の両方を作っているのは、CloudFront の IPv6 を有効にしているためです。AAAA があると、IPv6 で接続できるお客さんは IPv6 で、それ以外は IPv4 でつながります。
IAM:デプロイ専用ユーザー
ルートユーザーは使わず、デプロイ用の IAM ユーザーを作っています。デプロイ用ユーザーには、S3・CloudFront・ACM・Route 53 の AWS 管理ポリシーだけを付けています。
AmazonS3FullAccessCloudFrontFullAccessAWSCertificateManagerFullAccessAmazonRoute53FullAccess
今後は、対象のリソースだけに絞ったカスタムポリシーと、IAM Identity Center の一時的な認証情報に移行する予定です。
キャッシュの工夫
写真やデザインを更新したのに、古い表示が残らないようにしています。
-
ファイル名にハッシュを付ける:ビルド時にファイルの中身からハッシュを計算し、
style.css?v=1a2b3cのように URL に付けています。CloudFront のキャッシュキーには、クエリ文字列のうちvだけを含めています。中身が変わったファイルだけ、新しく取りに行く仕組みです。 -
ファイルの種類ごとにキャッシュ時間を変える:
index.htmlは60秒、ハッシュ付きの画像は1年など。 -
アップロードの順番:画像 → CSS・JS → 最後に
index.htmlの順にして、新しい HTML が古いファイルを参照しないようにしています。 - デプロイの最後に
/*の無効化(invalidation)もしています。無効化は月 1,000 パスまで無料で、/*は1パスと数えられます。
npm run deploy は、この一連の流れ(予備データの更新 → terraform apply → ビルド → aws s3 sync → 無効化)を1つにまとめたスクリプトです。
商品データ:スプレッドシートを「ウェブに公開」して CSV で読む
- Googleスプレッドシートを「ファイル → 共有 → ウェブに公開」で、CSV 形式で公開します。
- ブラウザがその CSV を
fetchで直接読み、JavaScript でカタログを組み立てます。
Google 側でキャッシュされるため、シートを直してから反映されるまで 1〜2分かかります。Canva で作り直していたころと比べれば、十分な速さです。
壊れにくくするために、次の工夫をしています。
- 列は見出しの言葉で判別:列の順番を入れ替えても壊れません。
- 予備データ:CSV が読めなかったときは、デプロイ時に保存した JSON を表示します。
- 公開しない情報は別のタブに:「ウェブに公開」したシートは誰でも読めるため、お客さんに見せない情報は公開しないタブに分けています。
リクエストフォームは AWS ではなく Google Apps Script
「入荷してほしい商品」を匿名で送れるフォームを付けました。受け口は Google Apps Script で、届いた内容をスプレッドシートの別タブに記録し、メールで通知します。
API Gateway + Lambda + SES でも作れますが、記録先がスプレッドシートなら Apps Script が一番シンプルで、費用もかかりません。スパム対策として、ボットを見分ける仕組みと、一定時間あたりの受付件数の上限を入れています。
まとめ
- Canva で作り直していた作業が、スプレッドシートのセルを直すだけになりました。お客さんも、写真や商品の特徴を自分で確認できるようになりました。
- データはスプレッドシート、配信は S3 + CloudFront と役割を分けたことで、毎日の更新は AWS に触らずに済んでいます。
- S3 は非公開 + OAC、HTTPS は ACM、ドメインは Route 53 の Alias という、静的サイトの定番構成を Terraform でまとめました。
次回は、前回の記事の在庫管理システムを、このカタログと同じスプレッドシートを商品マスタにして作り直した話を書く予定です。




