Your own collections
Live preview for collections you define.
When you add your own collection on top of Systhema, you get the same live preview — server/client mode, the localhost cookie fix, the auth-gated /sys/preview route — without re-implementing any of it. Two pieces:
1. Point the admin at the preview with systhemaPreviewAdmin (replaces the hand-rolled generatePreviewPath pair):
import { systhemaPreviewAdmin } from '@systhemaui/payload'
export const Posts: CollectionConfig = {
slug: 'posts',
versions: { drafts: true },
admin: {
// `prefix` is where your route lives; the preview path becomes `${prefix}/${slug}`.
...systhemaPreviewAdmin({ collection: 'posts', prefix: '/blog' }),
},
fields: [/* slug, title, ... */],
}2. Wrap your render in <SysthemaLivePreview> with a single view — one 'use client' component that renders both the published page and the live overlay (server and client mode). This is the whole integration:
// app/(site)/blog/[slug]/page.tsx — a Server Component
import { draftMode } from 'next/headers'
import { notFound } from 'next/navigation'
import { getPayload } from 'payload'
import config from '@payload-config'
import { SysthemaLivePreview, getPreviewDepth } from '@systhemaui/payload/next'
import PostView from './PostView'
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const { isEnabled: draft } = await draftMode()
const payload = await getPayload({ config })
const { docs } = await payload.find({
collection: 'posts',
where: { slug: { equals: slug } },
draft,
depth: getPreviewDepth(), // one depth, shared with the live overlay
limit: 1,
overrideAccess: draft,
})
const post = docs[0]
if (!post) notFound()
return <SysthemaLivePreview collection="posts" initialData={post} view={PostView} />
}view takes { data } (the doc — live, unsaved form state while previewing) and must be a 'use client' component, since its reference crosses the server→client boundary:
'use client'
import type { Post } from '@/payload-types'
export default function PostView({ data }: { data: Post }) {
return (
<main>
<h1>{data.title}</h1>
{/* ... */}
</main>
)
}The wrapper reads livePreview.mode itself — flip the global setting once and every collection follows. Exports (all from @systhemaui/payload/next unless noted):
| Export | Purpose |
|---|---|
SysthemaLivePreview | mode-aware wrapper; pass a single view (simple) or children + clientView (advanced) |
LivePreviewClientView<T> | type for your 'use client' view/clientView renderer ({ data, aux }) |
getPreviewDepth() | the live-preview depth; pass to your payload.find({ depth }) |
useSysthemaLivePreview<T> | low-level client hook for custom layouts / multiple previewed regions |
LivePreviewListener | the bare server-mode listener, for fully hand-rolled routes |
systhemaPreviewAdmin({ collection, prefix?, slugField? }) | the admin.livePreview + preview block — from @systhemaui/payload |