Skip to content

Commit b62a30a

Browse files
fix(live-preview): reset cache per subscription and ignore invalid preview messages (#13793)
### What? Fix two live preview issues affecting client-side navigation: 1. Stale preview data leaking between pages using `useLivePreview`. 2. Erroneous fetches to `/api/undefined` and incorrect content rendering when preview events lack slugs. ### Why? The live-preview module cached merged preview data globally, which persisted across route changes, causing a new page to render with the previous page’s data. The client attempted to merge when preview events didn’t specify collectionSlug or globalSlug, producing an endpoint of undefined and triggering requests to /api/undefined, sometimes overwriting state with mismatched content. ### How? Clear the internal cache at the time of `subscribe()` so each page using `useLivePreview` starts from a clean slate. In `handleMessage`, only call `mergeData` when `collectionSlug` or `globalSlug` is present; otherwise return `initialData` and perform no request. Fixes #13792
1 parent dfb0021 commit b62a30a

File tree

5 files changed

+58
-3
lines changed

5 files changed

+58
-3
lines changed

packages/live-preview/src/handleMessage.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ const _payloadLivePreview: {
1414
previousData: undefined,
1515
}
1616

17+
// Reset the internal cached merged data. This is useful when navigating
18+
// between routes where a new subscription should not inherit prior data.
19+
export const resetCache = (): void => {
20+
_payloadLivePreview.previousData = undefined
21+
}
22+
1723
export const handleMessage = async <T extends Record<string, any>>(args: {
1824
apiRoute?: string
1925
depth?: number
@@ -27,6 +33,12 @@ export const handleMessage = async <T extends Record<string, any>>(args: {
2733
if (isLivePreviewEvent(event, serverURL)) {
2834
const { collectionSlug, data, globalSlug, locale } = event.data
2935

36+
// Only attempt to merge when we have a clear target
37+
// Either a collectionSlug or a globalSlug must be present
38+
if (!collectionSlug && !globalSlug) {
39+
return initialData
40+
}
41+
3042
const mergedData = await mergeData<T>({
3143
apiRoute,
3244
collectionSlug,

packages/live-preview/src/subscribe.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { CollectionPopulationRequestHandler } from './types.js'
22

3-
import { handleMessage } from './handleMessage.js'
3+
import { handleMessage, resetCache } from './handleMessage.js'
44

55
export const subscribe = <T extends Record<string, any>>(args: {
66
apiRoute?: string
@@ -12,6 +12,10 @@ export const subscribe = <T extends Record<string, any>>(args: {
1212
}): ((event: MessageEvent) => Promise<void> | void) => {
1313
const { apiRoute, callback, depth, initialData, requestHandler, serverURL } = args
1414

15+
// Ensure previous subscription state does not leak across navigations
16+
// by clearing the internal cached data before subscribing.
17+
resetCache()
18+
1519
const onMessage = async (event: MessageEvent) => {
1620
const mergedData = await handleMessage<T>({
1721
apiRoute,

test/live-preview/app/live-preview/_components/Link/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const CMSLink: React.FC<CMSLinkType> = ({
3636
}) => {
3737
const href =
3838
type === 'reference' && typeof reference?.value === 'object' && reference.value.slug
39-
? `/live-preview/${reference.value.slug}`
39+
? `/live-preview${reference.relationTo === 'posts' ? '/posts' : ''}/${reference.value.slug}`
4040
: url
4141

4242
if (!href) {

test/live-preview/seed/header.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,38 @@ export const header: Partial<Header> = {
1313
label: 'Posts',
1414
},
1515
},
16+
{
17+
link: {
18+
type: 'reference',
19+
url: '',
20+
reference: {
21+
relationTo: 'posts',
22+
value: '{{POST_1_ID}}',
23+
},
24+
label: 'Post 1',
25+
},
26+
},
27+
{
28+
link: {
29+
type: 'reference',
30+
url: '',
31+
reference: {
32+
relationTo: 'posts',
33+
value: '{{POST_2_ID}}',
34+
},
35+
label: 'Post 2',
36+
},
37+
},
38+
{
39+
link: {
40+
type: 'reference',
41+
url: '',
42+
reference: {
43+
relationTo: 'posts',
44+
value: '{{POST_3_ID}}',
45+
},
46+
label: 'Post 3',
47+
},
48+
},
1649
],
1750
}

test/live-preview/seed/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,13 @@ export const seed: Config['onInit'] = async (payload) => {
168168

169169
await payload.updateGlobal({
170170
slug: 'header',
171-
data: JSON.parse(JSON.stringify(header).replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)),
171+
data: JSON.parse(
172+
JSON.stringify(header)
173+
.replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)
174+
.replace(/"\{\{POST_1_ID\}\}"/g, post1DocID)
175+
.replace(/"\{\{POST_2_ID\}\}"/g, post2DocID)
176+
.replace(/"\{\{POST_3_ID\}\}"/g, post3DocID),
177+
),
172178
})
173179

174180
await payload.updateGlobal({

0 commit comments

Comments
 (0)