Docs
Systhema Design (opens in new tab)
Unreleased

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