Skip to content

Commit

Permalink
fix(next): does not format top-level domains within admin.preview or …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
jacobsfletch authored Dec 9, 2024
1 parent 84abfdf commit e095222
Show file tree
Hide file tree
Showing 16 changed files with 63 additions and 44 deletions.
10 changes: 8 additions & 2 deletions docs/admin/collections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

<Banner type="success">
<strong>Note:</strong>
Expand Down
12 changes: 9 additions & 3 deletions docs/live-preview/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
9 changes: 2 additions & 7 deletions packages/next/src/routes/rest/collections/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/routes/rest/globals/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand Down
12 changes: 6 additions & 6 deletions packages/next/src/views/LivePreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,21 @@ export const LivePreviewView: PayloadServerReactComponent<EditViewComponent> = 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 <LivePreviewClient breakpoints={breakpoints} initialData={doc} url={url} />
}
5 changes: 5 additions & 0 deletions packages/payload/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,12 @@ export type LivePreviewConfig = {
data: Record<string, any>
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)
| string
}
Expand Down
10 changes: 4 additions & 6 deletions templates/website/src/collections/Pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
11 changes: 5 additions & 6 deletions templates/website/src/collections/Posts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
14 changes: 11 additions & 3 deletions templates/website/src/utilities/generatePreviewPath.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CollectionSlug } from 'payload'
import { PayloadRequest, CollectionSlug } from 'payload'

const collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {
posts: '/posts',
Expand All @@ -8,9 +8,10 @@ const collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {
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 = {
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -38,7 +38,7 @@ export const PageClient: React.FC<{
/>
<Gutter>
<div id={renderedPageTitleID}>{`For Testing: ${data.title}`}</div>
<div id={renderedPageTitleID}>
<div id={localizedPageTitleID}>
{`For Testing (Localized): ${typeof data.relationToLocalized === 'string' ? data.relationToLocalized : data.relationToLocalized?.localizedTitle}`}
</div>
</Gutter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export async function generateStaticParams() {
try {
const ssrPages = await getDocs<Page>(ssrAutosavePagesSlug)
return ssrPages?.map(({ slug }) => slug)
} catch (error) {
} catch (_err) {
return []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export async function generateStaticParams() {
try {
const ssrPages = await getDocs<Page>(ssrPagesSlug)
return ssrPages?.map(({ slug }) => slug)
} catch (error) {
} catch (_err) {
return []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export async function generateStaticParams() {
try {
const ssrPages = await getDocs<Page>(ssrAutosavePagesSlug)
return ssrPages?.map(({ slug }) => slug)
} catch (error) {
} catch (_err) {
return []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export async function generateStaticParams() {
try {
const ssrPages = await getDocs<Page>(ssrPagesSlug)
return ssrPages?.map(({ slug }) => slug)
} catch (error) {
} catch (_err) {
return []
}
}
2 changes: 2 additions & 0 deletions test/live-preview/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ export const desktopBreakpoint = {
}

export const renderedPageTitleID = 'rendered-page-title'

export const localizedPageTitleID = 'localized-page-title'
6 changes: 3 additions & 3 deletions test/live-preview/utilities/formatLivePreviewURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down

0 comments on commit e095222

Please sign in to comment.