Skip to content

Instantly share code, notes, and snippets.

@cbratschi
Created August 19, 2024 14:21
Show Gist options
  • Save cbratschi/c3e67dcf9ea8387fd722eab83d5d9425 to your computer and use it in GitHub Desktop.
Save cbratschi/c3e67dcf9ea8387fd722eab83d5d9425 to your computer and use it in GitHub Desktop.
Payload 2.x Live Preview with Next.js
Parts:
- server component: see page.tsx
- client component: LivePreview
Difference to Payload 3.x:
- updates immediately, does not have to wait for draft to be saved
'use client';
import { useLivePreview } from '@payloadcms/live-preview-react';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect } from 'react';
/**
* Live preview properties.
*/
interface LivePreviewProps {
collection: string
path: string
lang: string
serverUrl: string
}
/**
* Live preview client component.
*
* @param param0
*/
export default function LivePreview({
collection,
path,
serverUrl
}: LivePreviewProps) {
const { data } = useLivePreview({
serverURL: serverUrl,
apiRoute: '/cms/api',
depth: 0,
initialData: null //Note: using only preview data
//initialData: document
});
const pathname = usePathname();
const router = useRouter();
useEffect(() => {
//prevent anchor navigation
const listener = (event: MouseEvent) => {
//debug
//console.dir(event);
//console.dir(event.target);
//check parents
const target = event.target as HTMLElement;
const anchor = target.closest('a');
const form = target.closest('form');
if (!anchor && !form) {
return;
}
if (anchor) {
const href = anchor.getAttribute('href');
if (!href) {
return;
}
}
event.stopPropagation();
event.preventDefault();
};
document.addEventListener('click', listener, true);
return () => {
document.removeEventListener('click', listener, true);
};
}, []);
useEffect(() => {
//data has changed
//debug
//console.log(`Live Preview: ${pathname}`);
//console.dir(data);
//add to URL
if (!data) {
return;
}
//Note: had to increase --max-http-header-size Node.js value
const url = `${pathname}?collection=${encodeURIComponent(collection)}&data=${encodeURIComponent(JSON.stringify(data))}&path=${encodeURIComponent(path)}`;
router.replace(url);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ data ]);
//nothing to render here
return null;
//debug
/*
return (
<>
<h2>Live Preview Data</h2>
{JSON.stringify(data)}
</>
);
*/
}
{
"scripts": {
"next:dev": "NODE_OPTIONS='--max-http-header-size=524288' next dev",
"next:start": "NODE_OPTIONS='--max-http-header-size=524288' next start"
}
}
import { cookies } from 'next/headers';
import LivePreview from './_components/live-preview';
import RenderPreview from './_components/previews';
/**
* Payload document preview properties.
*/
interface PayloadPreviewProps {
params: {
/**
* Language.
*/
lang: string,
},
/**
* URL search parameters.
*/
searchParams: Record<string, string | string[] | undefined>
}
/**
* Payload document preview.
*
* @param param0
* @returns
*/
export default async function PayloadPage({
params: {
lang
},
searchParams
}: PayloadPreviewProps) {
//validate user access
const cookieStore = cookies();
const token = cookieStore.get('payload-token')?.value;
if (!token || !await verifyPayloadToken(token)) {
throw new Error('Payload user token missing or invalid');
}
//debug
//console.log('render preview page');
//console.dir(searchParams);
//check params
// 1) collection
const collection = searchParams.collection;
if (!collection) {
throw new Error('collection missing');
}
// 2) data
//Note: id missing data -> not adding it here, renderers should solely use the preview data and not last data
let data = null;
if (typeof searchParams.data === 'string') {
data = JSON.parse(searchParams.data);
}
// 3) path
let path;
if (typeof searchParams.path === 'string') {
path = searchParams.path;
} else {
throw new Error(`invalid path: ${JSON.stringify(searchParams.path)}`);
}
// 4) ID
let id;
if (typeof searchParams.id === 'string') {
if (searchParams.id !== '0') {
id = searchParams.id;
}
}
//render
const serverUrl = getSiteUrl();
if (!serverUrl) {
throw new Error('invalid server URL (preview)');
}
return (
<>
{ data ? (
<RenderPreview
collection={collection}
data={data}
path={path}
id={id}
lang={lang}
/>
):null }
<LivePreview
collection={collection}
path={path}
lang={lang}
serverUrl={serverUrl}
/>
</>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment