Docs
Next

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:

app/(site)/blog/[slug]/PostView.tsx
'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):

ExportPurpose
SysthemaLivePreviewmode-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
LivePreviewListenerthe bare server-mode listener, for fully hand-rolled routes
systhemaPreviewAdmin({ collection, prefix?, slugField? })the admin.livePreview + preview block — from @systhemaui/payload