Astro Database で CMS なしにリアルタイムデータを管理する実装ガイド【2026年新機能】
Astro DB の型安全なスキーマ定義からリアルタイムデータ更新まで、CMS なしで動的コンテンツを管理する実装方法を実例付きで解説。
従来の静的サイトジェネレーター(SSG)では、動的なデータ管理にはヘッドレス CMS やマイクロサービスを組み合わせる必要がありました。しかし Astro の公式データベースソリューション「Astro DB」の登場により、CMS なしでリアルタイムにデータを管理し、ビルド時・実行時の両方で活用できる環境が整いました。
本記事では、Astro DB の型安全なスキーマ定義から、ローカル開発環境でのデータ操作、本番環境へのデプロイとリアルタイムデータ更新の実装方法まで、実際のコード例を交えて解説します。
Astro DB とは何か
Astro DB は、Astro 公式が提供する SQL ベースのリレーショナルデータベースです。Turso(libSQL)をベースにしており、以下の特徴があります。
- 型安全なスキーマ定義: TypeScript でテーブル構造を定義し、コード補完とバリデーションが効く
- ローカル開発環境: SQLite ベースのローカルデータベースで、npm スクリプトで即座にセットアップ可能
- Astro Studio との統合: Astro の公式ホスティングプラットフォーム Astro Studio でホストされ、デプロイが簡単
- ビルド時・実行時の両対応: SSG のビルド時にデータを取得しつつ、サーバーサイドレンダリング(SSR)やサーバーアクションでリアルタイムに更新も可能
従来の Headless CMS(Contentful, Strapi など)と比較した場合、Astro DB は以下の点で優位性があります。
| 項目 | Astro DB | Headless CMS |
|---|---|---|
| セットアップ | npm install 1 コマンド | 外部サービス登録・API キー設定が必要 |
| 型安全性 | TypeScript でスキーマ定義、完全な型推論 | API レスポンスは any 型になりがち |
| コスト | 無料枠が大きい(Astro Studio Free: 1GB ストレージ) | 無料枠が限定的 |
| データ移行 | SQL ダンプで容易 | ベンダーロックインのリスク |
ただし、Astro DB は リレーショナルデータ向けであり、リッチテキストエディタや画像管理 UI などは提供されません。編集者向け UI が必要な場合は Tina CMS などと併用する選択肢もあります。
Astro DB のセットアップ手順
プロジェクトへのインストール
Astro DB を既存プロジェクトに追加するには、公式の CLI を使用します。
npx astro add db
このコマンドは以下の処理を自動で実行します。
@astrojs/dbパッケージのインストールastro.config.mjsへのインテグレーション追加db/config.tsとdb/seed.tsの雛形ファイル生成
スキーマ定義
db/config.ts でテーブル構造を定義します。以下は、ブログ記事と著者を管理する例です。
import { defineDb, defineTable, column } from 'astro:db';
const Author = defineTable({
columns: {
id: column.number({ primaryKey: true }),
name: column.text(),
email: column.text({ unique: true }),
createdAt: column.date({ default: new Date() }),
}
});
const Post = defineTable({
columns: {
id: column.number({ primaryKey: true }),
title: column.text(),
slug: column.text({ unique: true }),
content: column.text(),
published: column.boolean({ default: false }),
authorId: column.number({ references: () => Author.columns.id }),
publishedAt: column.date({ optional: true }),
}
});
export default defineDb({
tables: { Author, Post }
});
型定義のポイント:
column.number(),column.text(),column.date(),column.boolean()などの型が用意されているreferences: () => Author.columns.idで外部キー制約を定義unique: true,optional: true,defaultなどのオプションで制約を指定
初期データの投入
db/seed.ts で開発用のシードデータを用意します。
import { db, Author, Post } from 'astro:db';
export default async function seed() {
await db.insert(Author).values([
{ id: 1, name: 'Alice Smith', email: 'alice@example.com' },
{ id: 2, name: 'Bob Johnson', email: 'bob@example.com' },
]);
await db.insert(Post).values([
{
id: 1,
title: 'Getting Started with Astro DB',
slug: 'astro-db-intro',
content: '...',
published: true,
authorId: 1,
publishedAt: new Date('2026-04-15'),
},
{
id: 2,
title: 'Real-time Data with Astro',
slug: 'astro-realtime-data',
content: '...',
published: false,
authorId: 2,
},
]);
}
ローカル環境でシードを実行するには以下のコマンドを使用します。
npx astro db push --seed
このコマンドは、スキーマをローカルの SQLite データベースに反映し、シードデータを投入します。
ビルド時のデータ取得(SSG)
Astro DB は、静的サイト生成時にデータベースからデータを取得し、ページを事前レンダリングできます。
記事一覧ページの例
---
// src/pages/blog/index.astro
import { db, Post, Author, eq } from 'astro:db';
const posts = await db.select()
.from(Post)
.innerJoin(Author, eq(Post.authorId, Author.id))
.where(eq(Post.published, true))
.orderBy(Post.publishedAt, 'desc');
---
<html>
<head>
<title>Blog Posts</title>
</head>
<body>
<h1>Latest Posts</h1>
<ul>
{posts.map(({ Post: post, Author: author }) => (
<li>
<a href={`/blog/${post.slug}`}>{post.title}</a>
<p>By {author.name} on {post.publishedAt?.toLocaleDateString()}</p>
</li>
))}
</ul>
</body>
</html>
ポイント:
db.select().from(Post)で SQL ライクなクエリを記述innerJoin,where,orderByなどの Drizzle ORM ベースの API を使用- TypeScript の型推論が効くため、
post.titleなどのフィールドアクセスに補完が効く
動的ルートでの記事詳細ページ
---
// src/pages/blog/[slug].astro
import { db, Post, Author, eq } from 'astro:db';
export async function getStaticPaths() {
const posts = await db.select().from(Post).where(eq(Post.published, true));
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const [author] = await db.select().from(Author).where(eq(Author.id, post.authorId));
---
<html>
<head>
<title>{post.title}</title>
</head>
<body>
<article>
<h1>{post.title}</h1>
<p>By {author.name}</p>
<div set:html={post.content} />
</article>
</body>
</html>
ビルド時にすべての記事パスが生成され、完全な静的サイトとして配信されます。
リアルタイムデータ更新の実装(SSR + Server Actions)
Astro DB の真価は、SSR モードと組み合わせてリアルタイムにデータを更新できる点にあります。以下は、記事のいいね数をリアルタイムにカウントする実装例です。
スキーマの拡張
// db/config.ts に Like テーブルを追加
const Like = defineTable({
columns: {
id: column.number({ primaryKey: true }),
postId: column.number({ references: () => Post.columns.id }),
count: column.number({ default: 0 }),
}
});
export default defineDb({
tables: { Author, Post, Like }
});
API エンドポイントの作成
// src/pages/api/like.ts
import type { APIRoute } from 'astro';
import { db, Like, eq } from 'astro:db';
export const POST: APIRoute = async ({ request }) => {
const { postId } = await request.json();
const [existing] = await db.select().from(Like).where(eq(Like.postId, postId));
if (existing) {
await db.update(Like)
.set({ count: existing.count + 1 })
.where(eq(Like.postId, postId));
} else {
await db.insert(Like).values({ postId, count: 1 });
}
const [updated] = await db.select().from(Like).where(eq(Like.postId, postId));
return new Response(JSON.stringify({ count: updated.count }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
};
フロントエンドからの呼び出し
---
// src/pages/blog/[slug].astro(一部抜粋)
const { post } = Astro.props;
const [likeData] = await db.select().from(Like).where(eq(Like.postId, post.id));
const initialCount = likeData?.count || 0;
---
<article>
<h1>{post.title}</h1>
<button id="like-btn" data-post-id={post.id}>
👍 <span id="like-count">{initialCount}</span>
</button>
</article>
<script>
document.getElementById('like-btn')?.addEventListener('click', async (e) => {
const postId = (e.currentTarget as HTMLElement).dataset.postId;
const res = await fetch('/api/like', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ postId: Number(postId) })
});
const { count } = await res.json();
document.getElementById('like-count')!.textContent = count;
});
</script>
このように、API エンドポイントを経由してデータベースを更新し、クライアント側で即座に UI に反映できます。
Astro Studio へのデプロイ
本番環境のセットアップ
Astro Studio は、Astro 公式が提供するホスティングプラットフォームで、Astro DB を本番環境で簡単に利用できます。
- Astro Studio アカウント作成: https://studio.astro.build でサインアップ
- プロジェクトのリンク:
npx astro login
npx astro link
- 本番データベースへのプッシュ:
npx astro db push --remote
このコマンドで、ローカルで定義したスキーマが本番環境のデータベースに反映されます。
環境変数の設定
Vercel や Netlify などのホスティングサービスでデプロイする場合、以下の環境変数を設定します。
ASTRO_STUDIO_APP_TOKEN=your_token_here
この token は astro link 実行時に生成され、.env ファイルに自動で記録されます。
デプロイフロー図
flowchart TD
A[ローカル開発] -->|astro db push| B[ローカル SQLite DB]
A -->|astro db push --remote| C[Astro Studio DB]
C -->|環境変数設定| D[Vercel/Netlify]
D -->|ビルド時クエリ| C
D -->|実行時クエリ| C
E[ユーザー] -->|リクエスト| D
D -->|静的/動的レスポンス| E
パフォーマンス最適化とキャッシング戦略
ビルド時データ取得のベストプラクティス
静的生成時にデータベースクエリを実行する場合、以下の点に注意します。
- 必要なカラムのみ取得:
db.select({ title: Post.title, slug: Post.slug })のように明示的に指定 - インデックスの活用: 頻繁に検索されるカラムには
indexed: trueを設定(Astro DB は自動的に primary key と unique にインデックスを作成) - ページネーション: 大量のデータがある場合は
limit()とoffset()を使用
const pageSize = 10;
const page = 1;
const posts = await db.select()
.from(Post)
.where(eq(Post.published, true))
.limit(pageSize)
.offset((page - 1) * pageSize);
リアルタイムデータのキャッシング
API エンドポイントでは、頻繁にアクセスされるデータをキャッシュすることで、データベースへの負荷を軽減できます。
// src/pages/api/post/[id].ts
import type { APIRoute } from 'astro';
import { db, Post, eq } from 'astro:db';
const cache = new Map<number, { data: any; expires: number }>();
export const GET: APIRoute = async ({ params }) => {
const postId = Number(params.id);
const now = Date.now();
if (cache.has(postId) && cache.get(postId)!.expires > now) {
return new Response(JSON.stringify(cache.get(postId)!.data), {
status: 200,
headers: { 'Content-Type': 'application/json', 'X-Cache': 'HIT' }
});
}
const [post] = await db.select().from(Post).where(eq(Post.id, postId));
cache.set(postId, { data: post, expires: now + 60000 }); // 1分キャッシュ
return new Response(JSON.stringify(post), {
status: 200,
headers: { 'Content-Type': 'application/json', 'X-Cache': 'MISS' }
});
};
本番環境では、Cloudflare Workers KV や Redis などの外部キャッシュストアを使用することも検討してください。
まとめ
Astro DB を活用することで、以下のメリットが得られます。
- CMS 不要: 外部サービスに依存せず、コード内で完結したデータ管理が可能
- 型安全性: TypeScript でスキーマを定義し、エディタの補完とバリデーションが効く
- 柔軟性: SSG とリアルタイムデータ更新の両方に対応し、用途に応じて使い分けられる
- 低コスト: Astro Studio の無料枠が充実しており、小規模プロジェクトなら運用コストがほぼゼロ
ただし、以下の点には注意が必要です。
- 編集 UI なし: 非技術者向けの管理画面が必要な場合は別途用意する必要がある
- スケーラビリティ: 大規模トラフィックには Astro Studio の有料プランや別のデータベースへの移行が必要
- バックアップ戦略: 定期的な SQL ダンプとバージョン管理が推奨される
Astro DB は、ブログ・ドキュメントサイト・ポートフォリオなど、中小規模のデータ駆動型サイトに最適なソリューションです。従来の Headless CMS と比較して、セットアップの手軽さと型安全性で大きなアドバンテージがあります。