# Set up the live preview

> Let editors see drafts in your website inside the CMS editor, and click blocks to select them.

Source: https://docs.bifrostorbit.com/cms/developers/preview/

## What the editor opens

Beside the editor, the CMS loads your website in a frame at the space's **Preview URL** followed by the page's address and a preview token, such as `https://staging.example.com/products/hiking-boots?preview=eyJ…`. Each token is for one page's draft and lasts an hour; the editor makes new ones as needed. Your website needs:

1. a proxy that sends any address with `?preview=` to the preview route, keeping the page's address;
2. the preview route, which checks the token and turns on draft mode;
3. a page route that reads drafts with `preview: true` while draft mode is on;
4. the bridge, so editors can click blocks and see changes as they type.

Step 14 of [Connect a website](https://docs.bifrostorbit.com/cms/developers/connect-a-website/) has every file in full. The website must be served over `https` and allow the CMS, `https://app.bifrostorbit.com`, to show it in a frame; see [Deploy your website](https://docs.bifrostorbit.com/cms/developers/deploy-your-website/).

## The proxy

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

export function proxy(request: NextRequest) {
  const token = request.nextUrl.searchParams.get('preview')
  if (!token) return NextResponse.next()
  const target = new URL('/api/preview', request.url)
  target.searchParams.set('preview', token)
  target.searchParams.set('path', request.nextUrl.pathname)
  return NextResponse.redirect(target)
}

export const config = {
  matcher: [{ source: '/((?!api/|_next/).*)', has: [{ type: 'query', key: 'preview' }] }],
}
```

Without it, the page opens without draft mode and shows the published version.

## The preview route

```ts
// app/api/preview/route.ts
import { createPreviewHandler } from '@bifrost-orbit-cms/sdk/next'
import { cms } from '@/lib/cms'
export const GET = createPreviewHandler({ client: cms })
```

It checks the CMS preview token, turns on Next.js draft mode and redirects to the page: to `?path=` if given and on your website, otherwise to the page the token is for. A missing token answers `400`, and an invalid or expired one `401`.

To leave draft mode, add an exit route and link to `/api/preview/exit?path=/current-page`:

```ts
// app/api/preview/exit/route.ts
import { createPreviewExitHandler } from '@bifrost-orbit-cms/sdk/next'
export const GET = createPreviewExitHandler()
```

## The bridge

```tsx
// app/[region]/layout.tsx, inside <body>
import { CmsBridge } from '@bifrost-orbit-cms/sdk/next'
<CmsBridge client={cms} />
```

The bridge renders only in draft mode. It lets editors click a block in the preview to select it.

For clicking to work, each component's outer element needs `data-cms-id` set to the block's `id`, such as `<section data-cms-id={block.id}>`. The bridge selects the nearest element with it, so a component without it cannot be picked from the preview, and its parent is selected instead.

Parts of the website that are not the page itself, such as the header, footer and banner, can point editors to where they are edited. Add `data-cms-edit` with what the part is made from, separated by spaces: `global:<key>` for a settings page, `navigation:<key>` for a menu and `banner:<id>` for a banner. `data-cms-edit-label` names it:

```tsx
<header data-cms-edit="global:header navigation:header" data-cms-edit-label="Header">
```

Hovering it in the preview shows a tag with that name, and its menu opens each settings page, menu or banner it lists.

In the editor, clicking a link to another page of the same website opens that page for editing, in the same environment, region and language. Links to other websites open in a new tab, and holding ⌘ or Ctrl while clicking leaves the link to the browser.

## Show changes as editors type

With the bridge alone, the preview reloads each time the editor saves, about a second after they stop typing. To show every change at once, render the page with `CmsLivePage` in a client component:

```tsx
// components/live-page.tsx
'use client'
import { CmsLivePage } from '@bifrost-orbit-cms/sdk/react'
import type { CmsPage } from '@bifrost-orbit-cms/sdk'
import { registry } from '@/cms/registry'

export function LivePage({ page }: { page: CmsPage }) {
  return <CmsLivePage page={page} registry={registry} />
}
```

```tsx
// in your page, where you render the page body
import { CmsPage } from '@bifrost-orbit-cms/sdk/react'
import { LivePage } from '@/components/live-page'

{result.page.draft ? <LivePage page={result.page} /> : <CmsPage page={result.page} registry={registry} />}
```

While the page is shown in the editor, the CMS sends each change to the preview as the delivery API would answer it: images and links filled in, and references expanded if the page was read with `include: ['references']`. `CmsLivePage` swaps the new fields and components into the page and renders them with your registry, so the preview changes as the editor types, without reloading. Outside the editor, and for published pages, it renders exactly like `CmsPage`.

- Your components then also run in the browser while previewing. Keep them free of server-only code, such as reading files, secrets or environment variables that only exist on the server. Visitors to published pages still get them rendered on the server, and the browser code for the preview is only loaded in draft mode.
- Anything outside the page body, such as the header or the title in the browser tab, updates when the editor reloads the preview.
- To render more than the components, for example a template for a content type, use the hook instead: `const live = useLivePage(page)` gives the page with the editor's latest fields and components.
- `onSaved` on `CmsLivePage` or `useLivePage` is called when the editor saves or publishes.

Without the SDK, the bridge gives the same changes to any framework:

```js
window.cmsOrbit.on('page', ({ url, fields, components }) => render(fields, components), { include: ['references'] })
window.cmsOrbit.on('saved', () => { /* the draft was saved or published */ })
```

`window.cmsOrbit` exists once the bridge has loaded, which the bridge announces with a `cms-orbit:ready` event on `window`. A website that listens for `page`, `input` or `saved` handles changes itself, so the editor stops reloading the preview after each save. `input` gives the editor's own copy of the page, with image ids instead of images.

## Entries without a body

Some content types have fields but no body, such as a blog post whose text is a rich text field. When entries of such a type have their own address, the editor shows the fields beside your website's preview of the entry, the same frame and token as for pages. Your page route reads the draft with `preview: true` as it does for any page, and the bridge works the same way:

- The preview reloads each time the editor saves. With `CmsLivePage`, `useLivePage` or `window.cmsOrbit.on('page', …)`, the new fields arrive as the editor types, with `components` empty, so render the entry from `fields`, such as `live.fields.text`.
- There are no blocks to click, so `data-cms-id` is not needed. Editors change the entry in the fields beside the preview.
- `data-cms-edit` works as on pages, so the header, footer and banner still open where they are edited.

Types without an address, such as authors or FAQ entries, have no page of their own, so the editor shows their fields without a website preview. Their entries appear in the preview of the pages that use them. Which editor a collection opens in is set on its content type; see [Choose how a collection is edited](https://docs.bifrostorbit.com/cms/editors/collections/).

## Tell the CMS where the website is

In the space's **Settings**, set the preview address and the device widths editors can switch between. Editors can also copy a share link that opens the preview for someone without an account.
