# SvelteKitでヘッドレスCMSを使う：load・静的生成・アダプター選定

公開日: 2026-10-20 | 更新日: 2026-10-20 | URL: https://blog.genko.me/sveltekit-headless-cms

> SvelteKit 2とSvelte 5でgenkoを使う手順を解説します。サーバー専用のload関数で記事一覧と詳細を表示する方法、prerenderとentriesによる静的生成、アダプター選定、下書きプレビューの分離まで確認できます。

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を接続する準備

1. SvelteKit 2・Svelte 5を用意します。
2. [genko.me](https://genko.me/)でリスト形式のAPI `blog`を作ります。
3. `title`をテキスト、`body`をリッチテキストとして追加し、記事を公開します。
4. `blog`の読み取りを許可したAPIキーを発行します。
5. ビルド環境に`GENKO_API_KEY`を設定します。

```bash
npm install @genko-me/sdk
```

初期操作は[クイックスタート](https://docs.genko.me/getting-started)を参照してください。表示部品は導入ガイドの別構成です。

## CMSクライアントをサーバー専用にする

```ts
// src/lib/server/genko.ts
import { 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`です。

```ts
// src/routes/blog/+page.server.ts
import { 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"],
  }),
});
```

```svelte
<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`です。

```ts
// src/routes/blog/[id]/+page.server.ts
import { 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 };
};
```

```svelte
<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`を残して次を追加します。

```ts
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](https://docs.genko.me/guides/webhooks)などで再ビルドが必要です。削除・公開終了後に古いページが残らないことも確認します。

## adapter-staticとadapter-nodeはどう選ぶ？

| 配信先 | アダプター |
| --- | --- |
| 完全静的 | `adapter-static` |
| Nodeサーバー | `adapter-node`など |

他の配信先では依存パッケージとの互換性も確認します。

SvelteKit 2の`adapter-static`は`svelte.config.js`で設定します。既存の前処理を残し、公式の[2系ガイド](https://github.com/sveltejs/kit/blob/%40sveltejs%2Fkit%402.49.1/documentation/docs/25-build-and-deploy/50-adapter-static.md)に従って変更してください。

完全静的なら全ルートを生成します。`ssr = false`の空シェルで代用しません。

## 下書きプレビューを公開ブログから分離する

サーバー配信なら公開記事を静的生成し、`/preview/[id]`だけをリクエスト時に処理できます。

```ts
// src/routes/preview/[id]/+page.server.ts
import { 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・削除後の反映を確認します。取得失敗は空の一覧と区別します。

関連：[Astro版](https://blog.genko.me/astro-blog-cms)、[Nuxt版](https://blog.genko.me/nuxt-headless-cms)。

## よくある質問

### SvelteKitの+page.tsからCMSキーを使えますか？

共通の`load`はブラウザでも動くため、秘密値を扱う取得は`+page.server.ts`へ置きます。表示用の記事だけを戻り値に含めます。

### SvelteKitでCMSブログを完全静的にできますか？

公開記事をビルド時に取得し、生成対象を列挙できるなら可能です。記事の追加・削除後は再ビルドが必要です。

### adapter-staticで下書きプレビューは動きますか？

リクエストごとに下書きを取得するサーバールートは動きません。別のプレビュー環境か、サーバー対応アダプターが必要です。

## 出典

- [genko SvelteKit導入](https://docs.genko.me/frameworks/sveltekit/getting-started)
- [SvelteKit公式：$env/static/private](https://svelte.dev/docs/kit/$env-static-private)
- [SvelteKit 2.49.1公式ソース：Page options](https://github.com/sveltejs/kit/blob/%40sveltejs%2Fkit%402.49.1/documentation/docs/20-core-concepts/40-page-options.md)
- [SvelteKit 2.49.1公式ソース：Static site generation](https://github.com/sveltejs/kit/blob/%40sveltejs%2Fkit%402.49.1/documentation/docs/25-build-and-deploy/50-adapter-static.md)

確認日：2026年10月20日。
