SvelteKitでヘッドレスCMSを使うには、+page.server.tsのload関数からCMSを呼び出し、表示に必要な記事だけをページへ渡します。公開記事はprerenderで静的生成する構成も選べますが、下書きプレビューにはリクエストを処理できるサーバーが必要です。
本記事はgenko.meの運営側が執筆しています。2026年10月20日時点でSvelteKit 2・Svelte 5を対象にします。現行公式ページの3系とは設定が異なります。
CMS取得はどのloadに書く?
秘密値を使う取得は+page.server.tsに置きます。+page.tsはブラウザでも動く共通loadです。戻り値はブラウザへ渡るため、記事だけを返します。
SvelteKitでブログAPIを接続する準備
SvelteKit 2・Svelte 5を用意します。
genko.meでリスト形式のAPI
blogを作ります。titleをテキスト、bodyをリッチテキストとして追加し、記事を公開します。blogの読み取りを許可したAPIキーを発行します。ビルド環境に
GENKO_API_KEYを設定します。
npm install @genko-me/sdk初期操作はクイックスタートを参照してください。表示部品は導入ガイドの別構成です。
CMSクライアントをサーバー専用にする
// src/lib/server/genko.tsimport { createGenko, listApi } from "@genko-me/sdk";import { GENKO_API_KEY } from "$env/static/private";type Blog = { title: string; body: string };export const client = createGenko({ workspace: "your-workspace", apiKey: GENKO_API_KEY, apis: { blog: listApi<Blog>({ fields: [ { id: "title", type: "text" }, { id: "body", type: "richtext" }, ], }), },}).apis;ワークスペースIDを置き換え、CMSのフィールドID・種類をそろえます。
$lib/serverでブラウザ側への誤importを防ぎます。SSGにも使えるようビルド時のキーを読むため、交換には再ビルドが必要で、サーバー成果物も秘密値として扱います。
SSR専用なら$env/dynamic/privateへ変更できますが、プリレンダリングには使えません。
load関数で記事一覧を表示する
ページはsrc/routes/blog/+page.svelteです。
// src/routes/blog/+page.server.tsimport { client } from "$lib/server/genko";import type { PageServerLoad } from "./$types";export const load: PageServerLoad = async () => ({ posts: await client.blog.getAll({ orders: "-publishedAt", fields: ["id", "title"], }),});<script lang="ts"> import type { PageData } from "./$types"; let { data }: { data: PageData } = $props();</script><svelte:head><title>Blog</title></svelte:head><h1>Blog</h1><ul> {#each data.posts as post (post.id)} <li><a href={`/blog/${post.id}`}>{post.title}</a></li> {/each}</ul>{#if data.posts.length === 0}<p>No published posts.</p>{/if}getAll()は全公開記事を集めます。記事が増えたらlist()のlimit・offsetでページ分割し、本文を一覧へ含めない設計を維持します。
SvelteKitで記事詳細と404を作る
ページはsrc/routes/blog/[id]/+page.svelteです。
// src/routes/blog/[id]/+page.server.tsimport { error } from "@sveltejs/kit";import { client } from "$lib/server/genko";import type { PageServerLoad } from "./$types";export const load: PageServerLoad = async ({ params }) => { const post = await client.blog.getOrNull(params.id); if (!post) error(404, "Post not found"); return { post };};<script lang="ts"> import type { PageData } from "./$types"; let { data }: { data: PageData } = $props();</script><svelte:head><title>{data.post.title}</title></svelte:head><article> <h1>{data.post.title}</h1> <div>{@html data.post.body}</div></article>getOrNull()は404だけをnullに変換します。認証失敗などはエラーのまま、存在しない記事はページも404にします。
bodyは保存時にサニタイズ済みのHTMLです。{@html ...}は描画用で、任意のHTMLを安全にする機能ではありません。
prerenderとentriesでブログを静的生成する
一覧の+page.server.tsへexport const prerender = true;を追記します。詳細の同ファイルには、既存のloadを残して次を追加します。
import type { EntryGenerator } from "./$types";export const prerender = true;export const entries: EntryGenerator = async () => { const posts = await client.blog.getAll({ fields: ["id"] }); return posts.map(post => ({ id: post.id }));};entries()で全公開記事のIDを列挙します。一覧からリンクされていない記事も生成対象になります。
更新反映にはWebhookなどで再ビルドが必要です。削除・公開終了後に古いページが残らないことも確認します。
adapter-staticとadapter-nodeはどう選ぶ?
配信先 | アダプター |
|---|---|
完全静的 |
|
Nodeサーバー |
|
他の配信先では依存パッケージとの互換性も確認します。
SvelteKit 2のadapter-staticはsvelte.config.jsで設定します。既存の前処理を残し、公式の2系ガイドに従って変更してください。
完全静的なら全ルートを生成します。ssr = falseの空シェルで代用しません。
下書きプレビューを公開ブログから分離する
サーバー配信なら公開記事を静的生成し、/preview/[id]だけをリクエスト時に処理できます。
// src/routes/preview/[id]/+page.server.tsimport { error } from "@sveltejs/kit";import { client } from "$lib/server/genko";import type { PageServerLoad } from "./$types";export const prerender = false;export const load: PageServerLoad = async ({ params, url, setHeaders }) => { setHeaders({ "cache-control": "private, no-store", "x-robots-tag": "noindex, nofollow", "referrer-policy": "no-referrer", }); const draftKey = url.searchParams.get("draftKey"); if (!draftKey) error(400, "Missing draft key"); const post = await client.blog.getOrNull(params.id, { draftKey }); if (!post) error(404, "Draft not found"); return { post };};src/routes/preview/[id]/+page.svelteは上の詳細と同じ描画にし、プレビューURLをhttps://your-site.example/preview/{id}?draftKey={key}にします。保存済み下書きが対象です。このURLを共有しないでください。
draftKeyはプレビューだけで扱います。adapter-staticには動的ルートを同梱できないため、専用環境かサーバー対応アダプターが必要です。
通知・終了ボタンは自動追加されません。終了時は公開ページへ戻り、CDNにも下書きを残しません。
npm run checkとnpm run build後、全記事・遷移・404・削除後の反映を確認します。取得失敗は空の一覧と区別します。
よくある質問
SvelteKitの+page.tsからCMSキーを使えますか?
共通のloadはブラウザでも動くため、秘密値を扱う取得は+page.server.tsへ置きます。表示用の記事だけを戻り値に含めます。
SvelteKitでCMSブログを完全静的にできますか?
公開記事をビルド時に取得し、生成対象を列挙できるなら可能です。記事の追加・削除後は再ビルドが必要です。
adapter-staticで下書きプレビューは動きますか?
リクエストごとに下書きを取得するサーバールートは動きません。別のプレビュー環境か、サーバー対応アダプターが必要です。
出典
確認日:2026年10月20日。
