開発・個人開発

静的サイトに動的機能を足す設計(JAMstack)アーキテクチャの考え方

静的サイトに動的機能を足す設計(JAMstack)アーキテクチャの考え方

この記事の要点

JAMstackは静的サイト(Markup)にAPIとJavaScriptを組み合わせて動的機能を実現するアーキテクチャだ。Astro + Vercel + SupabaseのスタックでJAMstack的な設計をどう実現するかを解説する。

結論

JAMstack的な設計の基本は、「静的なHTMLをCDNで配信」+「動的なデータはクライアントサイドのAPIで処理」という分離だ。Astro + Vercel + Supabaseのスタックでは、アクセスカウント・問い合わせフォーム・認証などをSupabase APIで実現し、ページ自体は静的に保つことで速度とセキュリティを両立できる。

JAMstackの基本概念

JAMstackはJavaScript・API・Markupの3つを組み合わせたアーキテクチャの名称だ。

要素役割技術例
JavaScriptクライアントサイドの動的処理Vanilla JS・React・Astroのscript
APIサーバーサイドの処理Supabase・Vercel Functions・Edge Functions
Markup静的なHTMLAstroでビルド生成

従来のWordPressのようなアーキテクチャは、ページリクエストのたびにPHPサーバーがDBを参照してHTMLを生成する。JAMstackはHTMLをあらかじめ生成してCDNに置き、動的な部分だけをAPIで補完する。

主なメリット

  • 表示速度:HTMLがCDNから直接配信されるため高速
  • セキュリティ:サーバーサイドのアタックサーフェスが小さい
  • コスト:静的ファイルのホスティングは安い
  • スケーラビリティ:CDNがトラフィックを吸収する

Astroでの静的・動的の使い分け

Astroでは、ページの種類によって静的生成とクライアントサイドAPIの使い分けを設計できる。

静的生成が適しているもの

  • 記事ページ(内容が変わらない)
  • 記事一覧ページ(新記事追加時はリビルド)
  • プロフィール・Aboutページ
  • 利用規約・プライバシーポリシー

クライアントサイドAPIが必要なもの

  • アクセスカウンター(ページ表示のたびに更新)
  • 問い合わせフォームの送信
  • ページごとの人気記事ランキング
  • ユーザー認証が必要な機能

機能別の実装パターン

パターン1:アクセスカウンター

ページが表示されたとき、JavaScriptからSupabase APIを呼び出してカウントを更新する。

<!-- src/components/ViewCounter.astro -->
<span id="view-count" data-slug={slug}></span>

<script>
  import { supabase } from '../lib/supabase';
  
  const el = document.getElementById('view-count');
  const slug = el.dataset.slug;
  
  // カウントアップ
  await supabase.rpc('increment_view', { page_slug: slug });
  
  // 現在のカウントを取得して表示
  const { data } = await supabase
    .from('page_views')
    .select('count')
    .eq('slug', slug)
    .single();
  
  el.textContent = `${data?.count ?? 0} views`;
</script>

パターン2:フォーム送信

問い合わせフォームは、静的なHTMLとJavaScriptのfetchを組み合わせる。送信処理はSupabase Edge Functionsで行う。

<!-- フォーム -->
<form id="contact-form">
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">送信</button>
</form>

<script>
  document.getElementById('contact-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const data = Object.fromEntries(new FormData(e.target));
    
    await fetch('/api/contact', {
      method: 'POST',
      body: JSON.stringify(data),
    });
  });
</script>

パターン3:認証が必要な管理ページ

Supabase Authを使った認証を、クライアントサイドで実装する。

import { supabase } from '../lib/supabase';

// セッションを確認
const { data: { session } } = await supabase.auth.getSession();

if (!session) {
  // 未認証の場合はログインページにリダイレクト
  window.location.href = '/login';
}

ページ自体は静的に生成されるが、表示時に認証状態を確認して未認証ならリダイレクトする。

ビルド時データフェッチ vs. クライアントサイドフェッチ

