Files
payloadcms/packages/next/src/utilities/initReq.ts
Jacob Fletcher 00a673e491 feat(next): regenerate live preview url on save (#13631)
Closes #12785.

Although your live preview URL can be dynamic based on document data, it
is never recalculated after initial mount. This means if your URL is
dependent of document data that was just changed, such as a "slug"
field, the URL of the iframe does not reflect that change as expected
until the window is refreshed or you navigate back.

This also means that server-side live preview will crash when your
front-end attempts to query using a slug that no longer exists. Here's
the general flow: slug changes, autosave runs, iframe refreshes (url has
old slug), 404.

Now, we execute your live preview function on submit within form state,
and the window responds to the new URL as expected, refreshing itself
without losing its connection.

Here's the result:


https://github.com/user-attachments/assets/7dd3b147-ab6c-4103-8b2f-14d6bc889625

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094211063140
2025-09-23 09:37:15 -04:00

137 lines
3.1 KiB
TypeScript

import type { AcceptedLanguages, I18n, I18nClient } from '@payloadcms/translations'
import type {
ImportMap,
Locale,
Payload,
PayloadRequest,
SanitizedConfig,
SanitizedPermissions,
TypedUser,
} from 'payload'
import { initI18n } from '@payloadcms/translations'
import { headers as getHeaders } from 'next/headers.js'
import {
createLocalReq,
executeAuthStrategies,
getAccessResults,
getPayload,
getRequestLanguage,
parseCookies,
} from 'payload'
import { getRequestLocale } from './getRequestLocale.js'
import { selectiveCache } from './selectiveCache.js'
type Result = {
cookies: Map<string, string>
headers: Awaited<ReturnType<typeof getHeaders>>
languageCode: AcceptedLanguages
locale?: Locale
permissions: SanitizedPermissions
req: PayloadRequest
}
type PartialResult = {
i18n: I18nClient
languageCode: AcceptedLanguages
payload: Payload
responseHeaders: Headers
user: null | TypedUser
}
// Create cache instances for different parts of our application
const partialReqCache = selectiveCache<PartialResult>('partialReq')
const reqCache = selectiveCache<Result>('req')
/**
* Initializes a full request object, including the `req` object and access control.
* As access control and getting the request locale is dependent on the current URL and
*/
export const initReq = async function ({
canSetHeaders,
configPromise,
importMap,
key,
overrides,
}: {
canSetHeaders?: boolean
configPromise: Promise<SanitizedConfig> | SanitizedConfig
importMap: ImportMap
key: string
overrides?: Parameters<typeof createLocalReq>[0]
}): Promise<Result> {
const headers = await getHeaders()
const cookies = parseCookies(headers)
const partialResult = await partialReqCache.get(async () => {
const config = await configPromise
const payload = await getPayload({ config, cron: true, importMap })
const languageCode = getRequestLanguage({
config,
cookies,
headers,
})
const i18n: I18nClient = await initI18n({
config: config.i18n,
context: 'client',
language: languageCode,
})
const { responseHeaders, user } = await executeAuthStrategies({
canSetHeaders,
headers,
payload,
})
return {
i18n,
languageCode,
payload,
responseHeaders,
user,
}
}, 'global')
return reqCache.get(async () => {
const { i18n, languageCode, payload, responseHeaders, user } = partialResult
const { req: reqOverrides, ...optionsOverrides } = overrides || {}
const req = await createLocalReq(
{
req: {
headers,
host: headers.get('host'),
i18n: i18n as I18n,
responseHeaders,
user,
...(reqOverrides || {}),
},
...(optionsOverrides || {}),
},
payload,
)
const locale = await getRequestLocale({
req,
})
req.locale = locale?.code
const permissions = await getAccessResults({
req,
})
return {
cookies,
headers,
languageCode,
locale,
permissions,
req,
}
}, key)
}