Live preview for Payload CMS in Astro: how our new package works
We have released a package that brings Payload CMS live preview to Astro, so editors see draft changes as they type. Here is how to set it up.

If you build Astro sites on top of Payload CMS, you can now give editors a true live preview: as they type in the admin panel, the Astro page next to it re-renders and updates in place. We have packaged the wiring that makes this work and published it as @brainwebuk/astro-payload-live-preview, an open-source npm package. This post explains what it does, how to set it up, and the few things that catch people out.
We built it because Payload ships a live-preview component for React front ends but nothing for Astro, and the gap is annoying enough that we kept rewriting the same glue code on client projects. Rather than keep that to ourselves, we have released it for anyone doing the same kind of Payload and modern JavaScript work the studio does.
What does the package actually do?
It connects Payload's admin panel to a server-rendered Astro site so that saving a document in Payload refreshes the matching Astro page without a full reload. Payload embeds your Astro page in an iframe inside its Live Preview tab and sends a message every time the document is saved. The package listens for that message and re-fetches the route, swapping the new HTML into place.
The mechanism is Payload's server-side live preview, not the client-side React version. Payload supports two modes. The client mode streams field values straight into a React component as you type, which only works if your front end is React. The server mode instead tells the front end "something was saved, go and re-render", which works for any server-rendered framework, Astro included. Our package implements that second mode. The visible effect is the same to an editor: they type, and the preview keeps up.
The one piece that makes it feel genuinely live rather than merely current is Payload's autosave. With autosave set to a low interval, Payload saves the draft every fraction of a second, and each save triggers a refresh. Without autosave you still get an accurate preview, just only when the editor hits save.
How do you set it up?
Setup is three steps: configure live preview in Payload, render our component on the Astro page, and add Astro's ClientRouter so the refresh is smooth. You also need Astro running in server mode and drafts enabled on the collections you want to preview.
Start with the requirements, because two of them are easy to miss. Astro must be in SSR mode, meaning output: 'server' in your config, or at least export const prerender = false on the preview routes. A statically generated page cannot reflect a saved change without a rebuild, so static output and live preview are fundamentally incompatible. You also need Payload 3.x reachable from your Astro server, and drafts plus autosave enabled on the collections you want to preview. Autosave is the part that makes it feel live, so set a low interval such as 100.
First, configure live preview in your Payload config and point it at your Astro site. Include a preview secret in the URL so that only the admin panel can request draft content:
// payload.config.tsexport default buildConfig({ admin: { livePreview: { collections: ['posts'], url: ({ data }) => `${process.env.ASTRO_URL}/posts/${data.id}?preview=${process.env.PREVIEW_SECRET}`, }, }, collections: [ { slug: 'posts', fields: [{ name: 'title', type: 'text', required: true }], versions: { drafts: { autosave: { interval: 100 } } }, }, ],})
Second, install the package and render the component on your Astro page, fetching draft content only when the secret matches:
npm install @brainwebuk/astro-payload-live-preview
---// src/pages/posts/[id].astroimport { RefreshRouteOnSave } from '@brainwebuk/astro-payload-live-preview'const isPreview = Astro.url.searchParams.get('preview') === process.env.PREVIEW_SECRETconst post = await fetchPost(Astro.params.id, { draft: isPreview })if (!post) { return new Response(null, { status: 404 }) }---<h1>{post.title}</h1>{isPreview && <RefreshRouteOnSave serverURL={process.env.PAYLOAD_SERVER_URL} />}
The serverURL is the origin of your Payload admin panel, for example http://localhost:3000. The component uses it to check that incoming messages really came from your admin panel and not from somewhere else, which matters because the page is listening to browser messages.
Third, add Astro's ClientRouter to your layout. This is the part that turns a jarring full-page reload into a smooth in-place swap that keeps the editor's scroll position:
---import { ClientRouter } from 'astro:transitions'---<head> <ClientRouter /></head>
ClientRouter is optional. Without it the package falls back to a normal full-page reload, which still works and still shows the right content. It just flickers, and the editor loses their place on the page each time. For anything beyond a quick trial it is worth adding.
How do you let Astro read draft content?
Your Astro server runs separately from Payload, so it has no automatic right to see drafts, and you have to grant that access deliberately. The pattern is a shared secret: Payload puts a secret in the preview URL, Astro checks it against the same secret from its environment, and only fetches drafts when the two match.
This is why the setup above passes ?preview=... from Payload and compares it on the Astro side before fetching with draft: true. Drafts are unpublished content, so you do not want them readable by anyone who guesses the URL. The secret keeps draft access limited to the admin panel.
There is a further wrinkle if you fetch over Payload's REST API rather than its Local API. Payload's REST endpoints require an authenticated user to return drafts, so a plain server-to-server request will come back without the draft you expect. Authenticate that request, for instance with an API-key user, and the drafts appear. If you use the Local API inside the same process this does not arise, but most Astro-plus-Payload setups run as two separate apps, so it is worth knowing.
What catches people out?
Three things, all of them avoidable once you know them: static output, missing draft authentication, and expecting a refresh on first load. None is a bug in the package. Each is a consequence of how server-side live preview works.
The first is static output, covered above but worth repeating because it is the single most common cause of "the preview is not updating". If Astro builds the page at build time, no amount of saving in Payload will change it until the next build. The preview routes must be server-rendered.
The second is draft authentication over REST. If your preview shows the published version and ignores your edits, the most likely cause is that the draft fetch is not authenticated and Payload is quietly returning the published document instead. Check that the preview secret matches and, if you are on REST, that the request carries credentials.
The third is specific to Astro and worth understanding rather than just working around. Unlike Payload's React component, ours deliberately does not refresh when it first mounts. A server-rendered Astro page is already freshly rendered the moment the iframe loads it, so an extra refresh on mount is redundant, and under the full-reload fallback it would cause an endless reload loop. We removed it on purpose. The practical upshot: the first render is correct as-is, and refreshes happen on save from then on.
What this means for your website
If you run a site on Payload and Astro, this package removes a real friction point: editors can see their changes in context as they make them, instead of saving, switching tabs, and reloading to check their work. That is the difference between a CMS people tolerate and one they trust.
It is also a small example of how we approach client work. When the same glue code shows up on project after project, we would rather build the proper version once, test it where it counts, and share it, than keep reinventing it quietly. The package is MIT-licensed and on npm, with the full source, a working example, and the lower-level helpers on GitHub.
If you are weighing up a build on Astro and a headless CMS, or you have an older site you want moved onto something faster and easier to edit, this is the kind of modern JavaScript and Payload development we do day to day. And if you are choosing the stack itself, our write-up on connecting Payload CMS to Claude with OAuth covers another piece of the same modern-Payload picture.
Join the Fortress.
Monthly insights on security and design delivered with intellectual precision.