# How your website and CMS Orbit work together

> The parts involved when a website shows CMS content, what each one does, and what happens when a visitor opens a page, an editor previews and an editor publishes.

Source: https://docs.bifrostorbit.com/cms/developers/how-it-works/

CMS Orbit is a headless CMS: editors write content in the CMS, and your website reads it over an API and draws it with your own code and design. This page explains the parts and how they fit, so the step-by-step guides make sense. To build it, follow [Connect a website](https://docs.bifrostorbit.com/cms/developers/connect-a-website/).

## The parts

| Part | Where it runs | What it does |
| --- | --- | --- |
| **The editor** | In the editor's browser, on CMS Orbit | Where editors write pages and see a preview of your website beside them |
| **The delivery API** | CMS Orbit, at `https://api.bifrostorbit.com/cms` | Answers your website's questions with published content, or drafts for previews |
| **Your website** | Your hosting | Asks the delivery API for content and draws it as HTML with your components |
| **The SDK** | Inside your website | Makes the calls, caches the answers, draws pages with your components, and handles previews and webhooks |
| **Webhooks** | CMS Orbit calling your website | Tell your website what changed when editors publish |
| **The bridge** | A script loaded into your website while previewing | Connects the preview to the editor: sends changes as editors type, and lets them click blocks |

The SDK is optional. Everything it does is a plain HTTP call or a few lines of code, documented in the [Delivery API](https://docs.bifrostorbit.com/cms/developers/delivery-api/) and [Connect a website without Next.js](https://docs.bifrostorbit.com/cms/developers/connect-any-website/).

## How content is organised

What your website reads is shaped by how the space is set up:

- **A space** is one website or app, with its own content, editors and settings.
- **Environments**, such as Staging and Production, are separate copies of the content. Editors usually work in Staging and promote to Production. A key belongs to one environment and only sees its content.
- **Regions**, such as Germany and Switzerland, are versions of the website that can have different pages. Each has a key, such as `germany`, and may have an address prefix, such as `/de`.
- **Languages** belong to regions. A region has a default language and may have more, such as `de-DE` and `en-DE`. A language can fall back to another when a page is not translated.
- **Content types** define the kinds of entry, such as a page, a blog post or a footer, and their fields.
- **Components** are the blocks editors stack on a page, such as a hero or an FAQ list. Each has a key, such as `hero`, and fields.
- **Pages** are entries with an address, such as `/products/hiking-boots`. The address is stored without the domain or the region prefix.

Your website chooses which environment it reads by the key it uses, and which region and language by what it asks for.

## What a page looks like

A page from the delivery API has its own fields, its components and everything needed for SEO:

```json
{
  "url": "/products/hiking-boots",
  "name": "Hiking boots",
  "contentType": "page",
  "region": "germany",
  "locale": "de-DE",
  "draft": false,
  "publishedAt": "2026-10-01T09:30:00.000Z",
  "version": 7,
  "fields": { "summary": "Boots fitted for your first long walk." },
  "components": [
    {
      "id": "6650f1c2a9e4b3d2c1f0ea11",
      "type": "hero",
      "schemaVersion": 1,
      "variant": null,
      "fields": {
        "heading": "Find a shop",
        "image": { "id": "…", "url": "https://…/uploads/…/hero.jpg", "width": 2400, "height": 1600, "alt": "A pair of hiking boots", "mimeType": "image/jpeg", "title": null }
      },
      "children": {}
    }
  ],
  "seo": { "title": "Hiking boots | Acme Outdoors", "description": "…", "canonical": "https://www.example.com/de/products/hiking-boots", "…": "…" }
}
```

- **`components`** is a list, in the order editors placed them. Each has an `id`, a `type` (the component key), `fields`, a `variant` if editors chose one, and `children`: components placed inside it, grouped by slot name.
- **Images, links and references** arrive filled in. An image has its address, size and alt text; a link has its current `href`, so renaming a page never breaks links to it.
- **Rich text** is HTML.
- **`seo`** has the finished title, description, canonical address, Open Graph, Twitter card, hreflang alternates, breadcrumbs and JSON-LD, with defaults applied. The SDK's `toMetadata` turns it into Next.js metadata.

The SDK adds `meta`, which gathers the region, language, fallback and draft details in one place. [Field types](https://docs.bifrostorbit.com/cms/developers/field-types/) shows what each field type looks like, and the [Delivery API](https://docs.bifrostorbit.com/cms/developers/delivery-api/) every endpoint.

## What happens when a visitor opens a page

1. The visitor asks your website for `/products/hiking-boots`.
2. If your website has a fresh rendered copy, it sends it. The CMS is not involved. This is what happens almost every time.
3. Otherwise your page route calls `cms.resolve({ path: '/products/hiking-boots' })`. The SDK sends `GET /resolve?url=/products/hiking-boots&region=germany` with the public key.
4. The CMS answers what is at that address: a page, a redirect, gone or not found.
5. Your route renders the page with your components, or redirects, or shows the 404 page.
6. Next.js keeps the answer and the rendered page, labelled with the page's cache tag, `cms:page:germany:/products/hiking-boots`.

If the CMS cannot be reached, the SDK tries again twice, and Next.js keeps serving the copy it has. [Caching and refreshing pages](https://docs.bifrostorbit.com/cms/developers/caching-and-refresh/) explains every cache involved.

## What happens when an editor publishes

1. The editor selects **Publish**. The page's draft becomes its published version.
2. The CMS sends a signed `POST` to each webhook for that environment, such as `{"event":"content.published","data":{"url":"/products/hiking-boots","region":"germany",…}}`.
3. Your webhook route checks the signature and turns the message into cache tags: the page's tag, plus lists, sitemaps and pages that show references.
4. Next.js marks those tags as expired. Nothing is fetched yet.
5. The next visitor to that page waits a moment while your website fetches the new version and renders it. Everyone after gets the new copy from the cache.

Without a webhook, the change still appears, once the cached copy is older than your `revalidate` setting.

*Diagram: What happens between an editor publishing and your website showing it.*

## What happens when an editor previews

1. The editor opens a page. The CMS creates a preview token for that draft, valid for an hour, and loads your website in a frame at the **Preview URL** from the space's settings plus the page's address, such as `https://staging.example.com/products/hiking-boots?preview=eyJ…`.
2. Your proxy sees `?preview=` and redirects to your preview route.
3. The preview route asks the CMS whether the token is valid, turns on Next.js draft mode, which sets a cookie, and redirects back to the page.
4. The page route sees draft mode and calls `resolve` with `preview: true`. The SDK uses the preview key, adds `draft=true` and skips every cache.
5. The layout adds the bridge script, because draft mode is on.
6. The bridge connects to the editor. As the editor types, it sends the changed page to `CmsLivePage`, which redraws it. When the editor clicks a block in the preview, the bridge finds the nearest element with `data-cms-id` and tells the editor to select it.

Visitors never have the draft mode cookie, so they never see drafts, never load the bridge and are never slowed down by previews. See [Set up the live preview](https://docs.bifrostorbit.com/cms/developers/preview/).

## Addresses, redirects and gone pages

Your website does not need its own list of pages or redirects. `resolve` answers for any address:

| Answer | When |
| --- | --- |
| `page` | A page is published at the address, in the region and language asked for, or in a fallback |
| `redirect` | An editor added a redirect from the address, or a page used to be there and moved. Chains are followed for you, and `hops` says how many. |
| `gone` | An editor marked the address as gone, for a page removed on purpose |
| `notFound` | Nothing is at the address |

When an editor changes a page's address and publishes, the CMS adds a redirect from the old address by itself, and links and menus pointing at the page use the new one.

### Gone pages

A gone page tells search engines the page was removed on purpose, so they drop it sooner. The right status is `410`. Next.js can only answer `404` from a page, so the SDK's `gone()` calls `notFound()` and shows your 404 page. That is enough for most websites.

To answer a true `410`, check the address before the page renders, in `proxy.ts`. This costs one CMS call for every page request, so most websites do not do it:

```ts
// proxy.ts
import { NextResponse, type NextRequest } from 'next/server'
import { cms } from '@/lib/cms'

export async function proxy(request: NextRequest) {
  const result = await cms.resolve({ path: request.nextUrl.pathname }).catch(() => null)
  if (result?.kind === 'gone') return new NextResponse('This page has been removed.', { status: 410 })
  return NextResponse.next()
}

export const config = { matcher: ['/((?!api/|_next/|.*\\..*).*)'] }
```

The proxy runs before Next.js's caches, so this call is made for every request it matches, cached copies included. Keep the `matcher` as narrow as you can.

## What the SDK does and does not do

The SDK does:

- make every delivery API call, with retries, timeouts and typed answers;
- label answers with cache tags and refresh the right ones from webhooks;
- draw pages from your registry of components, and keep the preview live;
- turn SEO fields into metadata, JSON-LD, sitemaps and robots.txt;
- check preview tokens and webhook signatures;
- generate TypeScript types from your schema;
- give you a mock client for tests.

It does not:

- **style anything.** Every component is yours.
- **sanitise rich text.** It is HTML written by your editors; see [Render components](https://docs.bifrostorbit.com/cms/developers/render-blocks/).
- **resize images on your server.** Images are served by the CMS, which can make smaller copies; `CmsImage` asks for them.
- **search full text.** `list({ q })` matches entry names. Use a search service for full-text search.
- **route regions or languages for you.** Your folders decide the addresses; see [Regions and languages](https://docs.bifrostorbit.com/cms/developers/regions-and-languages/).
- **write content.** The delivery API is read-only. Scripts that change content use the [Management API](https://docs.bifrostorbit.com/cms/developers/management-api/), and AI agents use [MCP](https://docs.bifrostorbit.com/cms/developers/connect-an-ai-agent/).
