genko.meDocs

導入

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

インストール

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

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

APIの定義と型を書きます。フィールドIDと種類は管理画面のスキーマに合わせてください。

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

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/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しても構いません)。

src/main.ts
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で取得します。

src/components/BlogList.vue
<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()で取得します。

src/router.ts
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 },  ],});
src/components/BlogPost.vue
<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}に遷移して本文が出れば接続完了です。

表示されないとき

状況確認すること
一覧が空記事が保存済みで「公開」か。公開終了日時を過ぎていないか
401APIキーが有効期間内か。VITE_で始まる名前にしているか。開発サーバーを再起動したか
403APIキーを発行したワークスペースと接続先が同じか
404ワークスペースIDとエンドポイントが正しいか。キーでそのAPIを許可しているか
<ListItem>などが素通りするGenkoVuePluginをapp.use()していないか、部品を個別にimportし忘れていないか
フィールド部品がエラーになる<ListItem>/<ViewItem>を<EntryProvider>または<ListClient>の外に置いていないか

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

最終更新: 2026年10月16日

目次