Here is the gist of it:
In
payload.config.ts
I just mapped the collection slugs to generate a link to the draft route that includes the collection slug as a query parameter.
payload.config.ts
const collections = [...example, example2]
export default buildConfig({
plugins: [
visualEditor({
previewUrl: () => `${draftURL}?secret=1234`,
showPreview: false,
collections: (() => {
const collectionPreviewLinks = {};
Array.from(collections).forEach(collection => {
collectionPreviewLinks[collection.slug] = {
previewUrl: (previewURLData) => {
return `${draftURL}?secret=1234&pageTemplate=${collection.slug}`
}
}
})
return collectionPreviewLinks;
})()
}),
]
})
Here is the draft route in
app/api/draft/route.ts
app/api/draft/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
// route handler enabling draft mode
import { draftMode } from 'next/headers'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const secret = searchParams.get('secret')
const pageTemplate = searchParams.get('pageTemplate')
if (secret != process.env.SOMESECRET) {
return new Response('Invalid token', { status: 401 })
}
switch (pageTemplate) {
case 'pages':
redirectURL = `http://localhost:4001/pages?draft=true`; // draft parameter set here in case the cookie persists outside of payload.
break
case: 'more-pages-here'
...
default:
}
draftMode().enable()
redirect(redirectURL)
}
Now the request is redirected with the draft cookie set and a query parameter of
draft=true
At this point, I have my page component check if the cookie is set and determine which component to render. Both use the same template so you don't have maintain two versions.
app/pages/[[...slug]]/page.tsx
import Client from "./Client";
import Server from "./Server";
import { draftMode } from "next/headers";
export default async function Page({
params,
searchParams,
}: {
params: { slug: string };
searchParams?: { [key: string]: string | string[] | undefined };
}) {
const { isEnabled } = draftMode();
if (isEnabled && searchParams?.draft) {
console.log("Loading Preview...");
return <Client />;
} else {
console.log("Loading Live Page...");
// prettier-ignore
{/* @ts-expect-error Server Component */} // Ignore promise warning?
return <Server params={{ slug: params.slug }} />;
}
}
app/pages/[[...slug]]/Client.tsx
"use client";
import { useEffect, useState } from "react";
import { Post } from "@payload-types";
import Template from "./PageTemplate";
export default function Preview() {
const [props, setProps] = useState<Post | null>(null);
useEffect(() => {
const listener = (event: MessageEvent) => {
if (event.data.cmsLivePreviewData) {
setProps(event.data.cmsLivePreviewData);
}
};
window.addEventListener("message", listener, false);
return () => {
window.removeEventListener("message", listener);
};
}, []);
if (!props) {
return <div>Loading...</div>;
}
return <Template {...props} />;
}
app/pages/[[...slug]]/Server.tsx
import Template from "./PageTemplate";
async function getData(slug: string) {
// Custom Endpoint to get by slug, replace as needed.
const url = `http://localhost:4001/api/pages/slug/${slug}`;
const res = await fetch(url, { cache: "no-cache" });
const pages = await res.json();
let page;
if (pages.hasOwnProperty("docs")) {
page = pages.docs.length > 0 ? pages.docs[0] : pages.docs;
}
return await page;
}
export default async function Post({ params }: { params: { slug: string } }) {
const props = await getData(params.slug);
return <Template {...props} />;
}
app/pages/[[...slug]]/PageTemplate.tsx
import { Post as PostType } from "@payload-types";
export const PostTemplate = (props: PostType) => {
const { title, slug, author, publishedDate, category, tags, content } = props;
return <div className="max-w-xl m-auto">{props.title}</div>;
};
export default PostTemplate;
That looks nice, is very similar to what we do in app router, but we did not use the draft mode functionality thus far. Great work!
But we also use a template in a separate file so we can dynamically load it from the preview or the actual page.tsx
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.