genko.meDocs
Frameworks and languagesNuxt

Get started

Install @genko-me/vue in Nuxt, fetch through server routes, and render a list and a single entry with SSR.

This guide connects genko.me to a Nuxt app and renders list and detail pages on the server.

To create a workspace and blog API, follow steps 1–3 of the quickstart.

Prerequisites

  • A genko.me account and workspace. You need Owner access to create an API and issue an API key.
  • An API with at least one saved, published entry. The delivery API does not return drafts by default.
  • A Nuxt 4 project with Vue 3.5 or later. @genko-me/vue lists Vue 3.5 as a peer dependency.
  • Node.js 20.12 or later and a package manager.
  • A Nitro preset with a Node.js-compatible runtime, such as the default node-server. Pagination signatures use node:crypto directly and cannot run in runtimes without it, such as Cloudflare Workers.

Replace your-workspace with your workspace ID and blog with your endpoint in the examples below.

Install

bash
npm install @genko-me/vue @genko-me/sdk

This page covers @genko-me/[email protected].

Prepare the schema and types

Generate types and API definitions from your dashboard schema. Run this at the project root.

bash
npx @genko-me/cli init

The CLI detects the output location as if this were Next.js. Nuxt 4 uses app/ by default, which the CLI cannot distinguish from Next.js App Router's app/. It writes to the project root's lib/. Move the files to app/lib/ and update schemaOutput and clientOutput in genko.config.json.

genko.config.json
{  "schemaOutput": "app/lib/schema.ts",  "clientOutput": "app/lib/client.ts"}

After moving them, genko pull uses that configuration. Nuxt's ~ alias points to app/ (srcDir), letting server routes and pages use the same imports.

If you do not use the CLI, write app/lib/schema.ts yourself. Match field IDs and types to the dashboard schema.

app/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" },    ],  }),};

Set the API key and workspace ID

Issue an API key in the dashboard under Settings and plan → API keys (/w/your-workspace/settings/api-keys). Give it a descriptive name and expiry. If you choose “Selected APIs only,” allow blog.

You can use the generated app/lib/client.ts as-is. It reads process.env.GENKO_API_KEY.

app/lib/client.ts
import { createGenko } from "@genko-me/sdk";import { apis } from "./schema";
export const genko = createGenko({  workspace: "your-workspace",  apiKey: process.env.GENKO_API_KEY!,  apis,});export const client = genko.apis;
.env
GENKO_API_KEY=your-issued-key

Keep the environment variable named GENKO_API_KEY. Pagination signing and verification in @genko-me/vue/server read process.env.GENKO_API_KEY directly, without going through client.ts. If apiKey uses a different value, pagination fails its configuration check.

Make sure Git does not track .env. Nuxt reads it automatically in development. Set the same variable in your hosting environment for production.

Show a list

Fetch in a route under server/. The return value of fetchGenkoList can be passed directly as props to ListClient.

server/api/blog.get.ts
import { fetchGenkoList } from "@genko-me/vue/server";import { client } from "~/lib/client";
export default defineEventHandler(event => fetchGenkoList({  api: client.blog,  limit: 6,  orders: "-publishedAt",  href: "/blog/{id}",  fields: ["title", "publishedAt"],  searchParams: getQuery(event) as Record<string, string | string[] | undefined>,}));

List the IDs displayed by ListItem in fields. React can inspect children to infer fields; Vue cannot, so you must specify them here. Omitting fields fetches everything. An ID missing from the schema causes a configuration error. {id} in href is included automatically.

app/pages/blog/index.vue
<script setup lang="ts">const { data } = await useFetch("/api/blog");</script>
<template>  <main>    <h1>Blog</h1>    <ListClient v-if="data?.ok" :initial="data.initial" :initial-page="data.initialPage" :fields="data.fields" :schemas="data.schemas" href="/blog/{id}">      <article>        <h2><ListItem id="title" /></h2>        <time><ListItem id="publishedAt" format="yyyy/MM/dd" time-zone="Asia/Tokyo" /></time>      </article>      <template #empty><Null>No published entries yet.</Null></template>    </ListClient>    <Error v-else template />  </main></template>

You can use registered names such as ListItem, Null, and Error directly in templates after registering GenkoVuePlugin. No import is needed for template use. Without the plugin, import them from @genko-me/vue yourself.

app/plugins/genko.ts
import { GenkoVuePlugin } from "@genko-me/vue";
export default defineNuxtPlugin(nuxtApp => { nuxtApp.vueApp.use(GenkoVuePlugin); });

The default ListClient slot defines one item's layout. The named pagination, loading, error, and empty slots are not repeated. If you omit a state slot, nothing appears for that state. template shows a default message.

Start the development server and open /blog. Seeing entries confirms the connection.

Show a single entry

server/api/blog/[id].get.ts
import { fetchGenkoEntry } from "@genko-me/vue/server";import { client } from "~/lib/client";
export default defineEventHandler(event => fetchGenkoEntry({  api: client.blog,  id: getRouterParam(event, "id"),  fields: ["title", "body"],  searchParams: getQuery(event) as Record<string, string | string[] | undefined>,}));
app/pages/blog/[id].vue
<script setup lang="ts">const route = useRoute();const { data } = await useFetch(`/api/blog/${route.params.id}`, { query: route.query });</script>
<template>  <main>    <EntryProvider v-if="data?.ok && data.entry" :entry="data.entry" :fields="data.fields" :schemas="data.schemas">      <h1><ViewItem id="title" /></h1>      <ViewItem id="body" />    </EntryProvider>    <p v-else>No entry found.</p>  </main></template>

Always pass the page's query (route.query) to useFetch. /api/blog/... is a separate URL, so opening /blog/xxx?draftKey=... does not automatically forward draftKey. Without it, draft preview always shows the published result. The same applies to URL pagination in the tutorial.

fetchGenkoEntry returns entry: null for a 404 without throwing (data.ok remains true). data.ok is false only if fetching or configuration fails.

Troubleshooting

SymptomCheck
Empty listIs an entry saved and Published? Has its publication end time passed?
data.ok is falseLook for a [genko.me] line in the server log. It includes configuration errors or HTTP status codes.
401Is the key valid, and can the server process read GENKO_API_KEY?
403Does the key belong to the workspace you are connecting to?
404Are the workspace ID and blog endpoint correct, and does the key allow blog?
Cannot find ~/lib/clientThe CLI wrote to lib/, but ~ points to app/lib/. Did you move the files as described above?
Drafts or pagination do not workDid you pass { query: route.query } to the page's useFetch?

Next, use the tutorial to add pagination and draft previews.

Last updated: October 16, 2026

On this page