このサイトについて

このサイトは、作品を載せるためのポートフォリオであり、同時に「設計から運用まで一人で回す」練習台でもある。 構成、技術を選んだ理由、運用の工夫、つまずいた点を、作品ページと同じ型でまとめる。 ブログは別のサイトにあり、トップページに新着を出している。

目的

構成

本番の構成図。閲覧者は Route 53 で名前を引き、CloudFront に HTTPS でつなぐ。CloudFront の証明書は us-east-1 の ACM にある。CloudFront の Function は、www と以前の URL を apex へ、以前の /blog/ をブログへ 301 で転送し、/works/ などを index.html に書き換える。静的ファイルは東京リージョンの S3(OAC)から返す。/api/* はキャッシュせず API Gateway の HTTP API に送り、ルートごとに throttle をかけて、SnapStart の Lambda(Spring Boot、arm64、live エイリアス)を呼ぶ。Lambda は X-Origin-Verify ヘッダーが違えば 403 で断り、起動時に SSM パラメータストアの値を読み、Aurora DSQL に IAM 認証で接続する。ほかに、デプロイ時に記事を upsert するマイグレーションの Lambda、EventBridge Scheduler が毎日呼ぶゲームの管理の Lambda がある。CloudWatch のアラームは API と Lambda を見て、SNS に通知する
フロントエンド
Next.js(静的 export)、TypeScript、CSS Modules。記事の Markdown は unified(remark / rehype)で HTML にし、Marp のスライドも表示する
バックエンド
Java 25、Spring Boot 4、Maven。AWS Lambda(arm64、SnapStart)で動かし、API Gateway(HTTP API)から呼ぶ
データベース
Aurora DSQL。IAM 認証で接続し、スキーマは Flyway で管理する
配信
独自ドメイン(Route 53、証明書は us-east-1 の ACM)。CloudFront 1 つで静的サイトと /api/* を同じオリジンに見せ、 セキュリティヘッダーと CSP を付ける。www と以前の URL は apex へ、以前の /blog/ はブログへ 301 で転送する
IaC・CI/CD
AWS CDK(TypeScript)。GitHub Actions から OIDC で AWS のロールを引き受け、長期のアクセスキーを持たない
テスト
backend は Spock と Testcontainers(PostgreSQL)、frontend は node:test、infra は Jest で CloudFormation のテンプレートを検査する

配信の流れ

テストは PR の CI で回し、main にマージすると Deploy が CDK でデプロイする(PR の CI が通っていなければ、Deploy でもテストを通す)。 マイグレーションが先に記事を DB に入れ、その後に frontend をビルドして S3 に置く。 ブログの新着を取り込むため、frontend だけは毎日ビルドし直す。

配信の流れの図。PR では CI が gitleaks と、変わった所のテストを 1 つのジョブで動かす。main にマージすると Deploy が動き、PR の CI が成功していれば backend のテストを省く。Deploy と、毎日 04:00 の Rebuild site は、OIDC で AWS のデプロイ用の IAM ロールを引き受ける。Deploy は CDK で Dns、Certificate(us-east-1)、Api、StaticSite のスタックをデプロイし、先にマイグレーションの Lambda が記事を Aurora DSQL に upsert して、その後に API の Lambda の新しいバージョンを作る。そのあとビルドした HTML を S3 に sync し、CloudFront のキャッシュを無効化する。Sync shared to blog は共有部品をブログへ同期の PR で送り、ブログの新着は Rebuild site がフィードから読む

設計の判断

静的 export とビルド時のデータ取得

記事は DB にあるが、ページはビルド時に API から取得して静的な HTML にする。 閲覧は S3 と CloudFront だけで済み、ページを見るときに API(Lambda)は呼ばれない。 記事を main に入れると、push でデプロイが走ってページを作り直す。

Spring Boot on Lambda(SnapStart)

常時起動のサーバーを持たずに Java を使いたかった。SnapStart は公開したバージョンにだけ効くので、 API Gateway は $LATEST ではなくエイリアスを呼ぶ。arm64 にして x86 より安く動かしている。

Aurora DSQL

サーバーレスで、使っていない時間の費用がほぼかからない。PostgreSQL 互換だが制約も多い (DDL と DML を同じトランザクションに混ぜられない、インデックスは CREATE INDEX ASYNC、 1 トランザクションで変更できる行数に上限がある、など)ので、 SQL は DSQL と PostgreSQL の両方で同じに書き、テストは Testcontainers の PostgreSQL で回す。

記事はリポジトリの Markdown から

作品の記事は content/ に Markdown で置き、PR でレビューしてから入れる。 デプロイ時に migration Lambda が S3 のアセットから読み、DB に upsert する。 リポジトリから消えた記事は削除せず非公開にする。 記事が少ないうちは「公開中の記事が 0 件になる変更」をデプロイ側で止める安全装置も入れた。

ブログは別のサイトに

ブログは最初このサイトに載せていたが、別のサイトに移した。 記事を書くたびにこのサイトの API と DB を通す必要は無く、静的サイトだけで済むため。 API が返すのは作品だけにし、DB に入っていたブログの記事は削除せず非公開で残している。 トップページの「ブログの新着」は、ビルド時にブログのフィードを読んで作る (取れないときは区画を出さず、ビルドは止めない)。以前の /blog/ と /feed.xml の URL は、 CloudFront でブログへ 301 で転送する。

閲覧者の情報を持たない

Cookie もアクセス解析も使わず、データベースには記事しか置かない。 CSP のレポート先も置かず、閲覧者の情報が外へ出る経路を作らない。

つまずいた点

結果

ソースコードは今のところ非公開だが、作り方の記録はブログ ↗に書いていく。