Noma
Gatsby · SSG · 2026

CMS for Gatsby

Gatsby excels at build-time data fusion. Noma is a natural headless source: fetch published entries in gatsby-node.js with the JavaScript SDK, pass them through pageContext, and deploy static HTML—then let webhooks kick off rebuilds when content changes.

Fit

Gatsby + headless Noma

Unlike a gatsby-source-* plugin, a thin createPages integration keeps full control over queries, locales, and publish state (state: "published"). You trade GraphQL introspection of CMS fields for straightforward TypeScript-friendly objects from the SDK.

If you later want GraphQL, add a custom source plugin or use sourceNodes in gatsby-node.js to push Noma entries into Gatsby’s data layer—start simple, evolve when multiple consumers need the same normalized nodes.

Prerequisites

Keys and collections

  • Noma project UUID + API key from the dashboard (docs).
  • A collection slug (example posts) with the fields your templates expect under entry.fields.
  • Hosting that supports Gatsby builds and optional Gatsby 5 features you enable in config.
Install

SDK and environment

npm install @nomacms/js-sdk gatsby

Add secrets to .env.development / production CI— no GATSBY_ prefix on the API key:

NOMA_PROJECT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
NOMA_API_KEY=noma_...

Load env in gatsby-config via require("dotenv").config() or your host’s injected variables so process.env is populated during gatsby build.

Build-time pages

createPages pattern

Fetch once per build, normalize the list vs paginated response shape, then call createPage for the index and each detail route:

// gatsby-node.js
const path = require("path");
const { createClient } = require("@nomacms/js-sdk");
 
exports.createPages = async ({ actions }) => {
  const { createPage } = actions;
  const noma = createClient({
    projectId: process.env.NOMA_PROJECT_ID,
    apiKey: process.env.NOMA_API_KEY,
  });
 
  const result = await noma.content.list("posts", {
    state: "published",
    paginate: 100,
    page: 1,
    sort: "created_at:desc",
  });
  const posts = "data" in result ? result.data : result;
 
  createPage({
    path: "/posts",
    component: path.resolve("src/templates/posts.tsx"),
    context: { posts },
  });
 
  for (const post of posts) {
    createPage({
      path: `/posts/${post.uuid}`,
      component: path.resolve("src/templates/post.tsx"),
      context: { post },
    });
  }
};
// src/templates/posts.tsx
import * as React from "react";
import { Link } from "gatsby";
import type { PageProps } from "gatsby";
 
type CmsEntry = { uuid: string; fields: Record<string, unknown> };
 
const PostsTemplate: React.FC<PageProps<object, { posts: CmsEntry[] }>> = ({
  pageContext,
}) => (
  <ul>
    {pageContext.posts.map((post) => (
      <li key={post.uuid}>
        <Link to={`/posts/${post.uuid}`}>
          {String(post.fields?.title ?? post.uuid)}
        </Link>
      </li>
    ))}
  </ul>
);
 
export default PostsTemplate;
// src/templates/post.tsx
import * as React from "react";
import type { PageProps } from "gatsby";
 
type CmsEntry = {
  uuid: string;
  fields: Record<string, unknown>;
};
 
const PostTemplate: React.FC<PageProps<object, { post: CmsEntry }>> = ({
  pageContext,
}) => {
  const { post } = pageContext;
  return (
    <article>
      <h1>{String(post.fields?.title ?? "")}</h1>
    </article>
  );
};
 
export default PostTemplate;

For large collections, paginate in a loop in gatsby-node.js or fetch by updated cursor if you add indexing—respect Noma rate limits.

Drafts and previews

Optional preview builds

Use a separate CI job or branch build with a read-capable key and state: "draft" in SDK calls, protected by basic auth at the CDN. Never expose draft content on the public production domain.

Webhooks

Rebuild on publish

// Netlify / similar: call your build hook from a Noma webhook.
// Verify a shared secret in a small serverless function if you expose a public URL.

Point Noma webhooks at your provider’s build hook or an authenticated endpoint that triggers gatsby build. Debounce bursts if editors publish many entries in sequence.

AI tooling

MCP and skills

Editors and developers can use @nomacms/mcp-server from Cursor or Claude Code. The skills repo is strongest for Next/Nuxt/Astro; pair it with this page for Gatsby-specific createPages wiring—see agent skills.

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.