Astroでは、ビルド時にデータを取得して静的ページに組み込む方法と、ページ表示後にクライアントサイドで取得する方法の両方が使える。

ビルド時フェッチ(静的)

---
// ビルド時に実行される(サーバーサイド)
const { data: topArticles } = await supabase
  .from('page_views')
  .select('slug, count')
  .order('count', { ascending: false })
  .limit(10);
---

<ul>
  {topArticles.map(a => <li><a href={`/articles/${a.slug}/`}>{a.slug}</a></li>)}
</ul>

メリット:SEOに効く(HTMLにデータが含まれる)、表示が速い
デメリット:リビルドしないとデータが更新されない

クライアントサイドフェッチ(動的)

<ul id="top-articles"></ul>

<script>
  import { supabase } from '../lib/supabase';
  
  const { data } = await supabase
    .from('page_views')
    .select('slug, count')
    .order('count', { ascending: false })
    .limit(10);
  
  const ul = document.getElementById('top-articles');
  ul.innerHTML = data.map(a => `<li><a href="/articles/${a.slug}/">${a.slug}</a></li>`).join('');
</script>

メリット:常に最新データが表示される
デメリット:JavaScript実行後にコンテンツが表示される(CLS・SEOの懸念)

リアルタイム性が必要なデータ(アクセスカウント・問い合わせ数)はクライアントサイドフェッチ、SEOが重要なコンテンツはビルド時フェッチと使い分けるのが基本的な設計だ。

Vercel Edge Functionsによるサーバーサイド処理

Astroに@astrojs/vercelアダプターを追加することで、特定のページやAPIルートをVercel Edge Functionsで処理できる。

// src/pages/api/contact.js
export const POST = async ({ request }) => {
  const { name, email, message } = await request.json();
  
  // サーバーサイドでのみ実行される処理
  // APIキーをクライアントに見せずに使える
  const result = await sendEmail({ name, email, message });
  
  return new Response(JSON.stringify({ success: true }), {
    headers: { 'Content-Type': 'application/json' },
  });
};

APIキー等の機密情報を使う処理を、Edge Functions側に閉じ込めることでセキュリティが向上する。

Supabaseの基本セットアップはSupabaseの始め方を、アクセスカウンターの詳細はSupabaseでアクセス数カウンターを作るで解説している。

まとめ

JAMstackの設計原則は「静的なコンテンツはビルドして高速配信、動的なデータはAPIで」だ。Astro + Supabaseのスタックではこれを実現しやすく、アクセスカウント・フォーム送信・管理者認証をクライアントサイドのAPI呼び出しで実現しつつ、記事ページは静的HTMLとして高速に配信できる。どのデータをビルド時に取得してどのデータをクライアントサイドで取得するかを設計の段階で決めておくと、SEOと動的機能のバランスを取りやすい。

よくある質問

JAMstackとは何ですか

JavaScript・API・Markup(HTML)を組み合わせたウェブアーキテクチャの考え方です。静的なHTMLをCDNで高速配信しながら、動的なデータ処理はAPIとJavaScriptで補完します。WordPressのようなサーバーサイドレンダリングと対比される概念です。

静的サイトに動的機能を追加するとはどういう意味ですか

静的サイトはビルド時にHTMLを生成してCDNで配信します。ページが表示された後、JavaScriptからAPIを呼び出してデータを取得・更新する「クライアントサイドのAPI通信」を追加することで、フォーム送信・アクセスカウント・認証などの動的な機能を実現できます。

Astroは静的サイトだけに使えますか

Astroはサーバーサイドレンダリング(SSR)もサポートしており、静的生成とSSRを混在させることもできます。しかしコンテンツ中心のメディアサイトでは、静的生成+クライアントサイドAPI通信のJAMstack的なアプローチが速度とコストの点で優れています。

JAMstackの限界は何ですか

リアルタイムでデータが変わるページ(在庫数・ライブ配信)やユーザーごとに完全に異なるコンテンツは、静的生成では対応しにくいです。また、認証が必要なページを静的生成することは可能ですが、クライアントサイドの認証処理が複雑になります。