Astro 5.0 Server Islands でリアルタイムデータを部分的に更新する実装パターン【2026年5月最新】
Astro 5.0 Server Islands と Deferred Content API を使い、ユーザーデータ・在庫情報・天気などリアルタイムデータを部分的に更新する実装パターンを解説。静的コンテンツと動的データの最適な組み合わせ方を具体例で紹介します。
はじめに:静的サイトの弱点を Server Islands で克服する
Astro は静的サイト生成(SSG)に強いフレームワークですが、ユーザー固有情報・在庫数・天気予報などページ表示時にしか確定しないデータを扱うには従来 SSR(Server-Side Rendering)が必要でした。
しかし 2024年11月リリースの Astro 5.0 で導入された Server Islands により、ページの大部分は静的HTML、一部のコンポーネントだけサーバーサイドレンダリングという新しいハイブリッド構成が可能になりました。
本記事では、Server Islands を使ったリアルタイムデータの部分更新パターンを以下のユースケース別に解説します:
- ユーザー認証情報の表示(ログイン状態・ユーザー名)
- ECサイトの在庫数・価格表示
- 外部API連携(天気・為替レート・株価)
- データベースから取得する動的コンテンツ
既存の Server Islands 解説記事ではアーキテクチャ概要に留まっているものが多いため、本記事では具体的な実装コードとパフォーマンス最適化のベストプラクティスに焦点を当てます。
Server Islands とは?従来の SSR との違い
アーキテクチャ比較図
graph TD
A[ユーザーリクエスト] --> B{レンダリング方式}
B -->|従来のSSG| C[完全静的HTML<br/>動的データなし]
B -->|従来のSSR| D[すべてサーバーで生成<br/>遅い初期表示]
B -->|Server Islands| E[静的HTML + 島状の動的部分]
E --> F[静的部分は即座に表示]
E --> G[Server Islands は非同期読み込み]
G --> H[動的データ取得完了後に挿入]
従来の選択肢の課題
完全SSG(Static Site Generation):
- メリット: 高速、CDN配信可能
- デメリット: ビルド時にデータが確定している必要がある。ユーザー固有情報を扱えない
完全SSR(Server-Side Rendering):
- メリット: 常に最新データを表示
- デメリット: すべてのリクエストでサーバー処理が走るため遅く、コストが高い
Server Islands の利点
Astro 5.0 の Server Islands は、ページの大部分を静的HTML として即座に配信し、動的データが必要な部分だけサーバーで非同期レンダリングします。
- TTFB(Time To First Byte)が速い: 静的部分はCDNからミリ秒で配信
- 動的データは必要な部分だけ: 在庫数やユーザー名など、本当に動的である必要がある箇所のみサーバーレンダリング
- 段階的な表示: 静的コンテンツを先に表示し、動的部分はスケルトン表示後に埋め込み
実装パターン1:ユーザー認証情報の表示
最も一般的なユースケースは、ログイン状態に応じたナビゲーション表示です。
コード例:ログイン状態に応じたヘッダー
src/components/UserNav.astro(Server Island コンポーネント)
---
// このコンポーネントは Server Island としてマークする
export const server = {
island: true,
fallback: "blocking" // 表示されるまでスケルトンを表示
};
// サーバーサイドでのみ実行される認証チェック
import { getSession } from '../lib/auth';
const session = await getSession(Astro.request);
const user = session?.user;
---
<nav class="user-nav">
{user ? (
<div class="logged-in">
<span>こんにちは、{user.name}さん</span>
<a href="/logout">ログアウト</a>
</div>
) : (
<div class="logged-out">
<a href="/login">ログイン</a>
<a href="/signup">新規登録</a>
</div>
)}
</nav>
src/pages/index.astro(ページ本体)
---
// このページは静的(SSG)
import UserNav from '../components/UserNav.astro';
---
<html>
<head>
<title>トップページ</title>
</head>
<body>
<header>
<h1>サイト名</h1>
<!-- この部分だけサーバーサイドレンダリング -->
<UserNav />
</header>
<!-- 以下は静的コンテンツ:ビルド時に生成済み -->
<main>
<h2>最新記事</h2>
<!-- 記事一覧など -->
</main>
</body>
</html>
ポイント
export const server = { island: true }で Server Island として宣言fallback: "blocking"により、データ取得中はスケルトンを表示(ユーザー体験向上)- ページ本体(index.astro)は静的生成され、UserNav コンポーネントだけサーバーで動的レンダリング
実装パターン2:ECサイトの在庫数・価格表示
ECサイトでは、商品詳細ページは静的に生成しつつ、在庫数と価格だけリアルタイムで取得したいケースが多くあります。
コード例:在庫情報の動的表示
src/components/ProductStock.astro(Server Island)
---
export const server = { island: true };
interface Props {
productId: string;
}
const { productId } = Astro.props;
// データベースから在庫情報を取得
import { db } from '../lib/db';
const product = await db.query(
'SELECT stock, price FROM products WHERE id = ?',
[productId]
);
---
<div class="product-stock">
<p class="price">¥{product.price.toLocaleString()}</p>
{product.stock > 0 ? (
<p class="in-stock">在庫あり(残り{product.stock}点)</p>
) : (
<p class="out-of-stock">在庫切れ</p>
)}
</div>
<style>
.out-of-stock {
color: red;
font-weight: bold;
}
.in-stock {
color: green;
}
</style>
src/pages/products/[id].astro(商品詳細ページ)
---
// このページは静的生成(SSG)
import ProductStock from '../../components/ProductStock.astro';
// ビルド時に全商品のパスを生成
export async function getStaticPaths() {
const products = await fetch('https://api.example.com/products').then(r => r.json());
return products.map(product => ({
params: { id: product.id },
props: { product }
}));
}
const { product } = Astro.props;
---
<html>
<body>
<h1>{product.name}</h1>
<img src={product.image} alt={product.name} />
<p>{product.description}</p>
<!-- この部分だけ動的に在庫情報を取得 -->
<ProductStock productId={product.id} />
<button>カートに追加</button>
</body>
</html>
パフォーマンス最適化
在庫情報の取得は高頻度で発生するため、以下の最適化を推奨します:
// src/lib/db.ts
import { Cache } from '@astro/cache';
const stockCache = new Cache({
ttl: 60 * 1000 // 60秒キャッシュ
});
export async function getProductStock(productId: string) {
const cached = await stockCache.get(productId);
if (cached) return cached;
const stock = await db.query('SELECT stock, price FROM products WHERE id = ?', [productId]);
await stockCache.set(productId, stock);
return stock;
}
実装パターン3:外部API連携(天気・為替レート)
外部APIからリアルタイムデータを取得する場合も Server Islands が有効です。
コード例:現在地の天気情報を表示
src/components/WeatherWidget.astro(Server Island)
---
export const server = { island: true };
interface Props {
city: string;
}
const { city } = Astro.props;
// 環境変数から API キーを取得
const apiKey = import.meta.env.WEATHER_API_KEY;
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric&lang=ja`
);
const weather = await response.json();
---
<div class="weather-widget">
<h3>{city}の天気</h3>
<p class="temp">{Math.round(weather.main.temp)}°C</p>
<p class="description">{weather.weather[0].description}</p>
<img
src={`https://openweathermap.org/img/wn/${weather.weather[0].icon}@2x.png`}
alt={weather.weather[0].description}
/>
</div>
エラーハンドリングとフォールバック
外部APIは失敗する可能性があるため、適切なエラーハンドリングが必要です:
---
export const server = { island: true };
const { city } = Astro.props;
let weather;
let error = false;
try {
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${import.meta.env.WEATHER_API_KEY}&units=metric&lang=ja`,
{ timeout: 5000 } // 5秒でタイムアウト
);
weather = await response.json();
} catch (e) {
console.error('Weather API error:', e);
error = true;
}
---
{error ? (
<div class="weather-error">
<p>天気情報を取得できませんでした</p>
</div>
) : (
<div class="weather-widget">
<!-- 天気情報を表示 -->
</div>
)}
実装パターン4:Deferred Content API との組み合わせ
Astro 5.0 では、Server Islands と併用できる Deferred Content API も導入されました。これにより、初期表示後に段階的にコンテンツを読み込むことが可能です。
処理フロー図
sequenceDiagram
participant User as ユーザー
participant CDN as CDN
participant Server as Astro Server
participant DB as データベース
User->>CDN: ページリクエスト
CDN->>User: 静的HTML即座に返却
Note over User: メインコンテンツ表示<br/>スケルトン表示
User->>Server: Server Island リクエスト
Server->>DB: データ取得
DB->>Server: データ返却
Server->>User: 動的HTML挿入
Note over User: リアルタイムデータ表示完了
コード例:記事のコメント欄を遅延読み込み
src/components/Comments.astro(Deferred Server Island)
---
export const server = {
island: true,
defer: true // 遅延読み込み
};
interface Props {
articleId: string;
}
const { articleId } = Astro.props;
// データベースからコメントを取得
import { db } from '../lib/db';
const comments = await db.query(
'SELECT * FROM comments WHERE article_id = ? ORDER BY created_at DESC LIMIT 50',
[articleId]
);
---
<section class="comments">
<h3>コメント({comments.length}件)</h3>
{comments.map(comment => (
<article class="comment">
<p class="author">{comment.author}</p>
<p class="body">{comment.body}</p>
<time>{new Date(comment.created_at).toLocaleDateString('ja-JP')}</time>
</article>
))}
</section>
src/pages/articles/[slug].astro
---
import Comments from '../../components/Comments.astro';
const { slug } = Astro.params;
const article = await getArticle(slug);
---
<html>
<body>
<article>
<h1>{article.title}</h1>
<div>{article.content}</div>
</article>
<!-- コメント欄は遅延読み込み:ユーザーが記事を読んでいる間に取得 -->
<Comments articleId={article.id} />
</body>
</html>
defer: true の動作
- ページの初期HTMLには含まれず、JavaScriptで非同期リクエスト
- ユーザーが記事本文を読んでいる間にバックグラウンドで取得
- Intersection Observer により、画面に近づいたタイミングで取得開始も可能
パフォーマンスベストプラクティス
1. Server Islands の数を最小限にする
Server Islands が多すぎると、複数のサーバーリクエストが発生し遅くなります。以下の基準で判断してください:
- ✅ 使う: ユーザー固有情報(ログイン状態、カート内容)
- ✅ 使う: リアルタイム性が必要なデータ(在庫、価格、天気)
- ❌ 使わない: ビルド時に確定するデータ(記事本文、商品説明)
- ❌ 使わない: クライアントサイドで取得できるデータ(いいね数、閲覧数)
2. キャッシュ戦略を組み合わせる
// src/middleware.ts
export async function onRequest({ request, locals }, next) {
// Server Islands のレスポンスに Cache-Control ヘッダーを設定
const response = await next();
if (request.url.includes('/__islands/')) {
response.headers.set('Cache-Control', 's-maxage=60, stale-while-revalidate=300');
}
return response;
}
3. スケルトンUIで体感速度を向上
---
export const server = {
island: true,
fallback: "skeleton" // スケルトン表示
};
---
<div class="product-stock">
<!-- データ取得中はスケルトンが表示される -->
<p class="price skeleton"></p>
<p class="stock skeleton"></p>
</div>
<style>
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>
まとめ
Astro 5.0 の Server Islands は、静的サイトの速さと動的コンテンツの柔軟性を両立する革新的な機能です。
主要な実装パターン
- ユーザー認証情報: ログイン状態・ユーザー名をヘッダーに表示
- 在庫・価格情報: ECサイトでリアルタイムデータを部分表示
- 外部API連携: 天気・為替レートなど外部データの埋め込み
- Deferred Content: コメント欄などを遅延読み込み
パフォーマンス最適化のポイント
- Server Islands の数を最小限にする
- データベースクエリにキャッシュを適用
- スケルトンUIで体感速度を向上
defer: trueで重要度の低いコンテンツを遅延読み込み
次のステップ
- Astro DB と Server Islands の組み合わせでフルスタックアプリを構築
- Cloudflare Workers や Vercel Edge Functions と統合してグローバル配信
- View Transitions API と組み合わせて SPA ライクな体験を実現