SnabbSajt · Docs
DeveloperSite kit

Headless delivery

Keep hosting your site yourself, and let your marketing team edit it in SnabbSajt.

Your marketing team edits the website in SnabbSajt and presses Publish. Your app fetches the published content and renders it with your own components, on your own hosting, from your own repository.

Content lives in SnabbSajt. Code lives in your git. Neither one syncs into the other, so nobody ever hands a marketer a merge conflict.

Connect a repository

One command, from anywhere inside your repo:

npx @snabbsajt/cli init

It walks up to your repository root, works out which workspace serves your marketing site, opens a browser once so you can approve the connection against a specific site, and writes three files.

Run it with --dry-run first if you want to see the exact file list before anything is written. That is the recommended first step in an unfamiliar repo.

npx @snabbsajt/cli init --dry-run

What it writes

sajt.config.json            which site this repo renders
apps/web/.env.local         SAJT_SITE_ID and SAJT_TOKEN (server-only)
apps/web/app/[[...slug]]/page.tsx    a starter route

The starter route exists to prove the wiring works and to show you the data shape. Replace its markup with your own components — keep the fetch.

Monorepos

init is monorepo-aware. Run it from apps/web, from the root, or from anywhere else; it finds the outermost repository root and writes the config there. It prefers a workspace named web, www, site, marketing or landing over one named app, dashboard or admin.

If two workspaces are equally plausible it stops and asks rather than guessing:

npx @snabbsajt/cli init --app apps/web

Running init twice is safe. The second run mints no second token, rewrites no route you have replaced, and tells you the repo is already connected.

Read the published site

import { createSajtClient, findPage } from "@snabbsajt/site-kit/delivery";

// Fail loudly on a misconfigured environment rather than at the first fetch,
// and narrow the types while you are at it.
const { SAJT_SITE_ID, SAJT_TOKEN } = process.env;
if (!SAJT_SITE_ID || !SAJT_TOKEN) {
  throw new Error("SnabbSajt environment variables are missing");
}

const client = createSajtClient({ siteId: SAJT_SITE_ID, token: SAJT_TOKEN });

const site = await client.getPublishedSite();
const page = findPage(site.snapshot, "om-oss");

getPublishedSite() returns null when the site has never been published — that is a normal state while you are still wiring things up, not an error. It throws only when something is actually wrong: a rejected token, a rate limit, a network failure.

Pass a locale for a translated snapshot on a multi-language site:

await client.getPublishedSite({ locale: "en" });

The token is a server credential

SAJT_TOKEN is read-only and works for exactly one site, but it must still never reach the browser. The client throws if you construct it in a browser environment, and the CLI refuses to write the token into a NEXT_PUBLIC_ variable. Fetch in a Server Component, a route handler, or at build time, and pass the data down.

Rebuild when someone publishes

In SnabbSajt: Settings → AI integrations → Var hemsidan visas. Choose "Hos er själva" and paste a deploy hook URL. Vercel, Netlify, AWS Amplify, Cloudflare Pages and GitLab pipelines all provide one.

We POST to that URL every time someone publishes, so your host rebuilds against the new snapshot. The outcome is shown in the same settings panel — a failed rebuild says so instead of looking like a silent success.

A marketer publishing never deploys your code: the hook rebuilds whatever commit your host already considers current. A developer deploying never changes content: the build fetches the current published snapshot. The two move on separate clocks, which is the point.

Rate limits

The endpoint expects a build, not a browser: 120 requests per minute per IP and 240 per minute per site. Cache the result between builds — revalidate in Next.js, or your framework's equivalent — rather than fetching per request.

Revoking access

Every key is listed under Settings → AI integrations → Nycklar för er app. Revoking one takes effect immediately, and a revoked key is rejected exactly like an unknown one.

Have an agent do it

If you use Cursor, Claude Code or Codex, paste this:

Set up SnabbSajt in this repository so our marketing team can edit the
website without touching code.

Read https://snabbsajt.com/llms.txt first — it indexes the developer docs.

Steps:
1. Identify which workspace serves our public marketing site (in a monorepo
   this is usually apps/web or apps/marketing). If more than one is
   plausible, list them and ask me — do not guess.
2. Run `npx @snabbsajt/cli init --dry-run` from the repo root and show me
   exactly which files it would create or change.
3. If it looks right, run it for real. It will open a browser once so I can
   approve the connection; tell me when to look.
4. Start the dev server and confirm the site renders locally.
5. Append the SnabbSajt block to AGENTS.md so future agents know that page
   content lives in SnabbSajt and must not be hardcoded into components.

Rules:
- Do not add any other CMS, dependency or deploy step.
- Do not put the SnabbSajt token in a client-exposed env var
  (no NEXT_PUBLIC_ prefix).
- Do not modify anything outside the marketing workspace and the repo-root
  config without telling me first.
- If the CLI does not support our framework, stop and show me the manual
  snippet instead of improvising an integration.

Leaving

sajt.config.json and one env var are the whole integration. There is no lock-in to unwind: keep the published JSON, delete the client, and your app keeps building.


Didn't find the answer, or is something wrong here? Tell us.

Last updated on

On this page