Files
payloadcms/test/live-preview/seed/index.ts
Anders Semb Hermansen b62a30a8dc 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
2025-09-12 18:40:24 +00:00

185 lines
5.1 KiB
TypeScript

import type { Config } from 'payload'
import path from 'path'
import { fileURLToPath } from 'url'
import { devUser } from '../../credentials.js'
import removeFiles from '../../helpers/removeFiles.js'
import { pagesSlug, postsSlug, ssrAutosavePagesSlug, ssrPagesSlug, tenantsSlug } from '../shared.js'
import { footer } from './footer.js'
import { header } from './header.js'
import { home } from './home.js'
import { post1 } from './post-1.js'
import { post2 } from './post-2.js'
import { post3 } from './post-3.js'
import { postsPage } from './posts-page.js'
import { tenant1 } from './tenant-1.js'
import { tenant2 } from './tenant-2.js'
import { trashedPost } from './trashed-post.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export const seed: Config['onInit'] = async (payload) => {
const existingUser = await payload.find({
collection: 'users',
where: {
email: {
equals: devUser.email,
},
},
})
// Seed already ran => this is likely a consecutive, uncached getPayload call
if (existingUser.docs.length) {
return
}
const uploadsDir = path.resolve(dirname, './media')
removeFiles(path.normalize(uploadsDir))
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
})
const tenant1Doc = await payload.create({
collection: tenantsSlug,
data: tenant1,
})
await payload.create({
collection: tenantsSlug,
data: tenant2,
})
const media = await payload.create({
collection: 'media',
filePath: path.resolve(dirname, 'image-1.jpg'),
data: {
alt: 'Image 1',
},
})
const mediaID = payload.db.defaultIDType === 'number' ? media.id : `"${media.id}"`
const tenantID = payload.db.defaultIDType === 'number' ? tenant1Doc.id : `"${tenant1Doc.id}"`
const post1Doc = await payload.create({
collection: postsSlug,
data: JSON.parse(
JSON.stringify(post1)
.replace(/"\{\{IMAGE\}\}"/g, mediaID)
.replace(/"\{\{TENANT_1_ID\}\}"/g, tenantID),
),
})
const post2Doc = await payload.create({
collection: postsSlug,
data: JSON.parse(
JSON.stringify(post2)
.replace(/"\{\{IMAGE\}\}"/g, mediaID)
.replace(/"\{\{TENANT_1_ID\}\}"/g, tenantID),
),
})
const post3Doc = await payload.create({
collection: postsSlug,
data: JSON.parse(
JSON.stringify(post3)
.replace(/"\{\{IMAGE\}\}"/g, mediaID)
.replace(/"\{\{TENANT_1_ID\}\}"/g, tenantID),
),
})
await payload.create({
collection: postsSlug,
data: JSON.parse(
JSON.stringify(trashedPost)
.replace(/"\{\{IMAGE\}\}"/g, mediaID)
.replace(/"\{\{TENANT_1_ID\}\}"/g, tenantID),
),
})
const postsPageDoc = await payload.create({
collection: pagesSlug,
data: JSON.parse(JSON.stringify(postsPage).replace(/"\{\{IMAGE\}\}"/g, mediaID)),
})
let postsPageDocID = postsPageDoc.id
let post1DocID = post1Doc.id
let post2DocID = post2Doc.id
let post3DocID = post3Doc.id
if (payload.db.defaultIDType !== 'number') {
postsPageDocID = `"${postsPageDoc.id}"`
post1DocID = `"${post1Doc.id}"`
post2DocID = `"${post2Doc.id}"`
post3DocID = `"${post3Doc.id}"`
}
await payload.create({
collection: pagesSlug,
data: JSON.parse(
JSON.stringify(home)
.replace(/"\{\{MEDIA_ID\}\}"/g, mediaID)
.replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)
.replace(/"\{\{POST_1_ID\}\}"/g, post1DocID)
.replace(/"\{\{POST_2_ID\}\}"/g, post2DocID)
.replace(/"\{\{POST_3_ID\}\}"/g, post3DocID)
.replace(/"\{\{TENANT_1_ID\}\}"/g, tenantID),
),
})
await payload.create({
collection: ssrPagesSlug,
data: {
...JSON.parse(
JSON.stringify(home)
.replace(/"\{\{MEDIA_ID\}\}"/g, mediaID)
.replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)
.replace(/"\{\{POST_1_ID\}\}"/g, post1DocID)
.replace(/"\{\{POST_2_ID\}\}"/g, post2DocID)
.replace(/"\{\{POST_3_ID\}\}"/g, post3DocID)
.replace(/"\{\{TENANT_1_ID\}\}"/g, tenantID),
),
title: 'SSR Home',
slug: 'home',
},
})
await payload.create({
collection: ssrAutosavePagesSlug,
data: {
...JSON.parse(
JSON.stringify(home)
.replace(/"\{\{MEDIA_ID\}\}"/g, mediaID)
.replace(/"\{\{POSTS_PAGE_ID\}\}"/g, postsPageDocID)
.replace(/"\{\{POST_1_ID\}\}"/g, post1DocID)
.replace(/"\{\{POST_2_ID\}\}"/g, post2DocID)
.replace(/"\{\{POST_3_ID\}\}"/g, post3DocID)
.replace(/"\{\{TENANT_1_ID\}\}"/g, tenantID),
),
title: 'SSR Home',
slug: 'home',
},
})
await payload.updateGlobal({
slug: 'header',
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({
slug: 'footer',
data: JSON.parse(JSON.stringify(footer)),
})
}