導入
Viteで作ったVueアプリに@genko-me/vueを入れ、記事一覧と記事詳細をブラウザから取得して表示する手順。
Viteで作ったVueアプリを例に、記事一覧と記事詳細を表示するまでの手順です。
ワークスペースの作成とブログAPIの用意はクイックスタートの手順1〜3にまとめています。
用意するもの
- genko.meのアカウントとワークスペース。APIの作成とAPIキーの発行にはオーナー権限が必要です。
- 記事が1件以上、保存済みかつ公開状態で入っているAPI。下書きのままでは配信APIから取得できません。
- Vue 3.5以上のプロジェクトと、バンドラー(Vite)。
@genko-me/vueはVue 3.5をpeerDependencyに指定しています。 - 複数ページを扱う場合はVue Router。この手順でも一覧・詳細の切り替えに使います。
- ビルドに使うNode.js 20.12以降。
以下のyour-workspaceは自分のワークスペースIDに、blogは自分のエンドポイントに置き換えます。
インストール
npm install @genko-me/vue @genko-me/sdkこのページは@genko-me/[email protected]に対応しています。ビルド時に@genko-me/sdkからcryptoが外部化される旨の警告が出ることがありますが、無視して構いません。Webhook署名検証用のNode API呼び出しをブラウザでは使わず、WebCryptoへ自動でフォールバックするためです。
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と種類は管理画面のスキーマに合わせてください。
import { listApi } from "@genko-me/sdk";
export type Blog = { title: string; body: string; publishedAt: string };
export const apis = { blog: listApi<Blog>({ fields: [ { id: "title", type: "text" }, { id: "body", type: "richtext" }, { id: "publishedAt", type: "datetime" }, ], }),};接続するクライアントを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/cliのinit / pullでschema.tsを管理画面から生成することもできます。ただし生成されるclient.tsはprocess.env.GENKO_API_KEYを読むので、Viteでは上のようにimport.meta.envへ書き換えてください。CLIは依存にnextがあるプロジェクトにしか@genko-me/reactを追加しませんが、@genko-me/vueは自動で追加されないため、いずれにせよ手動でインストールします。
@genko-me/vueの部品をテンプレートで使うには、一括登録するプラグインをmain.tsで使います(部品ごとにimportしても構いません)。
import { createApp } from "vue";import { GenkoVuePlugin } from "@genko-me/vue";import App from "./App.vue";import { router } from "./router";
createApp(App).use(router).use(GenkoVuePlugin).mount("#app");記事一覧を出す
fields・schemasはgetApiBinding(client.blog)から同期的に取れます(管理画面のスキーマそのものなので、通信は要りません)。実際のコンテンツだけをonMountedで取得します。
<script setup lang="ts">import { onMounted, ref } from "vue";import { getApiBinding } from "@genko-me/sdk";import type { GenkoListResult } from "@genko-me/sdk";import { client } from "../lib/genko";
const binding = getApiBinding(client.blog);const initial = ref<GenkoListResult<Record<string, unknown>>>();const failed = ref(false);
onMounted(async () => { try { initial.value = await client.blog.list({ limit: 10, orders: "-publishedAt" }); } catch { failed.value = true; }});</script>
<template> <Error v-if="failed" template /> <Loading v-else-if="!initial" template /> <ListClient v-else :initial="initial" :initial-page="1" :fields="binding.fields" :schemas="binding.schemas" href="/blog/{id}"> <article> <h2><ListItem id="title" /></h2> </article> <template #empty><Null>公開されている記事はありません。</Null></template> </ListClient></template>list()はcontentsとtotalCountを返します。ListClientの既定スロットが1件分のレイアウトです。pagination・loading・error・emptyは名前付きスロットで、繰り返しません。スロットを省略すると、その状態では何も表示しません。
href="/blog/{id}"は普通の<a>を出します。Vue Routerを使っていればhistoryモードの遷移としてそのまま動きます(クリックでフルリロードにはなりません)。
記事詳細を出す
一覧と同じく、fields・schemasはgetApiBindingから、本文はgetOrNull()で取得します。
import { createRouter, createWebHistory } from "vue-router";import BlogList from "./components/BlogList.vue";import BlogPost from "./components/BlogPost.vue";
export const router = createRouter({ history: createWebHistory(), routes: [ { path: "/blog", component: BlogList }, { path: "/blog/:id", component: BlogPost }, ],});<script setup lang="ts">import { onMounted, ref } from "vue";import { useRoute } from "vue-router";import { getApiBinding } from "@genko-me/sdk";import type { GenkoEntryMeta } from "@genko-me/sdk";import { client } from "../lib/genko";import type { Blog } from "../lib/schema";
const route = useRoute();const binding = getApiBinding(client.blog);const post = ref<(Blog & GenkoEntryMeta) | null>();
onMounted(async () => { try { post.value = await client.blog.getOrNull(route.params.id as string); } catch { post.value = null; }});</script>
<template> <p v-if="post === undefined">読み込み中…</p> <p v-else-if="post === null">記事が見つかりません。</p> <EntryProvider v-else :entry="post" :fields="binding.fields" :schemas="binding.schemas"> <h1><ViewItem id="title" /></h1> <ViewItem id="body" /> </EntryProvider></template>getOrNull()は404のときだけnullを返します。認証エラーやサーバーエラーは例外のままなので、catchで分けて扱ってください。本文の見出し・段落の間隔・画像の最大幅は、サイト側のCSSで指定します(ViewItemは標準の装飾を付けません)。
開発サーバーを起動し、/blogを開きます。記事が並び、クリックで/blog/{id}に遷移して本文が出れば接続完了です。
表示されないとき
| 状況 | 確認すること |
|---|---|
| 一覧が空 | 記事が保存済みで「公開」か。公開終了日時を過ぎていないか |
401 | APIキーが有効期間内か。VITE_で始まる名前にしているか。開発サーバーを再起動したか |
403 | APIキーを発行したワークスペースと接続先が同じか |
404 | ワークスペースIDとエンドポイントが正しいか。キーでそのAPIを許可しているか |
<ListItem>などが素通りする | GenkoVuePluginをapp.use()していないか、部品を個別にimportし忘れていないか |
| フィールド部品がエラーになる | <ListItem>/<ViewItem>を<EntryProvider>または<ListClient>の外に置いていないか |
次は実践で、ページ送りと下書きプレビューを組み立てます。
最終更新: 2026年10月16日