genko.meDocs

実践

Astroでページ送りを静的に組み立て、下書きプレビューをリクエスト時レンダリングへ切り出し、Webhookで反映する。

導入の一覧・詳細に、運用で必要になるものを足します。Astroには専用のPagination・View部品がないので、いずれも自分で組み立てます。

ページ送り

一覧の総件数はビルド時に分かるので、ページ番号ごとの静的ルートとして書き出せます。getStaticPaths()で必要なページ数を計算し、そのページ分だけHTMLを生成します。

src/pages/blog/page/[page].astro
---import { client } from "../../../lib/genko";
const PER_PAGE = 6;
export async function getStaticPaths() {  const perPage = 6;  const { totalCount } = await client.blog.list({ limit: 1 });  const lastPage = Math.max(1, Math.ceil(totalCount / perPage));  return Array.from({ length: lastPage }, (_, i) => ({ params: { page: String(i + 1) } }));}
const page = Number(Astro.params.page);const { contents, totalCount } = await client.blog.list({  limit: PER_PAGE,  offset: (page - 1) * PER_PAGE,  orders: "-publishedAt",});const lastPage = Math.max(1, Math.ceil(totalCount / PER_PAGE));---<html lang="ja">  <body>    <main>      <h1>ブログ({page} / {lastPage})</h1>      <ul>        {contents.map((post) => <li><a href={`/blog/${post.id}`}>{post.title}</a></li>)}      </ul>      <nav aria-label="ページ送り">        {page > 1 && <a href={`/blog/page/${page - 1}`}>前へ</a>}        {page < lastPage && <a href={`/blog/page/${page + 1}`}>次へ</a>}      </nav>    </main>  </body></html>

getStaticPaths()は独立した実行スコープを持ちます。 関数の外で定義したPER_PAGEをそのまま関数内で参照すると、ビルド時にPER_PAGE is not definedという実行時エラーになります。getStaticPaths()が必要とする値は、関数の中でも定義してください(上の例のperPage)。ページ本体側の描画コードでは、外側のPER_PAGEをそのまま使えます。

このページ送りは前へ・次へのリンクだけで、クリック後の再描画も含めてサーバーが返した静的HTMLです。JavaScriptは1バイトも要りません。総件数が変わるたびにページ数も変わるので、記事を追加・削除したら再ビルドします(後述のWebhookで自動化できます)。

保存済み下書きのプレビュー

下書きのdraftKeyはビルド時には分からないため、このページだけAstroのリクエスト時レンダリング(オンデマンドレンダリング)に切り替えます。まずアダプターを追加します(ここではNode向けの@astrojs/nodeを例にしますが、デプロイ先に応じた別のアダプターでも同じ考え方です)。

bash
npm install @astrojs/node
astro.config.mjs
import { defineConfig } from "astro/config";import react from "@astrojs/react";import node from "@astrojs/node";
export default defineConfig({  integrations: [react()],  adapter: node({ mode: "standalone" }),});

詳細ページの先頭でprerenderをfalseにし、draftKeyをクエリから読みます。

src/pages/blog/[id].astro
---export const prerender = false;
import { client } from "../../lib/genko";import { ShareButtons } from "@genko-me/react/share-buttons";
const { id } = Astro.params;const draftKey = Astro.url.searchParams.get("draftKey") ?? undefined;const post = await client.blog.getOrNull(id!, draftKey ? { draftKey } : undefined);
if (!post) Astro.response.status = 404;---<html lang="ja">  <body>    <main>      {post ? (        <>          <h1>{post.title}</h1>          <div set:html={post.body} />          <ShareButtons client:load url={`https://your-site.example/blog/${post.id}`} title={post.title} />          {draftKey && <p data-preview>プレビュー中</p>}        </>      ) : (        <p>記事が見つかりません。</p>      )}    </main>  </body></html>

[id].astroからgetStaticPaths()を削除したことで、このルート全体(公開済みの記事も含めて)がビルド時ではなくリクエストのたびにレンダリングされるようになります。1ファイル単位の切り替えなので、一覧・ページ送りは前節のまま静的に残せます。JSを送らない点も変わりません——prerenderはサーバーで実行するタイミングの話であり、クライアントへ送るJavaScriptの量とは別の設定です。

管理画面のAPI設定(/w/your-workspace/apis/blog/settings)で、プレビューURLテンプレートをこのページに合わせます。

text
https://your-site.example/blog/{id}?draftKey={key}

APIキーはprocess.envから読みます。import.meta.envではありません。 Astro(v6以降)のimport.meta.envは、ビルド時に値をそのまま埋め込みます。.envに書いた値はビルド時点で固定され、あとからホスティング先の環境変数だけを変えても反映されません。process.envはNode向けアダプターの実行時に毎回読まれるため、デプロイ後にAPIキーを差し替えたい・環境ごとに変えたい場合はこちらを使います(導入のlib/genko.tsも、最初からprocess.envにしてあるのはこのためです)。Cloudflareなどprocess.envを持たないアダプターを使う場合は、そのアダプターのドキュメントに従ってください。

Webhookで再生成・再検証する

このサイトの大部分は静的です。 記事を公開・更新しても、再ビルドするまでは古いHTMLが配信され続けます。管理画面のWebhookの通知先をホスティング先のデプロイフックに設定すると、記事を保存したときに自動で再ビルドが走ります。受信側のコードは書きません。

下書きプレビューのページ(prerender = falseにした[id].astro)は、リクエストごとに配信APIへ問い合わせているので、再ビルドしなくてもその場で最新の下書きが読めます。ここに再検証は要りません。

CDNやリバースプロキシで配信APIの応答自体をキャッシュしている場合だけ、そのキャッシュを破棄する小さなAPIルートを別途用意します。署名の検証には@genko-me/sdkのverifyGenkoSignatureが使えます。

src/pages/api/webhook.ts
export const prerender = false;import type { APIRoute } from "astro";import { verifyGenkoSignature } from "@genko-me/sdk";
export const POST: APIRoute = async ({ request }) => {  const rawBody = await request.text();  const valid = await verifyGenkoSignature({    secret: process.env.GENKO_WEBHOOK_SECRET!,    rawBody,    signatureHeader: request.headers.get("x-genko-signature"),  timestampHeader: request.headers.get("x-genko-timestamp"),  });  if (!valid) return new Response(null, { status: 401 });
  // ここでCDN・リバースプロキシのキャッシュパージAPIを呼ぶ。  return new Response(null, { status: 204 });};

このルートもprerender = falseが要るため、前節のアダプターが必要です。イベントの種類、ペイロード、配送の制限はWebhookにまとめています。

次に読む

最終更新: 2026年10月14日

目次