Skip to content

Commit 3441ffb

Browse files
Frontend /preview/<token> route renders CMS draft snapshots
Fetches the snapshot no-store and renders it through the same StreamRenderer and (static) layout chrome as live pages, so the editor previews exactly what publishing will produce. Draft banner + noindex; expired or malformed tokens get a friendly reopen-from-the-editor message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent c7b6057 commit 3441ffb

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import StreamRenderer from '@/app/components/RichTextRenderer/StreamRenderer';
2+
import {getCMSPreview} from '@/app/utils/api/cmsContent';
3+
import {Box, Flex, Heading, Text} from '@radix-ui/themes';
4+
5+
// Renders a draft snapshot minted by the Wagtail editor's Preview button.
6+
// Same layout chrome and StreamRenderer as the live pages, so what the
7+
// editor sees is what publishing will produce.
8+
export const dynamic = 'force-dynamic';
9+
10+
export const metadata = {
11+
title: 'Draft preview',
12+
robots: {index: false, follow: false},
13+
};
14+
15+
export default async function Page({params}: {params: Promise<{token: string}>}) {
16+
const {token} = await params;
17+
const cmsData = await getCMSPreview(token);
18+
19+
if (!cmsData?.content) {
20+
return (
21+
<Flex width="100%" justify="center" py="9">
22+
<Text>This preview has expired — reopen it from the editor.</Text>
23+
</Flex>
24+
);
25+
}
26+
27+
return (
28+
<Flex direction="column" width="100%" py="6">
29+
<Box className="bg-amber-100 border border-amber-300 rounded-md p-2 text-center mb-4">
30+
<Text size="2" weight="bold">
31+
Draft preview — this version is not published
32+
</Text>
33+
</Box>
34+
<Heading as="h1" size="6" mb="4">
35+
{cmsData.content.title}
36+
</Heading>
37+
<StreamRenderer body={cmsData.content.body} className="my-4" />
38+
</Flex>
39+
);
40+
}

app/src/app/utils/api/cmsContent.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ export const getCMSContent = <T extends CmsContentTypes>(
148148
`/api/content/${type}/slug/${slug}?language=${language}`
149149
);
150150

151+
/**
152+
* Fetch a draft snapshot minted by the Wagtail editor's "Preview on site"
153+
* button. Tokens are single-purpose uuids with a short server-side TTL;
154+
* no-store keeps drafts out of every cache.
155+
*/
156+
export const getCMSPreview = (token: string): Promise<CMSContentResponseWithLanguages | null> =>
157+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(token)
158+
? cmsFetch<CMSContentResponseWithLanguages>(`/api/content/preview/${token}`, {
159+
cache: 'no-store',
160+
})
161+
: Promise.resolve(null);
162+
151163
export const listCMSContent = async (
152164
type: CmsContentTypes,
153165
params: {language?: string} = {}

0 commit comments

Comments
 (0)