astroad
Astro frontend and Payload CMS with MongoDB, run in Docker and deployed through GitHub Actions
Astro frontend wired to a Payload CMS backend with a lightweight live preview approach
Sets up a split project with a Payload CMS backend and an Astro frontend, based on the blank Payload template with Posts, Media and a few globals added. The frontend uses static generation for main routes and server rendering for preview routes, and fetches content through Payload's REST API. SQLite is the default database, and the two apps run in separate terminals.
A template to demonstrate how to connect an Astro frontend and a Payload CMS backend including live preview.
This repository is a GitHub template: create your own copy of it in one click.
Create a repository from this templateThis starter is based on the default blank Payload template with basic collections and configuration added for demonstration purposes. The default database is SQLite to reduce external dependencies. Just clone the repo and start.
/preview/ routes├── backend/ # Payload CMS
│ ├── src/
│ │ ├── collections/ # Content collections (Posts, Media)
│ │ ├── globals/ # Global configs (Header, Landing Page)
│ │ └── payload.config.ts
│ ├── Dockerfile # Default Payload Dockerfile
│ └── docker-compose.yml
│
├── frontend/ # Astro site
│ ├── src/
│ │ ├── components/
│ │ ├── layouts/
│ │ ├── pages/
│ │ │ └── preview/ # Preview routes (SSR)
│ │ └── lib/ # Contains function for fetching globals on both preview and production routes
│ └── astro.config.mjs
Use this template
# Create from template (if using GitHub template feature)
# OR clone directly
git clone https://github.com/frockett/astro-payload-starter.git my-project
cd my-project
Install Dependencies
cd frontend/ && npm install
cd backend/ && npm install
Copy env.example files and set PAYLOAD_SECRET
cp frontend/.env.example frontend/.env
cp backend/.env.example backend/.env
Start development
# Use two terminals
# Terminal 1
cd frontend/ && npm run dev
# Terminal 2
cd backend/ && npm run dev
# frontend/.env
PAYLOAD_URI=http://localhost:3000
# backend/.env
DATABASE_URI=file:./testing-project.db
PAYLOAD_SECRET=YOUR_PAYLOAD_SECRET
CLIENT_URI=http://localhost:4321
PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
This starter uses the following implementation of Live Reload:
// Inside src/components/LivePreview.astro
<script is:inline>
window.addEventListener(
'message',
(event) => {
if (event.data.type === 'payload-document-event') {
// reload the page whenever a document is saved in the admin panel to get the latest data
location.reload()
}
},
false, // Add false to ensure event bubbles up through iframes
)
</script>
This approach does not make use of Payload's live preview hooks. Instead it listens for the payload event type that indicates an update has been saved, and then reloads the entire SSR route. This method is much simpler than the default implementation.
You can improve editing UX by decreasing the autosave interval. In this template it is set to 500ms.
Credit for this implementation goes to Jens from the Payload Discord server.
In src/pages/preview/blog/[slug].astro you can find an alternative implementation that does make use of Payload's hooks.
<!-- ALTERNATIVE SCRIPT USING PAYLOAD LIVE PREVIEW HOOKS -->
<script>
import { subscribe, ready } from "@payloadcms/live-preview";
import { convertLexicalToHTML } from "@payloadcms/richtext-lexical/html";
const serverUrl = "http://localhost:3000";
subscribe({
callback: (mergedData) => {
if (mergedData.title) {
const titleElement = document.querySelector(
'[data-text-field="title"]',
);
if (titleElement) {
titleElement.textContent = mergedData.title;
}
}
if (mergedData.content) {
const contentElement = document.querySelector(
'[data-text-field="content"]',
);
if (contentElement) {
try {
const html = convertLexicalToHTML({ data: mergedData.content });
contentElement.innerHTML = html;
} catch (error) {
console.error("Failed to convert rich text:", error);
contentElement.innerHTML = "<p>[Error converting rich text]</p>";
}
}
}
},
serverURL: serverUrl,
});
ready({
serverURL: serverUrl,
});
</script>
This approach offers "true" live preview but has the disadvantage of requiring implementation for every field type and layout change. The script has been preserved for reference.
Astro frontend and Payload CMS with MongoDB, run in Docker and deployed through GitHub Actions
Blank Payload 3 and Next.js 16 starter with Postgres, Tailwind, shadcn/ui primitives and a built-in SEO module

Blank Payload 3 starter on Next.js 16 with Turso SQLite storage and Cloudflare R2 media uploads