CMS for Nuxt
Nuxt gives you Vue for UI and Nitro for server APIs. Noma fits as a headless backend: keep CMS credentials in private runtime config, expose thin server routes for the browser, and use route rules or prerender hooks for static and ISR-style delivery.
Why Noma with Nuxt
The same @nomacms/js-sdk you use in Next.js runs on Nitro. Editors work in the Noma dashboard; your Nuxt app reads published entries through server-only code paths. That keeps Vue components free of secrets while still using useFetch and file-based routing.
Patterns apply to current Nuxt 3 and Nuxt 4 releases; always match snippets to the version you run. Start with the official Nuxt framework guide, then use this page for caching, prerender, and SEO-oriented checklists.
Project, workspace, API key
- Create a Noma project and collections at app.nomacms.com.
- Copy the project UUID and create an API key under the correct workspace (authentication).
- Match token abilities to your automation:
readfor public sites,adminonly for schema tooling.
SDK and runtime config
npm install @nomacms/js-sdkDeclare private runtime keys (never place the API key under runtimeConfig.public):
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
nomaProjectId: "",
nomaApiKey: "",
},
});NUXT_NOMA_PROJECT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
NUXT_NOMA_API_KEY=noma_...Nuxt maps NUXT_* env vars into runtimeConfig automatically—see Runtime config and prerendering.
// server/utils/noma.ts
import type { H3Event } from "h3";
import { createClient } from "@nomacms/js-sdk";
/** Pass `event` from `defineEventHandler` so Nitro resolves runtime config per request. */
export function useNomaServerClient(event?: H3Event) {
const config = event ? useRuntimeConfig(event) : useRuntimeConfig();
if (!config.nomaProjectId || !config.nomaApiKey) {
throw new Error("Missing nomaProjectId or nomaApiKey in runtime config");
}
return createClient({
projectId: config.nomaProjectId,
apiKey: config.nomaApiKey,
});
}Server API routes + useFetch
Option A: centralize reads in server/api so every Vue page uses the same JSON surface and your key stays on the server.
// server/api/posts.get.ts
export default defineEventHandler(async (event) => {
const noma = useNomaServerClient(event);
return noma.content.list("posts", {
state: "published",
paginate: 24,
sort: "created_at:desc",
});
});// server/api/posts/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, "id");
if (!id) throw createError({ statusCode: 400, statusMessage: "Missing id" });
const noma = useNomaServerClient(event);
return noma.content.get("posts", id, { state: "published" });
});<!-- pages/posts/index.vue -->
<script setup lang="ts">
const { data, error } = await useFetch("/api/posts");
</script>
<template>
<ul v-if="data && 'data' in data && data.data">
<li v-for="post in data.data" :key="post.uuid">
<NuxtLink :to="`/posts/${post.uuid}`">
{{ post.fields?.title ?? post.uuid }}
</NuxtLink>
</li>
</ul>
</template><!-- pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute();
const { data: post } = await useFetch(() => `/api/posts/${route.params.id}`);
</script>
<template>
<article v-if="post">
<h1>{{ post.fields?.title }}</h1>
</article>
</template>List shapes: with paginate, bodies are typically { data, meta, links }. Singleton collections return a single object from list—see Content SDK.
Node preset, edge, and ISR
Prefer a Node Nitro preset for SDK calls until you validate HTTP on edge or worker targets. Use routeRules for time-based regeneration:
// nuxt.config.ts (excerpt)
export default defineNuxtConfig({
routeRules: {
"/posts/**": { isr: 60 },
"/": { prerender: true },
},
});For static export, enumerate CMS URLs during prerender:
// nuxt.config.ts (excerpt)
import { createClient } from "@nomacms/js-sdk";
export default defineNuxtConfig({
hooks: {
async "prerender:routes"(ctx) {
const noma = createClient({
projectId: process.env.NUXT_NOMA_PROJECT_ID!,
apiKey: process.env.NUXT_NOMA_API_KEY!,
});
const res = await noma.content.list("posts", {
state: "published",
paginate: 100,
page: 1,
});
const entries = Array.isArray(res) ? res : res.data;
for (const post of entries) ctx.routes.add(`/posts/${post.uuid}`);
},
},
});Project Auth and sessions
Run OAuth token exchange in Nitro routes and store session cookies as with any backend. See Project Auth and the SDK README; never log raw provider tokens on the client.
MCP and noma-nuxt skills
Use @nomacms/mcp-server in the editor and install the noma-nuxt skill from Noma agent skills for Nitro + runtimeConfig conventions.