---
title: "Live Preview — Server-Side"
aliases: [server-live-preview, RefreshRouteOnSave, server-components-live-preview]
tags: [payloadcms, live-preview, nextjs, react-server-components, drafts]
sources: [raw/live-preview__server.md]
created: 2026-05-15
updated: 2026-05-15
---

## Overview

Server-side Live Preview is for frameworks that support **React Server Components** (e.g. Next.js App Router). It works differently from [[wiki/payloadcms/live-preview-client|client-side Live Preview]]: instead of streaming form-state diffs, it triggers a full server roundtrip on every document save (draft save, autosave, or publish).

**Flow:** Admin Panel emits `window.postMessage` → front-end catches it → calls `router.refresh()` → Next.js re-fetches fresh data from the [[wiki/payloadcms/local-api|Local API]].

> **Not applicable to:** Next.js Pages Router, React Router, Vue 3, Nuxt.js, Svelte (use [[wiki/payloadcms/live-preview-client|client-side]] instead).

---

## React / Next.js App Router

### Install

```bash
npm install @payloadcms/live-preview-react
```

### Usage pattern

Two files: a server page component + a thin `'use client'` wrapper.

**`page.tsx`** (Server Component):
```tsx
import { RefreshRouteOnSave } from './RefreshRouteOnSave'
import { getPayload } from 'payload'
import config from '../payload.config'

export default async function Page() {
  const payload = await getPayload({ config })
  const page = await payload.findByID({
    collection: 'pages',
    id: '123',
    draft: true,
  })

  return (
    <>
      <RefreshRouteOnSave />
      <h1>{page.title}</h1>
    </>
  )
}
```

**`RefreshRouteOnSave.tsx`** (Client Component):
```tsx
'use client'
import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react'
import { useRouter } from 'next/navigation.js'

export const RefreshRouteOnSave = () => {
  const router = useRouter()
  return (
    <PayloadLivePreview
      refresh={() => router.refresh()}
      serverURL={process.env.NEXT_PUBLIC_PAYLOAD_URL}
    />
  )
}
```

The `RefreshRouteOnSave` component must be a Client Component because it subscribes to `window.postMessage`.

---

## Building Your Own Refresh Component

Install the base package (framework-agnostic):

```bash
npm install @payloadcms/live-preview
```

| Export | Purpose |
|--------|---------|
| `ready({ serverURL })` | Tells Admin Panel the front-end is ready to receive messages |
| `isDocumentEvent(event, serverURL)` | Checks if a `MessageEvent` is a document-level event from the Admin Panel |

### Minimal implementation

```tsx
import { isDocumentEvent, ready } from '@payloadcms/live-preview'
import { useCallback, useEffect, useRef } from 'react'

export const RefreshRouteOnSave = ({ refresh, serverURL }) => {
  const hasSentReadyMessage = useRef(false)

  const onMessage = useCallback((event) => {
    if (isDocumentEvent(event, serverURL)) refresh()
  }, [refresh, serverURL])

  useEffect(() => {
    window.addEventListener('message', onMessage)
    if (!hasSentReadyMessage.current) {
      hasSentReadyMessage.current = true
      ready({ serverURL })
    }
    return () => window.removeEventListener('message', onMessage)
  }, [serverURL, onMessage])

  return null
}
```

---

## Key Takeaways

- **Server-side only for RSC frameworks** — requires `router.refresh()` (Next.js App Router pattern); not for SPA-style routing
- **Roundtrip on save** — less real-time than client-side (`useLivePreview`); updates appear only after a document save event
- **Enable Autosave** — set `versions.drafts.autosave.interval: 375` to make server-side feel responsive
- **Split Client/Server** — `RefreshRouteOnSave` must be `'use client'`; the page component stays a Server Component
- **Base package** — `@payloadcms/live-preview` provides `ready` + `isDocumentEvent` for custom non-React implementations
- **CSP gotcha** — if your front-end sets `Content-Security-Policy`, add `frame-ancestors: "self" localhost:* https://your-site.com` to allow the Admin Panel iframe

---

## Troubleshooting

| Symptom | Fix |
|---------|-----|
| Updates lag compared to client-side | Enable Autosave with low interval (`375ms`) |
| Iframe refuses to connect | Add Admin Panel domain to CSP `frame-ancestors` directive |

---

## Related

- [[wiki/payloadcms/live-preview|Live Preview — Overview]] — iframe config, breakpoints, mode selection
- [[wiki/payloadcms/live-preview-client|Live Preview — Client-Side]] — `useLivePreview` hook for SPAs
- [[wiki/payloadcms/live-preview-frontend|Live Preview — Frontend Integration Guide]] — server vs client decision guide
- [[wiki/payloadcms/local-api|Local API]] — what `router.refresh()` hits under the hood
- [[wiki/payloadcms/versions-autosave|Versions & Autosave]] — enable for responsive server-side preview

---

## Sources

- `raw/live-preview__server.md`
- https://payloadcms.com/docs/live-preview/server
