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
This commit is contained in:
committed by
GitHub
parent
dfb0021545
commit
b62a30a8dc
@@ -14,6 +14,12 @@ const _payloadLivePreview: {
|
||||
previousData: undefined,
|
||||
}
|
||||
|
||||
// Reset the internal cached merged data. This is useful when navigating
|
||||
// between routes where a new subscription should not inherit prior data.
|
||||
export const resetCache = (): void => {
|
||||
_payloadLivePreview.previousData = undefined
|
||||
}
|
||||
|
||||
export const handleMessage = async <T extends Record<string, any>>(args: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
@@ -27,6 +33,12 @@ export const handleMessage = async <T extends Record<string, any>>(args: {
|
||||
if (isLivePreviewEvent(event, serverURL)) {
|
||||
const { collectionSlug, data, globalSlug, locale } = event.data
|
||||
|
||||
// Only attempt to merge when we have a clear target
|
||||
// Either a collectionSlug or a globalSlug must be present
|
||||
if (!collectionSlug && !globalSlug) {
|
||||
return initialData
|
||||
}
|
||||
|
||||
const mergedData = await mergeData<T>({
|
||||
apiRoute,
|
||||
collectionSlug,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionPopulationRequestHandler } from './types.js'
|
||||
|
||||
import { handleMessage } from './handleMessage.js'
|
||||
import { handleMessage, resetCache } from './handleMessage.js'
|
||||
|
||||
export const subscribe = <T extends Record<string, any>>(args: {
|
||||
apiRoute?: string
|
||||
@@ -12,6 +12,10 @@ export const subscribe = <T extends Record<string, any>>(args: {
|
||||
}): ((event: MessageEvent) => Promise<void> | void) => {
|
||||
const { apiRoute, callback, depth, initialData, requestHandler, serverURL } = args
|
||||
|
||||
// Ensure previous subscription state does not leak across navigations
|
||||
// by clearing the internal cached data before subscribing.
|
||||
resetCache()
|
||||
|
||||
const onMessage = async (event: MessageEvent) => {
|
||||
const mergedData = await handleMessage<T>({
|
||||
apiRoute,
|
||||
|
||||
@@ -36,7 +36,7 @@ export const CMSLink: React.FC<CMSLinkType> = ({
|
||||
}) => {
|
||||
const href =
|
||||
type === 'reference' && typeof reference?.value === 'object' && reference.value.slug
|
||||
? `/live-preview/${reference.value.slug}`
|
||||
? `/live-preview${reference.relationTo === 'posts' ? '/posts' : ''}/${reference.value.slug}`
|
||||
: url
|
||||
|
||||
if (!href) {
|
||||
|
||||
@@ -13,5 +13,38 @@ export const header: Partial<Header> = {
|
||||
label: 'Posts',
|
||||
},
|
||||
},
|
||||
{
|
||||
link: {
|
||||
type: 'reference',
|
||||
url: '',
|
||||
reference: {
|
||||
relationTo: 'posts',
|
||||
value: '{{POST_1_ID}}',
|
||||
},
|
||||
label: 'Post 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
link: {
|
||||
type: 'reference',
|
||||
url: '',
|
||||
reference: {
|
||||
relationTo: 'posts',
|
||||
value: '{{POST_2_ID}}',
|
||||
},
|
||||
label: 'Post 2',
|
||||
},
|
||||
},
|
||||
{
|
||||
link: {
|
||||
type: 'reference',
|
||||
url: '',
|
||||
reference: {
|
||||
relationTo: 'posts',
|
||||
value: '{{POST_3_ID}}',
|
||||
},
|
||||
label: 'Post 3',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -168,7 +168,13 @@ export const seed: Config['onInit'] = async (payload) => {
|
||||
|
||||
await payload.updateGlobal({
|
||||
slug: 'header',
|
||||
data: JSON.parse(JSON.stringify(header).replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)),
|
||||
data: JSON.parse(
|
||||
JSON.stringify(header)
|
||||
.replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)
|
||||
.replace(/"\{\{POST_1_ID\}\}"/g, post1DocID)
|
||||
.replace(/"\{\{POST_2_ID\}\}"/g, post2DocID)
|
||||
.replace(/"\{\{POST_3_ID\}\}"/g, post3DocID),
|
||||
),
|
||||
})
|
||||
|
||||
await payload.updateGlobal({
|
||||
|
||||
Reference in New Issue
Block a user