導入
SvelteKitを使わないSvelte(Vite)のSPAに@genko-me/svelteを入れ、ブラウザから記事一覧と記事詳細を表示する手順。
Viteで作ったSvelteのSPAを例に、記事一覧と記事詳細を表示するまでの手順です。
ワークスペースの作成とブログAPIの用意はクイックスタートの手順1〜3にまとめています。
用意するもの
- genko.meのアカウントとワークスペース。APIの作成とAPIキーの発行にはオーナー権限が必要です。
- 記事が1件以上、保存済みかつ公開状態で入っているAPI。下書きのままでは配信APIから取得できません。
- Svelte 5のプロジェクト(
npm create vite@latestのsvelteまたはsvelte-tsテンプレートなど)。@genko-me/svelteはSvelte 5をpeerDependencyに指定しています。 - ビルドに使うNode.js 20.12以降。
以下のyour-workspaceは自分のワークスペースIDに、blogは自分のエンドポイントに置き換えます。
インストール
npm install @genko-me/svelte @genko-me/sdkこのページは@genko-me/[email protected]に対応しています。この対象では@genko-me/svelte/serverは使いません(概要を参照)。
APIキーとワークスペースIDを設定する
APIキーは管理画面の「設定・プラン」→「APIキー」(/w/your-workspace/settings/api-keys)で発行します。「選択したAPIのみ」で、ブラウザに出しても構わないAPIだけに絞ってください。この構成ではキーがバンドルに入り、誰でも読めます。
ViteではVITE_で始まる環境変数だけがimport.meta.envからブラウザ側のコードに渡ります。
VITE_GENKO_WORKSPACE=your-workspaceVITE_GENKO_API_KEY=発行したキーAPIの定義と型を書きます。フィールドIDと種類は管理画面のスキーマに合わせてください。@genko-me/cliのinitで生成することもできますが、生成先はNext.js向けの判定になるため、この対象では手で書くほうが早いです。
import { listApi } from "@genko-me/sdk";
export type Blog = { title: string; body: string };
export const apis = { blog: listApi<Blog>({ fields: [ { id: "title", type: "text" }, { id: "body", type: "richtext" }, ], }),};接続するクライアントを1か所に作ります。
import { createGenko } from "@genko-me/sdk";import { apis } from "./schema";
export const genko = createGenko({ workspace: import.meta.env.VITE_GENKO_WORKSPACE, apiKey: import.meta.env.VITE_GENKO_API_KEY, apis,});
export const client = genko.apis;記事一覧を出す
@genko-me/svelteの表示部品はSvelteKitに依存しないので、ListClientにそのまま渡せます。ただし取得はアプリ側で行うため、onMountでclient.blog.list()を呼び、結果をListClientが要求する形に組み立てます。
fields・schemas(ListItemが種類を判断するためのスキーマ情報)は、取得のたびに問い合わせる必要はありません。getApiBindingでschema.tsの定義からその場で取り出せます。
<script lang="ts"> import { onMount } from "svelte"; import { getApiBinding } from "@genko-me/sdk"; import { ListClient, ListItem, Error, Null, Loading } from "@genko-me/svelte"; import type { ListClientProps } from "@genko-me/svelte"; import { client } from "../lib/genko";
let listProps = $state<Omit<ListClientProps, "children"> | undefined>(undefined); let failed = $state(false);
onMount(async () => { try { const binding = getApiBinding(client.blog); const initial = await client.blog.list({ limit: 10, orders: "-publishedAt" }); listProps = { initial, initialPage: 1, fields: binding.fields, schemas: binding.schemas, href: "/blog/{id}" }; } catch { failed = true; } });</script>
<h1>ブログ</h1>
{#if failed} <Error template />{:else if !listProps} <Loading template />{:else} <ListClient {...listProps}> {#snippet children()} <article> <h2><ListItem id="title" /></h2> <time><ListItem id="publishedAt" format="yyyy/MM/dd" timeZone="Asia/Tokyo" /></time> </article> {/snippet} {#snippet empty()}<Null>公開されている記事はありません。</Null>{/snippet} </ListClient>{/if}publishedAtのようにgenkoが各エントリーに必ず付与するフィールド(id・createdAt・updatedAt・publishedAt)は、schema.tsに書かなくてもListItem・ViewItemから参照できます。schema.tsに並べるのは、本文や画像などAPIごとに違うフィールドだけです。
hrefテンプレートの{id}はSvelteの式と紛らわしいので、href="/blog/{id}"のように文字列として渡します。既定のタイムゾーンはUTCです。開発サーバーを起動して記事が並べば接続できています。
記事詳細を出す
同じ形で1件を取得し、EntryProviderに渡します。ここではBlogPost.svelteがid propを受け取る想定です。
<script lang="ts"> import { getApiBinding } from "@genko-me/sdk"; import { EntryProvider, ViewItem, Error } from "@genko-me/svelte"; import type { EntryProviderProps } from "@genko-me/svelte"; import { client } from "../lib/genko";
let { id }: { id: string } = $props();
let entryProps = $state<Omit<EntryProviderProps, "children"> | undefined>(undefined); let notFound = $state(false); let failed = $state(false);
$effect(() => { entryProps = undefined; notFound = false; failed = false; const binding = getApiBinding(client.blog); client.blog .getOrNull(id) .then((entry) => { if (!entry) { notFound = true; return; } entryProps = { entry, fields: binding.fields, schemas: binding.schemas }; }) .catch(() => { failed = true; }); });</script>
{#if failed} <Error template />{:else if notFound} <p>記事が見つかりません。</p>{:else if entryProps} <EntryProvider {...entryProps}> {#snippet children()} <article> <h1><ViewItem id="title" /></h1> <ViewItem id="body" /> </article> {/snippet} </EntryProvider>{/if}getOrNull()は404のときだけnullを返します。認証エラーやサーバーエラーは例外のままなので、catchで分けて扱ってください。$effectにすると、id(ルーターが変えるprop)が変わるたびに取得し直せます。
本文の見出し・段落の間隔・画像の最大幅は、サイト側のCSSで指定します。ViewItemは標準の装飾を付けません。
article :global(h2) { margin-block: 2em 0.75em; font-size: 1.5em; }article :global(p) { margin-block: 1em; }article :global(img) { max-width: 100%; height: auto; }表示されないとき
| 状況 | 確認すること |
|---|---|
| 一覧が空 | 記事が保存済みで「公開」か。公開終了日時を過ぎていないか |
401 | APIキーが有効期間内か。VITE_で始まる名前にしているか。開発サーバーを再起動したか |
403 | APIキーを発行したワークスペースと接続先が同じか |
404 | ワークスペースIDとエンドポイントが正しいか。キーでそのAPIを許可しているか |
フィールド部品はList/Viewの中に置いてください。 | ListItem・ViewItemをListClient・EntryProviderのchildren snippetの外に置いています |
次は実践で、ページ送りと下書きプレビューを組み立てます。
最終更新: 2026年10月16日