Get started
Connect a website without Next.js
Use the SDK from any JavaScript framework, or call the delivery API over plain HTTP from any language, with preview and refresh on publish.
Tutorial4 min read
CMS Orbit works with any website that can make an HTTP request. Pick the option that fits your stack. Every option uses the same keys and returns the same content.
@bifrost-orbit-cms/sdk/nextOther JavaScriptAstro, Remix, Nuxt, SvelteKit, Express or Workers, with the same typed client.@bifrost-orbit-cms/sdkAny languagePHP, Python, .NET or anything that speaks HTTP and JSON.GET /cms/v1GraphQLA page, its menu and its banners in one query, with the same keys.POST /cms/graphqlCreate the keys first, as in the Next.js tutorial: a public cms_pk_ key for published content and, on the server only, a secret cms_sk_ key for drafts.
1Any JavaScript framework
The client in @bifrost-orbit-cms/sdk needs only fetch, so it runs in Node 20 or later, Bun, Deno, Cloudflare Workers and other edge runtimes. React and Next.js are optional.
import { createCmsClient } from '@bifrost-orbit-cms/sdk'export const cms = createCmsClient({ baseUrl: process.env.CMS_API_URL!, apiKey: process.env.CMS_PUBLIC_KEY!, previewKey: process.env.CMS_PREVIEW_KEY, defaultRegion: 'germany',})Leave out the cache option: it only means something to Next.js. Render a page from the result of resolve, here in an Astro page:
import { cms } from '../lib/cms'const result = await cms.resolve({ path: '/' + (Astro.params.slug ?? '') })if (result.kind === 'redirect') return Astro.redirect(result.to, result.status)if (result.kind === 'gone') return new Response(null, { status: 410 })if (result.kind === 'notFound') return new Response(null, { status: 404 })const page = result.pageThen map each entry in page.components to your own component by its type. React-based frameworks can use CmsPage and defineRegistry from @bifrost-orbit-cms/sdk/react exactly as the Next.js tutorial does. Types from npx cms-orbit generate work in any TypeScript project.
2Any language over HTTP
Every endpoint is a GET under https://api.bifrostorbit.com/cms/v1, with the key as a bearer token:
curl -G "https://api.bifrostorbit.com/cms/v1/resolve" \ -d url=/products/hiking-boots \ -d region=de \ -H "Authorization: Bearer cms_pk_..."The answer is JSON with the content under data. For resolve, data.kind is page, redirect, gone or notFound, and the HTTP status matches: 200, 200, 410 and 404.
{ "data": { "kind": "page", "page": { "url": "/products/hiking-boots", "name": "Hiking boots", "components": [] } }}The same call from Python:
import requestsresponse = requests.get( "https://api.bifrostorbit.com/cms/v1/resolve", params={"url": "/products/hiking-boots", "region": "de"}, headers={"Authorization": "Bearer cms_pk_..."},)result = response.json()["data"]See the Delivery API for every endpoint and parameter, and try them in Configuration › API playground. Answers are cached for up to 60 seconds, so you can cache them on your side for as long.
3GraphQL
GraphQL is at https://api.bifrostorbit.com/cms/graphql and takes the same keys. Use it to fetch a page, its menu and its banners in one request. From the SDK, call cms.graphql(query, { variables }); from anywhere else, POST the query as JSON with the bearer key.
curl 'https://api.bifrostorbit.com/cms/graphql' \ -H 'Authorization: Bearer cms_pk_…' \ -H 'Content-Type: application/json' \ -d '{"query":"query ($url: String!) { page(url: $url, region: \"de\") { name components } navigation(key: \"main\", region: \"de\") { items { label href } } banners(path: $url, region: \"de\") }","variables":{"url":"/products/hiking-boots"}}'See GraphQL for every query, type and error.
4Preview without Next.js
The live preview opens your preview address with ?token=…&path=…. Your website then:
- Checks the token by calling GET
https://api.bifrostorbit.com/cms/preview?token=…. A 200 means it is valid. - Remembers that this visitor is previewing, for example in a cookie, and redirects to
path. - While previewing, reads with the secret key and
draft=true(with the SDK, passpreview: true), and adds the bridge script below. - Puts
data-cms-idwith the component'sidon each component's outer element, so editors can click it to select it.
<script src="https://api.bifrostorbit.com/cms/bridge.js" defer></script><section data-cms-id="{component.id}">…</section>The preview then reloads after each save. To show changes as editors type, see Show changes as editors type in Set up the live preview.
5Refresh on publish without Next.js
Add a POST endpoint and register it in Configuration › Webhooks. Each delivery carries x-orbit-timestamp and x-orbit-signature headers. The signature is the hex HMAC-SHA256 of timestamp + "." + body, keyed with the webhook secret.
import { createHmac, timingSafeEqual } from 'node:crypto'export async function POST(request: Request) { const timestamp = request.headers.get('x-orbit-timestamp') ?? '' const signature = request.headers.get('x-orbit-signature') ?? '' const body = await request.text() const expected = createHmac('sha256', process.env.CMS_WEBHOOK_SECRET!) .update(`${timestamp}.${body}`) .digest('hex') const valid = signature.length === expected.length && timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) const fresh = Math.abs(Date.now() - Number(timestamp)) < 5 * 60 * 1000 if (!valid || !fresh) return new Response(null, { status: 401 }) const { event, data } = JSON.parse(body) await purge(event, data) return new Response(null, { status: 204 })}purge is yours: clear the cached page at data.url, or your whole cache for events without one. Events and webhooks lists every event and what it carries.
Was this page helpful?