概要
これまで静的サイトはだいたい Next.js × Vercel で作っていた。
ただ、カウカウ をリリースして以降、このサービスだけで Vercel の無料枠を使い切り、他サイトのデプロイに影響が出るようになっていた。
そこで、一部の静的サイトを Astro × Cloudflare Pages に移行したので、その内容をまとめる。
ついでに Astro を初めて触ってみたが、設計思想もなかなか面白かったのでその点も紹介する。
PR
Astroとは?
Astro は静的サイト構築に適した Web フレームワーク。
Astro の流儀で開発すると、ビルド後は JavaScript を極力含まないシンプルな HTML になるのが特徴。
また今回は使っていないが、Astro は Islands architecture を採用しており、Astro の中にそのまま React、Preact、Vue、Svelte などのコンポーネントを import して使える。
必要な箇所だけ動的 UI を載せたい場合は、この仕組みを使って実装できる。
Astroでのコンポーネントの書き方
Vue の SFC に近い書き方で、1ファイルの中にスクリプト、HTML、CSS をまとめて表現できる。
たとえば以下のような形。
---
const title = 'Hello Astro';
---
<h1>{title}</h1>
<style>
h1 {
color: red;
}
</style>
実装
Next.js → Astroへの移行
こういう移行作業は AI の得意分野なので、Claude Code に依頼して修正してもらった。
コンポーネントの差分は以下の通り。
■ (Before)src/components/organisms/Top/News.tsx
import React from "react"; import { FeedItem } from "@/lib/feed"; import dayjs from "dayjs"; type Props = { feedItems: FeedItem[]; }; const News: React.FC<Props> = (props) => ( <div> <h1 className="text-2xl mb-7">Tech blog</h1> <div> <div className="my-2"> <span className="bg-gray-300 py-1 px-2 text-xs font-bold">Hatena</span> </div> <div className="font-light"> {props.feedItems .filter((v) => v.type === "hatena") .slice(0, 3) .map((v) => ( <div key={v.title} className="mb-5"> <div>{dayjs(v.createdAt).format("YYYY-MM-DD")}</div> <a href={v.url} target="_blank" rel="noopener noreferrer"> {v.title} </a> </div> ))} <div className="pt-3 pb-8"> <div className="text-xs"> <a href="https://www.wheatandcat.me/" target="_blank" rel="noopener noreferrer" > もっと読む </a> </div> </div> </div> <div className="my-2"> <span className="bg-gray-300 py-1 px-2 text-xs font-bold">Zenn</span> </div> <div className="font-light"> {props.feedItems .filter((v) => v.type === "zenn") .map((v) => ( <div key={v.title} className="mb-5"> <div>{dayjs(v.createdAt).format("YYYY-MM-DD")}</div> <div> <a href={v.url} target="_blank" rel="noopener noreferrer"> {v.title} </a> </div> </div> ))} <div className="pt-3 pb-8"> <div className="text-xs"> <a href="https://zenn.dev/wheatandcat" target="_blank" rel="noopener noreferrer" > もっと読む </a> </div> </div> </div> </div> </div> ); export default News;
■ (After)src/components/organisms/Top/News.astro
---
import type { FeedItem } from '@/lib/feed'
import dayjs from 'dayjs'
interface Props {
feedItems: FeedItem[]
}
const { feedItems } = Astro.props
const hatenaItems = feedItems.filter((v) => v.type === 'hatena').slice(0, 3)
const zennItems = feedItems.filter((v) => v.type === 'zenn')
---
<div>
<h1 class="text-2xl mb-7">Tech blog</h1>
<div>
<div class="my-2">
<span class="bg-gray-300 py-1 px-2 text-xs font-bold">Hatena</span>
</div>
<div class="font-light">
{hatenaItems.map((v) => (
<div class="mb-5">
<div>{dayjs(v.createdAt).format('YYYY-MM-DD')}</div>
<a href={v.url} target="_blank" rel="noopener noreferrer">
{v.title}
</a>
</div>
))}
<div class="pt-3 pb-8">
<div class="text-xs">
<a href="https://www.wheatandcat.me/" target="_blank" rel="noopener noreferrer">
もっと読む
</a>
</div>
</div>
</div>
<div class="my-2">
<span class="bg-gray-300 py-1 px-2 text-xs font-bold">Zenn</span>
</div>
<div class="font-light">
{zennItems.map((v) => (
<div class="mb-5">
<div>{dayjs(v.createdAt).format('YYYY-MM-DD')}</div>
<div>
<a href={v.url} target="_blank" rel="noopener noreferrer">
{v.title}
</a>
</div>
</div>
))}
<div class="pt-3 pb-8">
<div class="text-xs">
<a href="https://zenn.dev/wheatandcat" target="_blank" rel="noopener noreferrer">
もっと読む
</a>
</div>
</div>
</div>
</div>
</div>
React コンポーネントと比べると、Astro 側は Astro.props から受け取り、テンプレート構文もかなり素直。
静的サイト用途なら、むしろこちらのほうがシンプルで読みやすいと感じた。
Vercel → Cloudflare Pagesへの移行
Astro はビルドすると静的な HTML 一式になるため、Cloudflare Pages へのデプロイもかなり簡単。
Cloudflare で API Token を作成し、以下の GitHub Actions を用意すれば、main ブランチへの push で自動デプロイできる。
■ .github/workflows/deploy_website.yml
name: deploy website on: workflow_dispatch: inputs: ref: description: branch|tag|SHA to checkout default: "main" required: true schedule: - cron: "0 2 * * *" push: branches: - main jobs: deploy: runs-on: ubuntu-latest permissions: contents: read deployments: write steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.ref || github.ref }} - uses: actions/setup-node@v4 with: node-version: 20 cache: "npm" - run: npm ci - run: npm run build - uses: cloudflare/pages-action@v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} projectName: unicorn-website directory: dist gitHubToken: ${{ secrets.GITHUB_TOKEN }} branch: main
あとは移行先のサイトに既存ドメインを紐づければ完了。
以下の URL でアクセスできるようになり、移行は無事完了した。

まとめ
- 静的サイト用途なら、Astro × Cloudflare Pages はかなり扱いやすい
- Next.js × Vercel は便利だが、静的サイトまで全部そこに載せると無料枠の圧迫が起きやすい
- Astro は「静的であること」を前提にした設計が分かりやすく、初めて触ってもかなり面白かった
- 今後も静的サイトはこの組み合わせを使っていこうと思った