genko.meDocs

導入

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は自分のエンドポイントに置き換えます。

インストール

bash
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からブラウザ側のコードに渡ります。

.env.local
VITE_GENKO_WORKSPACE=your-workspaceVITE_GENKO_API_KEY=発行したキー

APIの定義と型を書きます。フィールドIDと種類は管理画面のスキーマに合わせてください。@genko-me/cliのinitで生成することもできますが、生成先はNext.js向けの判定になるため、この対象では手で書くほうが早いです。

src/lib/schema.ts
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か所に作ります。

src/lib/genko.ts
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の定義からその場で取り出せます。

src/components/BlogList.svelte
<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を受け取る想定です。

src/components/BlogPost.svelte
<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は標準の装飾を付けません。

css
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; }

表示されないとき

状況確認すること
一覧が空記事が保存済みで「公開」か。公開終了日時を過ぎていないか
401APIキーが有効期間内か。VITE_で始まる名前にしているか。開発サーバーを再起動したか
403APIキーを発行したワークスペースと接続先が同じか
404ワークスペースIDとエンドポイントが正しいか。キーでそのAPIを許可しているか
フィールド部品はList/Viewの中に置いてください。ListItem・ViewItemをListClient・EntryProviderのchildren snippetの外に置いています

次は実践で、ページ送りと下書きプレビューを組み立てます。

最終更新: 2026年10月16日

目次