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へ接続する準備
既存のNuxt 4プロジェクトを用意します。
genko.meでリスト形式のAPI
blogを作ります。titleをテキスト、bodyをリッチテキストにして、記事を保存・公開します。blogを読み取れるAPIキーを発行します。実行環境の
NUXT_GENKO_API_KEYへキーを設定します。
npm install @genko-me/sdk初期設定はクイックスタートを参照してください。Vue部品の署名付きページ送りは使いません。採用時は専用ガイドのGENKO_API_KEYとランタイム条件にも従います。
APIキーをNuxtのruntimeConfigで管理する
// nuxt.config.tsexport default defineNuxtConfig({ runtimeConfig: { genkoApiKey: "" },});// 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です。
// 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"], });});<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です。
// 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) };});<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日。
