turushihara.workの技術スタック
このブログはastroとtailwindをベースに、Cloudflareのエッジインフラを活用して構築されています。 ドキュメントを外部サービスに置いたりするのは管理やセキュリティが難しいと感じたのでプライベートなCMSとしてこのサイトを構築しました。 これでドキュメントを書かない言い訳が一つ減りました! 以下では、使用している技術スタックとその選定理由について詳しく解説します。
技術選定の背景
このブログは以下の要件を満たすために設計されました:
- 運用コスト: サーバーレスアーキテクチャによる低コスト運用
- SEO対応: 静的サイト生成による検索エンジン最適化
- 管理機能: 記事の投稿・編集・公開制御
- セキュリティ: CloudflareによるDDoS対策、アクセス制限、token認証
フロントエンド技術
Astro 5.12.9
静的サイト生成フレームワークのAstroを採用。以下の特徴があります:
- SEO最適化: 静的HTMLの生成により、検索エンジンに優しい構造
- セキュリティ: ローカルでのビルドとデプロイにより、サーバーサイドの脆弱性を排除
TailwindCSS 4.1.11
ユーティリティファーストのCSSフレームワーク:
- 高速開発: プリセットクラスによる迅速なスタイリング
- デザインシステム: 一貫したデザインの維持
- 最適化: 未使用CSSの自動削除によるファイルサイズ最小化
Cloudflareエッジスタック
Cloudflare Workers
サーバーレス実行環境で以下を実現:
- エッジ実行: 世界200以上の都市で処理を実行
- 低レイテンシ: ユーザーに最も近い場所での処理
- APIエンドポイント: 記事CRUD操作、画像アップロード
- 低コスト: 静的コンテンツの配信に最適化された料金体系
Cloudflare R2
オブジェクトストレージ:
- 低コスト: egress料金なしの費用効率
Cloudflare D1
SQLiteベースの分散データベース:
- 記事メタデータ: タイトル、作成日、タグ情報
- 公開制御: 記事の公開・非公開状態管理
コンテンツ管理
処理パイプライン
- remark: Markdownの解析
- rehype: HTMLへの変換と拡張
- Shiki: シンタックスハイライト
- Mermaid: 図表・ダイアグラムの描画
開発・ビルド環境
TypeScript
TypeScriptの一貫した言語採用によりfront/backのコンテキストスイッチを低減
デプロイメントフロー
このブログは管理画面での記事作成からデプロイまでの流れが整備されています:
記事投稿からデプロイまでの詳細手順
- 記事作成: 管理画面で記事のタイトル、タグ、本文(Markdown)を入力
- メタデータ保存: D1データベースに記事情報を保存
- ローカルSSG:
npm run build
でAstroが全記事を静的HTMLに変換 - デプロイ:
npm run deploy
でCloudflare Workersに配信
Cloudflareを活用したセキュリティ対策
APIエンドポイントの保護
Zero Trust > Access > サービス認証で利用できるサービストークンを用いてtoken認証を実装しています。 まずCloudflare Accessの概念として、以下のような感じになってます。
- アプリケーション: DNSやポリシーを組みわせて最終的にどういう挙動にするかコントロールする機能
- ポリシー: アクセスに対してどのような制限を行うかを定義する機能
- サービス認証: ポリシーで利用できるアクセス制限手段の一つ
サービス認証からトークンを生成し、ポリシーを先ほど作ったServiceTokenを設定して作成。さらにアプリケーションを作成から制限対象のエンドポイントを設定し、ポリシーをアタッチして完了という感じになってます。
実装としては以下のように、APIリクエストのヘッダーにトークンを追加することで、Cloudflare Accessを通じて認証を行います。
当サイトではCloudflare Workersをエンドポイントとして設定しているので、Workersのエントリー実行前に認証が行われるイメージです。
また、Workersから保護されたAPIエンドポイントを呼び出す場合には、Cloudflareの環境変数にトークンを設定しておく必要があります。
環境変数はCLIからnpx wrangler secret put
で設定するのが楽です。
const HEADERS = {
'CF-Access-Client-Id': import.meta.env.CF_ACCESS_CLIENT_ID,
'CF-Access-Client-Secret': import.meta.env.CF_ACCESS_CLIENT_SECRET,
};
const response = await fetch(
`${HOST}/any/private/endpoint`,
{
headers: HEADERS,
},
);
こちらの記事が詳しく説明されています→https://dev.classmethod.jp/articles/use-service-tokens-to-authenticate-cloudflare-access-from-my-application/
まとめ
技術選定では、最新技術の採用と実用性のバランスを重視し、長期間の運用に耐えうる安定した構成を心がけました。