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 initIt 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-runWhat 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 routeThe 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/webRunning 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