Noma

Nuxt

Nuxt

Use the JavaScript SDK on the server (Nitro routes, server middleware, or useAsyncData with a server-only composable) so your personal access token stays off the client.

Install

npm install @nomacms/js-sdk

Runtime configuration

Define secrets in nuxt.config.ts under runtimeConfig without putting them in public. Only runtimeConfig.public is exposed to the browser.

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    nomaProjectId: "",
    nomaApiKey: "",
  },
})

Set values in .env:

NUXT_NOMA_PROJECT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
NUXT_NOMA_API_KEY=noma_...

Nuxt maps NUXT_* keys to runtimeConfig automatically (environment variables).

Server-only client helper

// server/utils/noma.ts
import { createClient } from "@nomacms/js-sdk"
 
export function useNomaServerClient() {
  const config = useRuntimeConfig()
 
  if (!config.nomaProjectId || !config.nomaApiKey) {
    throw new Error("Missing nomaProjectId or nomaApiKey in runtime config")
  }
 
  return createClient({
    projectId: config.nomaProjectId,
    apiKey: config.nomaApiKey,
  })
}

Fetch content in a page

Call the SDK inside useAsyncData or useFetch from a server context—for example, pass a function that runs only on the server by using a server route or by importing a server-only helper.

Option A — server API route as a thin BFF

// server/api/posts.get.ts
export default defineEventHandler(async () => {
  const noma = useNomaServerClient()
  return noma.content.list("posts", {
    state: "published",
    paginate: 12,
    sort: "created_at:desc",
  })
})
<!-- pages/posts/index.vue -->
<script setup lang="ts">
const { data, error } = await useFetch("/api/posts")
</script>
 
<template>
  <ul v-if="data?.data">
    <li v-for="post in data.data" :key="post.uuid">
      <NuxtLink :to="`/posts/${post.uuid}`">{{ post.fields?.title }}</NuxtLink>
    </li>
  </ul>
</template>

Option B — dynamic Nitro route

// 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()
  return noma.content.get("posts", id, { state: "published" })
})

Keep any code path that calls createClient under server/ (or behind import.meta.server) so keys are never sent to the client.

Dynamic route: single entry

<!-- pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const { data: post, error } = await useFetch(() => `/api/posts/${route.params.id}`)
</script>
 
<template>
  <article v-if="post">
    <h1>{{ post.fields?.title }}</h1>
  </article>
</template>

This matches the handler in Option B.

Nitro runtime

Run Noma calls in Node Nitro preset when possible; the SDK targets standard server environments. If you use edge or worker presets, validate HTTP compatibility in your deployment.

End users (project auth)

For Project Auth flows (password or social sign-in), perform token exchange in server routes and persist session tokens using cookies or your chosen store—see Installation & Setup for projectUserAuth and Session Management.

  • Next.js and Astro - Same SDK patterns on other frameworks.
  • Installation & Setup - createClient, errors, and optional project user tokens.
  • Content API - Personal access tokens (per workspace) and abilities.
  • Quickstart - Dashboard setup and first collection.

Search documentation

Find guides and reference pages