Noma
Nuxt 3 & 4 · Nitro · 2026

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.

Positioning

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.

Prerequisites

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:read for public sites, admin only for schema tooling.
Install

SDK and runtime config

npm install @nomacms/js-sdk

Declare 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,
  });
}
Nitro BFF

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.

Deployment

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}`);
    },
  },
});
Auth

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.

AI tooling

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.

Links

Next steps

Now available

Start building with Noma

Create a free account, spin up a project, and ship structured content with our API, SDK, and AI tools.