NuxtでヘッドレスCMSを使うには、server/apiでCMSへ接続し、ページからuseFetch()で自分のAPIを呼び出す構成にします。APIキーをサーバー側に置きながらSSRで記事を表示でき、更新頻度に応じて公開ページをプリレンダリングする方法も選べます。

本記事はgenko.meの運営側が執筆しています。2026年10月20日時点のNuxt 4向けに、genkoのSDKを直接使う最小構成を紹介します。表示部品を使いたい場合はNuxt導入ガイドのVue連携も選べます。

SSRと静的生成はどう選ぶ?

方法

選ぶ目安

SSR

リクエスト時に取得

静的生成

更新後のビルド待ちを許容

プレビューにはサーバーが必要です。

Nuxt 4からgenkoへ接続する準備

  1. 既存のNuxt 4プロジェクトを用意します。

  2. genko.meでリスト形式のAPI blogを作ります。

  3. titleをテキスト、bodyをリッチテキストにして、記事を保存・公開します。

  4. blogを読み取れるAPIキーを発行します。

  5. 実行環境のNUXT_GENKO_API_KEYへキーを設定します。

bash
npm install @genko-me/sdk

初期設定はクイックスタートを参照してください。Vue部品の署名付きページ送りは使いません。採用時は専用ガイドのGENKO_API_KEYとランタイム条件にも従います。

APIキーをNuxtのruntimeConfigで管理する

ts
// nuxt.config.tsexport default defineNuxtConfig({  runtimeConfig: { genkoApiKey: "" },});
ts
// server/utils/genko.tsimport { createGenko, listApi } from "@genko-me/sdk";type Blog = { title: string; body: string };const apis = {  blog: listApi<Blog>({    fields: [      { id: "title", type: "text" },      { id: "body", type: "richtext" },    ],  }),};export function createBlogClient(apiKey: string) {  if (!apiKey) throw new Error("NUXT_GENKO_API_KEY is required");  return createGenko({ workspace: "your-workspace", apiKey, apis }).apis.blog;}

キーはruntimeConfig.publicへ入れず、本番環境にもNUXT_GENKO_API_KEYを設定します。your-workspaceを実際のIDへ置き換えます。

useFetchで記事一覧をSSR表示する

ページはapp/pages/blog/index.vueです。

ts
// server/api/blog/index.get.tsimport { createBlogClient } from "../../utils/genko";export default defineEventHandler(async event => {  const config = useRuntimeConfig(event);  return createBlogClient(config.genkoApiKey).getAll({    orders: "-publishedAt",    fields: ["id", "title"],  });});
vue
<script setup lang="ts">const { data: posts, error } = await useFetch("/api/blog");if (error.value) {  throw createError({ statusCode: 502, statusMessage: "Unable to load posts" });}useHead({ title: "Blog" });</script><template>  <main>    <h1>Blog</h1>    <ul>      <li v-for="post in posts" :key="post.id">        <NuxtLink :to="`/blog/${post.id}`">{{ post.title }}</NuxtLink>      </li>    </ul>    <p v-if="posts?.length === 0">No published posts.</p>  </main></template>

useFetch()はSSRの結果をクライアントへ引き継ぎます。内部APIは表示用の記事だけを返し、秘密値やCMS管理操作を公開しません。

記事数が増えたらlist()でページ分割します。

Nuxtで記事詳細と下書きプレビューを実装する

ページはapp/pages/blog/[id].vueです。

ts
// server/api/blog/[id].get.tsimport { createBlogClient } from "../../utils/genko";export default defineEventHandler(async event => {  const id = getRouterParam(event, "id");  if (!id) throw createError({ statusCode: 400, statusMessage: "Missing id" });  const query = getQuery(event);  const draftKey = typeof query.draftKey === "string" ? query.draftKey : undefined;  const config = useRuntimeConfig(event);  const post = await createBlogClient(config.genkoApiKey).getOrNull(id, { draftKey });  if (!post) throw createError({ statusCode: 404, statusMessage: "Post not found" });  if (draftKey) setResponseHeader(event, "Cache-Control", "private, no-store");  return { post, preview: Boolean(draftKey) };});
vue
<script setup lang="ts">const route = useRoute();const { data, error } = await useFetch(  () => `/api/blog/${encodeURIComponent(String(route.params.id))}`,  { query: computed(() => ({ draftKey: route.query.draftKey })) },);if (error.value || !data.value) {  throw createError({    statusCode: error.value?.statusCode === 404 ? 404 : 502,    statusMessage: "Unable to load post",  });}if (import.meta.server && data.value.preview) {  useResponseHeader("Cache-Control").value = "private, no-store";}useHead(() => ({  title: data.value?.preview ? "Draft preview" : data.value?.post.title,  meta: data.value?.preview ? [    { name: "robots", content: "noindex, nofollow" },    { name: "referrer", content: "no-referrer" },  ] : [],}));</script><template>  <main v-if="data">    <p v-if="data.preview">Draft preview</p>    <h1>{{ data.post.title }}</h1>    <div v-html="data.post.body" />  </main></template>

管理画面のプレビューURLにはhttps://your-site.example/blog/{id}?draftKey={key}を設定します。{id}と{key}は管理画面が置き換えます。

draftKeyはページから内部APIへ自動転送されません。queryとSDKの引数に明示的に渡し、保存済み下書きを取得します。

この実装にはVue連携のプレビューバーはありません。終了時はdraftKeyを外します。下書きキーも共有しないでください。

v-htmlはgenkoの保存時サニタイズ済み本文に使います。任意の外部HTMLを安全にする機能ではありません。

プレビューのキャッシュ対策

APIとSSRページにprivate, no-storeを付けます。CDN側の強制キャッシュも確認します。

プレビューURLを共有リンクへ使わず、ログ中のキーも保護します。実践ガイドも参照してください。

Nuxtのブログをプリレンダリングするには?

公開記事を静的生成する構成では、例えば次の設定を使えます。

nuxt.config.tsのnitro.prerenderにroutes: ["/blog"]とcrawlLinks: trueを追加します。

/blogから詳細リンクをたどって生成します。リンクのない記事はURLを列挙し、全記事の生成を確認します。

静的配信では実行時のserver/apiは使えません。必要な結果もビルド時に生成します。プレビューには別のサーバー環境かサーバー対応の配信が必要です。

Webhookで再ビルドする場合、手動公開の作成・更新イベントも処理します。

公開前の確認

npm run build後、直接アクセスとNuxtLink遷移で公開・下書き・404・更新反映を確認します。記事が空なら公開状態、認証失敗なら本番のキーとアクセス範囲を調べます。

関連:Astro版、SvelteKit版。

よくある質問

NuxtのuseFetchからCMSを直接呼んでもよいですか?

秘密のAPIキーが必要な場合はサーバールートを通します。ブラウザへ渡るコードや公開runtimeConfigにキーを置かないようにします。

Nuxtの下書きプレビューで公開版が表示されるのはなぜですか?

ページから内部API、内部APIからSDKへdraftKeyを渡したか確認します。保存前の編集内容は取得できません。

Nuxtの静的サイトでserver/apiは動きますか?

静的ファイルだけを配るホストでは動きません。必要な結果をビルド時に生成するか、実行時のサーバーを用意します。

出典

公式ページの確認日:2026年10月20日。