From e095222a9c6d14351c3a118064151ab1dbdc5d5d Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Mon, 9 Dec 2024 11:54:20 -0500 Subject: [PATCH] fix(next): does not format top-level domains within admin.preview or livePreview.url functions (#9831) Fixes #9830. Continuation of #9755 and #9746. Instead of automatically appending TLDs to the `admin.preview` and the `livePreview.url` URLs, we should instead ensure that `req` is passed through these functions, so that you can have full control over the format of this URL without Payload imposing any of its own formatting. --- docs/admin/collections.mdx | 10 ++++++++-- docs/live-preview/overview.mdx | 12 +++++++++--- .../next/src/routes/rest/collections/preview.ts | 9 ++------- packages/next/src/routes/rest/globals/preview.ts | 4 ++-- packages/next/src/views/LivePreview/index.tsx | 12 ++++++------ packages/payload/src/config/types.ts | 5 +++++ templates/website/src/collections/Pages/index.ts | 10 ++++------ templates/website/src/collections/Posts/index.ts | 11 +++++------ .../website/src/utilities/generatePreviewPath.ts | 14 +++++++++++--- .../live-preview/(pages)/[slug]/page.client.tsx | 4 ++-- .../(pages)/ssr-autosave/[slug]/page.tsx | 2 +- .../app/live-preview/(pages)/ssr/[slug]/page.tsx | 2 +- .../(pages)/ssr-autosave/[slug]/page.tsx | 2 +- .../app/live-preview/(pages)/ssr/[slug]/page.tsx | 2 +- test/live-preview/shared.ts | 2 ++ .../live-preview/utilities/formatLivePreviewURL.ts | 6 +++--- 16 files changed, 63 insertions(+), 44 deletions(-) diff --git a/docs/admin/collections.mdx b/docs/admin/collections.mdx index 612febe2c..dc02c4ffc 100644 --- a/docs/admin/collections.mdx +++ b/docs/admin/collections.mdx @@ -108,14 +108,20 @@ export const Posts: CollectionConfig = { } ``` -The `preview` property resolves to a string that points to your front-end application with additional URL parameters. This can be an absolute URL or a relative path. If you are using a relative path, Payload will prepend the application's origin onto it, creating a fully qualified URL. +The `preview` property resolves to a string that points to your front-end application with additional URL parameters. This can be an absolute URL or a relative path. The preview function receives two arguments: | Argument | Description | | --- | --- | | **`doc`** | The Document being edited. | -| **`ctx`** | An object containing `locale` and `token` properties. The `token` is the currently logged-in user's JWT. | +| **`ctx`** | An object containing `locale`, `token`, and `req` properties. The `token` is the currently logged-in user's JWT. | + +If your application requires a fully qualified URL, such as within deploying to Vercel Preview Deployments, you can use the `req` property to build this URL: + +```ts +preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlight-line +``` Note: diff --git a/docs/live-preview/overview.mdx b/docs/live-preview/overview.mdx index ef59472bf..8b3db8946 100644 --- a/docs/live-preview/overview.mdx +++ b/docs/live-preview/overview.mdx @@ -54,8 +54,6 @@ _\* An asterisk denotes that a property is required._ The `url` property resolves to a string that points to your front-end application. This value is used as the `src` attribute of the iframe rendering your front-end. Once loaded, the Admin Panel will communicate directly with your app through `window.postMessage` events. -This can be an absolute URL or a relative path. If you are using a relative path, Payload will prepend the application's origin onto it, creating a fully qualified URL. This is useful for Vercel preview deployments, for example, where URLs are not known ahead of time. - To set the URL, use the `admin.livePreview.url` property in your [Payload Config](../configuration/overview): ```ts @@ -107,8 +105,16 @@ The following arguments are provided to the `url` function: | Path | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------- | | **`data`** | The data of the Document being edited. This includes changes that have not yet been saved. | -| **`documentInfo`** | Information about the Document being edited like collection slug. [More details](../admin/hooks#usedocumentinfo). | | **`locale`** | The locale currently being edited (if applicable). [More details](../configuration/localization). | +| **`collectionConfig`** | The Collection Admin Config of the Document being edited. [More details](../admin/collections). | +| **`globalConfig`** | The Global Admin Config of the Document being edited. [More details](../admin/globals). | +| **`req`** | The Payload Request object. | + +If your application requires a fully qualified URL, such as within deploying to Vercel Preview Deployments, you can use the `req` property to build this URL: + +```ts +url: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlight-line +``` ### Breakpoints diff --git a/packages/next/src/routes/rest/collections/preview.ts b/packages/next/src/routes/rest/collections/preview.ts index db4513d55..086a07e06 100644 --- a/packages/next/src/routes/rest/collections/preview.ts +++ b/packages/next/src/routes/rest/collections/preview.ts @@ -11,7 +11,7 @@ export const preview: CollectionRouteHandlerWithID = async ({ id, collection, re const { searchParams } = req const depth = searchParams.get('depth') - const result = await findByIDOperation({ + const doc = await findByIDOperation({ id, collection, depth: isNumber(depth) ? Number(depth) : undefined, @@ -29,16 +29,11 @@ export const preview: CollectionRouteHandlerWithID = async ({ id, collection, re if (typeof generatePreviewURL === 'function') { try { - previewURL = await generatePreviewURL(result, { + previewURL = await generatePreviewURL(doc, { locale: req.locale, req, token, }) - - // Support relative URLs by prepending the origin, if necessary - if (previewURL && previewURL.startsWith('/')) { - previewURL = `${req.protocol}//${req.host}${previewURL}` - } } catch (err) { return routeError({ collection, diff --git a/packages/next/src/routes/rest/globals/preview.ts b/packages/next/src/routes/rest/globals/preview.ts index d2a20913c..3c4f4025d 100644 --- a/packages/next/src/routes/rest/globals/preview.ts +++ b/packages/next/src/routes/rest/globals/preview.ts @@ -11,7 +11,7 @@ export const preview: GlobalRouteHandler = async ({ globalConfig, req }) => { const { searchParams } = req const depth = searchParams.get('depth') - const result = await findOneOperation({ + const doc = await findOneOperation({ slug: globalConfig.slug, depth: isNumber(depth) ? Number(depth) : undefined, draft: searchParams.get('draft') === 'true', @@ -29,7 +29,7 @@ export const preview: GlobalRouteHandler = async ({ globalConfig, req }) => { if (typeof generatePreviewURL === 'function') { try { - previewURL = await generatePreviewURL(result, { + previewURL = await generatePreviewURL(doc, { locale: req.locale, req, token, diff --git a/packages/next/src/views/LivePreview/index.tsx b/packages/next/src/views/LivePreview/index.tsx index 399cec0e0..cfbb94ff6 100644 --- a/packages/next/src/views/LivePreview/index.tsx +++ b/packages/next/src/views/LivePreview/index.tsx @@ -36,21 +36,21 @@ export const LivePreviewView: PayloadServerReactComponent = a }, ] - let url = + const url = typeof livePreviewConfig?.url === 'function' ? await livePreviewConfig.url({ collectionConfig, data: doc, globalConfig, locale, + req, + /** + * @deprecated + * Use `req.payload` instead. This will be removed in the next major version. + */ payload: initPageResult.req.payload, }) : livePreviewConfig?.url - // Support relative URLs by prepending the origin, if necessary - if (url && url.startsWith('/')) { - url = `${initPageResult.req.protocol}//${initPageResult.req.host}${url}` - } - return } diff --git a/packages/payload/src/config/types.ts b/packages/payload/src/config/types.ts index 1d0fda066..c61e68ecd 100644 --- a/packages/payload/src/config/types.ts +++ b/packages/payload/src/config/types.ts @@ -148,7 +148,12 @@ export type LivePreviewConfig = { data: Record globalConfig?: SanitizedGlobalConfig locale: Locale + /** + * @deprecated + * Use `req.payload` instead. This will be removed in the next major version. + */ payload: Payload + req: PayloadRequest }) => Promise | string) | string } diff --git a/templates/website/src/collections/Pages/index.ts b/templates/website/src/collections/Pages/index.ts index e85fe32a1..079bba267 100644 --- a/templates/website/src/collections/Pages/index.ts +++ b/templates/website/src/collections/Pages/index.ts @@ -48,14 +48,12 @@ export const Pages: CollectionConfig<'pages'> = { return path }, }, - preview: (data) => { - const path = generatePreviewPath({ + preview: (data, { req }) => + generatePreviewPath({ slug: typeof data?.slug === 'string' ? data.slug : '', collection: 'pages', - }) - - return path - }, + req, + }), useAsTitle: 'title', }, fields: [ diff --git a/templates/website/src/collections/Posts/index.ts b/templates/website/src/collections/Posts/index.ts index 897ff654c..7d0567ae2 100644 --- a/templates/website/src/collections/Posts/index.ts +++ b/templates/website/src/collections/Posts/index.ts @@ -54,19 +54,18 @@ export const Posts: CollectionConfig<'posts'> = { const path = generatePreviewPath({ slug: typeof data?.slug === 'string' ? data.slug : '', collection: 'posts', + // req, TODO: thread `req` once 3.5.1 is out, see notes in `generatePreviewPath` }) return path }, }, - preview: (data) => { - const path = generatePreviewPath({ + preview: (data, { req }) => + generatePreviewPath({ slug: typeof data?.slug === 'string' ? data.slug : '', collection: 'posts', - }) - - return path - }, + req, + }), useAsTitle: 'title', }, fields: [ diff --git a/templates/website/src/utilities/generatePreviewPath.ts b/templates/website/src/utilities/generatePreviewPath.ts index cae80d18e..8e1b5a235 100644 --- a/templates/website/src/utilities/generatePreviewPath.ts +++ b/templates/website/src/utilities/generatePreviewPath.ts @@ -1,4 +1,4 @@ -import { CollectionSlug } from 'payload' +import { PayloadRequest, CollectionSlug } from 'payload' const collectionPrefixMap: Partial> = { posts: '/posts', @@ -8,9 +8,10 @@ const collectionPrefixMap: Partial> = { type Props = { collection: keyof typeof collectionPrefixMap slug: string + req?: PayloadRequest // TODO: make this required once 3.5.1 is out, it's a new argument in that version } -export const generatePreviewPath = ({ collection, slug }: Props) => { +export const generatePreviewPath = ({ collection, slug, req }: Props) => { const path = `${collectionPrefixMap[collection]}/${slug}` const params = { @@ -25,5 +26,12 @@ export const generatePreviewPath = ({ collection, slug }: Props) => { encodedParams.append(key, value) }) - return `/next/preview?${encodedParams.toString()}` + let url = `/next/preview?${encodedParams.toString()}` + + // TODO: remove this check once 3.5.1 is out, see note above + if (req) { + url = `${req.protocol}//${req.host}${url}` + } + + return url } diff --git a/test/live-preview/app/live-preview/(pages)/[slug]/page.client.tsx b/test/live-preview/app/live-preview/(pages)/[slug]/page.client.tsx index 09d992946..7e7c4f106 100644 --- a/test/live-preview/app/live-preview/(pages)/[slug]/page.client.tsx +++ b/test/live-preview/app/live-preview/(pages)/[slug]/page.client.tsx @@ -5,7 +5,7 @@ import React from 'react' import type { Page as PageType } from '../../../../payload-types.js' -import { renderedPageTitleID } from '../../../../shared.js' +import { localizedPageTitleID, renderedPageTitleID } from '../../../../shared.js' import { PAYLOAD_SERVER_URL } from '../../_api/serverURL.js' import { Blocks } from '../../_components/Blocks/index.js' import { Gutter } from '../../_components/Gutter/index.js' @@ -38,7 +38,7 @@ export const PageClient: React.FC<{ />
{`For Testing: ${data.title}`}
-
+
{`For Testing (Localized): ${typeof data.relationToLocalized === 'string' ? data.relationToLocalized : data.relationToLocalized?.localizedTitle}`}
diff --git a/test/live-preview/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx b/test/live-preview/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx index 16b71e334..f62a80948 100644 --- a/test/live-preview/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx +++ b/test/live-preview/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx @@ -46,7 +46,7 @@ export async function generateStaticParams() { try { const ssrPages = await getDocs(ssrAutosavePagesSlug) return ssrPages?.map(({ slug }) => slug) - } catch (error) { + } catch (_err) { return [] } } diff --git a/test/live-preview/app/live-preview/(pages)/ssr/[slug]/page.tsx b/test/live-preview/app/live-preview/(pages)/ssr/[slug]/page.tsx index a48a2a04a..6d16c7bf0 100644 --- a/test/live-preview/app/live-preview/(pages)/ssr/[slug]/page.tsx +++ b/test/live-preview/app/live-preview/(pages)/ssr/[slug]/page.tsx @@ -46,7 +46,7 @@ export async function generateStaticParams() { try { const ssrPages = await getDocs(ssrPagesSlug) return ssrPages?.map(({ slug }) => slug) - } catch (error) { + } catch (_err) { return [] } } diff --git a/test/live-preview/prod/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx b/test/live-preview/prod/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx index ab47d80c9..b9d4846e3 100644 --- a/test/live-preview/prod/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx +++ b/test/live-preview/prod/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx @@ -46,7 +46,7 @@ export async function generateStaticParams() { try { const ssrPages = await getDocs(ssrAutosavePagesSlug) return ssrPages?.map(({ slug }) => slug) - } catch (error) { + } catch (_err) { return [] } } diff --git a/test/live-preview/prod/app/live-preview/(pages)/ssr/[slug]/page.tsx b/test/live-preview/prod/app/live-preview/(pages)/ssr/[slug]/page.tsx index b87055e72..f5231edc4 100644 --- a/test/live-preview/prod/app/live-preview/(pages)/ssr/[slug]/page.tsx +++ b/test/live-preview/prod/app/live-preview/(pages)/ssr/[slug]/page.tsx @@ -46,7 +46,7 @@ export async function generateStaticParams() { try { const ssrPages = await getDocs(ssrPagesSlug) return ssrPages?.map(({ slug }) => slug) - } catch (error) { + } catch (_err) { return [] } } diff --git a/test/live-preview/shared.ts b/test/live-preview/shared.ts index 71764c100..927ed7d46 100644 --- a/test/live-preview/shared.ts +++ b/test/live-preview/shared.ts @@ -22,3 +22,5 @@ export const desktopBreakpoint = { } export const renderedPageTitleID = 'rendered-page-title' + +export const localizedPageTitleID = 'localized-page-title' diff --git a/test/live-preview/utilities/formatLivePreviewURL.ts b/test/live-preview/utilities/formatLivePreviewURL.ts index 0e710f57e..ede3d95f6 100644 --- a/test/live-preview/utilities/formatLivePreviewURL.ts +++ b/test/live-preview/utilities/formatLivePreviewURL.ts @@ -3,15 +3,15 @@ import type { LivePreviewConfig } from 'payload' export const formatLivePreviewURL: LivePreviewConfig['url'] = async ({ data, collectionConfig, - payload, + req, }) => { - let baseURL = '/live-preview' + let baseURL = `${req.protocol}//${req.host}/live-preview` // You can run async requests here, if needed // For example, multi-tenant apps may need to lookup additional data if (data.tenant) { try { - const fullTenant = await payload + const fullTenant = await req.payload .find({ collection: 'tenants', where: {