Docs
Next

Page and post templates

Client-side preview for custom page and post templates.

The built-in Pages and Posts rendering (SysthemaPage) wires SysthemaLivePreview for you, so every page/post template — built-in (default, archive) or your own custom templates — can get instant client-side preview, not just the default one. Two optional fields on a template registration drive it:

FieldPurpose
clientViewA 'use client' component rendered as <clientView data={liveDoc} aux={…} /> under client-side preview. Must be a static module export. Omit it and the template falls back to server-side (refresh-on-save) preview.
resolveDataA server hook ({ payload, pageData/postData, draft, … }) => serializable. Runs once in the render path; its result feeds BOTH the server render and the client overlay as aux. Put any in-component data fetch (payload.find, related rails, archive listings) here so the presentation can render on the client.

Because the client overlay only re-renders from the edited doc plus the request-time aux snapshot, derived data (related posts, archive listings, curated rails) stays fixed while the editor types — correct, since editing this doc can't change other documents — while the doc's own fields (hero, content, tags) update live.

The cleanest custom-template shape is one 'use client' presentational component used as both component and clientView (it reads { data, aux }), with resolveData doing the fetch:

// videosView.tsx — 'use client'
// IMPORTANT: import listing client components from `@systhemaui/payload/next/client`,
// NOT `@systhemaui/payload/next`. The main barrel transitively pulls the server
// plugin graph (icon-pack runtime → Node's `module`), which a `'use client'` file
// can't bundle for the browser — the build fails with `Can't resolve 'module'`.
import { PostsListClient, ArchiveFilterProvider } from '@systhemaui/payload/next/client'
export default function VideosView({ data, aux }: { data: Page; aux?: VideosAux }) {
  return <Article>{/* render from data + aux.archiveData */}</Article>
}

// videosData.ts — server. resolveArchiveData stays on the main barrel.
import { resolveArchiveData } from '@systhemaui/payload/next'
export const resolveVideosData = async ({ payload, pageData, draft }) => ({
  archiveData: await resolveArchiveData({ payload, listing: pageData.videos, draft }),
})

// pageTemplates.ts
import VideosView from './videosView'
import { resolveVideosData } from './videosData'
export const customPageTemplates: SysthemaPayloadPageTemplate[] = [
  {
    name: 'videos',
    component: VideosView,
    clientView: VideosView,
    resolveData: resolveVideosData,
  },
]

For posts, the standard bundle (postSettings, relatedPosts, resolvedAuthor) is always resolved and passed as aux; a post template's resolveData returns only the extra aux it needs, which is merged in. Use previewAux directly on <SysthemaLivePreview> when wiring your own non-template route. Context types: PageTemplateDataContext / PostTemplateDataContext (from @systhemaui/payload).