Skip to content

Commit e095222

Browse files
authored
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.
1 parent 84abfdf commit e095222

File tree

16 files changed

+63
-44
lines changed

16 files changed

+63
-44
lines changed

docs/admin/collections.mdx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,20 @@ export const Posts: CollectionConfig = {
108108
}
109109
```
110110

111-
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.
111+
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.
112112

113113
The preview function receives two arguments:
114114

115115
| Argument | Description |
116116
| --- | --- |
117117
| **`doc`** | The Document being edited. |
118-
| **`ctx`** | An object containing `locale` and `token` properties. The `token` is the currently logged-in user's JWT. |
118+
| **`ctx`** | An object containing `locale`, `token`, and `req` properties. The `token` is the currently logged-in user's JWT. |
119+
120+
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:
121+
122+
```ts
123+
preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlight-line
124+
```
119125

120126
<Banner type="success">
121127
<strong>Note:</strong>

docs/live-preview/overview.mdx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ _\* An asterisk denotes that a property is required._
5454

5555
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.
5656

57-
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.
58-
5957
To set the URL, use the `admin.livePreview.url` property in your [Payload Config](../configuration/overview):
6058

6159
```ts
@@ -107,8 +105,16 @@ The following arguments are provided to the `url` function:
107105
| Path | Description |
108106
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
109107
| **`data`** | The data of the Document being edited. This includes changes that have not yet been saved. |
110-
| **`documentInfo`** | Information about the Document being edited like collection slug. [More details](../admin/hooks#usedocumentinfo). |
111108
| **`locale`** | The locale currently being edited (if applicable). [More details](../configuration/localization). |
109+
| **`collectionConfig`** | The Collection Admin Config of the Document being edited. [More details](../admin/collections). |
110+
| **`globalConfig`** | The Global Admin Config of the Document being edited. [More details](../admin/globals). |
111+
| **`req`** | The Payload Request object. |
112+
113+
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:
114+
115+
```ts
116+
url: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlight-line
117+
```
112118

113119
### Breakpoints
114120

packages/next/src/routes/rest/collections/preview.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const preview: CollectionRouteHandlerWithID = async ({ id, collection, re
1111
const { searchParams } = req
1212
const depth = searchParams.get('depth')
1313

14-
const result = await findByIDOperation({
14+
const doc = await findByIDOperation({
1515
id,
1616
collection,
1717
depth: isNumber(depth) ? Number(depth) : undefined,
@@ -29,16 +29,11 @@ export const preview: CollectionRouteHandlerWithID = async ({ id, collection, re
2929

3030
if (typeof generatePreviewURL === 'function') {
3131
try {
32-
previewURL = await generatePreviewURL(result, {
32+
previewURL = await generatePreviewURL(doc, {
3333
locale: req.locale,
3434
req,
3535
token,
3636
})
37-
38-
// Support relative URLs by prepending the origin, if necessary
39-
if (previewURL && previewURL.startsWith('/')) {
40-
previewURL = `${req.protocol}//${req.host}${previewURL}`
41-
}
4237
} catch (err) {
4338
return routeError({
4439
collection,

packages/next/src/routes/rest/globals/preview.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const preview: GlobalRouteHandler = async ({ globalConfig, req }) => {
1111
const { searchParams } = req
1212
const depth = searchParams.get('depth')
1313

14-
const result = await findOneOperation({
14+
const doc = await findOneOperation({
1515
slug: globalConfig.slug,
1616
depth: isNumber(depth) ? Number(depth) : undefined,
1717
draft: searchParams.get('draft') === 'true',
@@ -29,7 +29,7 @@ export const preview: GlobalRouteHandler = async ({ globalConfig, req }) => {
2929

3030
if (typeof generatePreviewURL === 'function') {
3131
try {
32-
previewURL = await generatePreviewURL(result, {
32+
previewURL = await generatePreviewURL(doc, {
3333
locale: req.locale,
3434
req,
3535
token,

packages/next/src/views/LivePreview/index.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,21 @@ export const LivePreviewView: PayloadServerReactComponent<EditViewComponent> = a
3636
},
3737
]
3838

39-
let url =
39+
const url =
4040
typeof livePreviewConfig?.url === 'function'
4141
? await livePreviewConfig.url({
4242
collectionConfig,
4343
data: doc,
4444
globalConfig,
4545
locale,
46+
req,
47+
/**
48+
* @deprecated
49+
* Use `req.payload` instead. This will be removed in the next major version.
50+
*/
4651
payload: initPageResult.req.payload,
4752
})
4853
: livePreviewConfig?.url
4954

50-
// Support relative URLs by prepending the origin, if necessary
51-
if (url && url.startsWith('/')) {
52-
url = `${initPageResult.req.protocol}//${initPageResult.req.host}${url}`
53-
}
54-
5555
return <LivePreviewClient breakpoints={breakpoints} initialData={doc} url={url} />
5656
}

packages/payload/src/config/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,12 @@ export type LivePreviewConfig = {
148148
data: Record<string, any>
149149
globalConfig?: SanitizedGlobalConfig
150150
locale: Locale
151+
/**
152+
* @deprecated
153+
* Use `req.payload` instead. This will be removed in the next major version.
154+
*/
151155
payload: Payload
156+
req: PayloadRequest
152157
}) => Promise<string> | string)
153158
| string
154159
}

templates/website/src/collections/Pages/index.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,12 @@ export const Pages: CollectionConfig<'pages'> = {
4848
return path
4949
},
5050
},
51-
preview: (data) => {
52-
const path = generatePreviewPath({
51+
preview: (data, { req }) =>
52+
generatePreviewPath({
5353
slug: typeof data?.slug === 'string' ? data.slug : '',
5454
collection: 'pages',
55-
})
56-
57-
return path
58-
},
55+
req,
56+
}),
5957
useAsTitle: 'title',
6058
},
6159
fields: [

templates/website/src/collections/Posts/index.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,18 @@ export const Posts: CollectionConfig<'posts'> = {
5454
const path = generatePreviewPath({
5555
slug: typeof data?.slug === 'string' ? data.slug : '',
5656
collection: 'posts',
57+
// req, TODO: thread `req` once 3.5.1 is out, see notes in `generatePreviewPath`
5758
})
5859

5960
return path
6061
},
6162
},
62-
preview: (data) => {
63-
const path = generatePreviewPath({
63+
preview: (data, { req }) =>
64+
generatePreviewPath({
6465
slug: typeof data?.slug === 'string' ? data.slug : '',
6566
collection: 'posts',
66-
})
67-
68-
return path
69-
},
67+
req,
68+
}),
7069
useAsTitle: 'title',
7170
},
7271
fields: [

templates/website/src/utilities/generatePreviewPath.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CollectionSlug } from 'payload'
1+
import { PayloadRequest, CollectionSlug } from 'payload'
22

33
const collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {
44
posts: '/posts',
@@ -8,9 +8,10 @@ const collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {
88
type Props = {
99
collection: keyof typeof collectionPrefixMap
1010
slug: string
11+
req?: PayloadRequest // TODO: make this required once 3.5.1 is out, it's a new argument in that version
1112
}
1213

13-
export const generatePreviewPath = ({ collection, slug }: Props) => {
14+
export const generatePreviewPath = ({ collection, slug, req }: Props) => {
1415
const path = `${collectionPrefixMap[collection]}/${slug}`
1516

1617
const params = {
@@ -25,5 +26,12 @@ export const generatePreviewPath = ({ collection, slug }: Props) => {
2526
encodedParams.append(key, value)
2627
})
2728

28-
return `/next/preview?${encodedParams.toString()}`
29+
let url = `/next/preview?${encodedParams.toString()}`
30+
31+
// TODO: remove this check once 3.5.1 is out, see note above
32+
if (req) {
33+
url = `${req.protocol}//${req.host}${url}`
34+
}
35+
36+
return url
2937
}

test/live-preview/app/live-preview/(pages)/[slug]/page.client.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import React from 'react'
55

66
import type { Page as PageType } from '../../../../payload-types.js'
77

8-
import { renderedPageTitleID } from '../../../../shared.js'
8+
import { localizedPageTitleID, renderedPageTitleID } from '../../../../shared.js'
99
import { PAYLOAD_SERVER_URL } from '../../_api/serverURL.js'
1010
import { Blocks } from '../../_components/Blocks/index.js'
1111
import { Gutter } from '../../_components/Gutter/index.js'
@@ -38,7 +38,7 @@ export const PageClient: React.FC<{
3838
/>
3939
<Gutter>
4040
<div id={renderedPageTitleID}>{`For Testing: ${data.title}`}</div>
41-
<div id={renderedPageTitleID}>
41+
<div id={localizedPageTitleID}>
4242
{`For Testing (Localized): ${typeof data.relationToLocalized === 'string' ? data.relationToLocalized : data.relationToLocalized?.localizedTitle}`}
4343
</div>
4444
</Gutter>

test/live-preview/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export async function generateStaticParams() {
4646
try {
4747
const ssrPages = await getDocs<Page>(ssrAutosavePagesSlug)
4848
return ssrPages?.map(({ slug }) => slug)
49-
} catch (error) {
49+
} catch (_err) {
5050
return []
5151
}
5252
}

test/live-preview/app/live-preview/(pages)/ssr/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export async function generateStaticParams() {
4646
try {
4747
const ssrPages = await getDocs<Page>(ssrPagesSlug)
4848
return ssrPages?.map(({ slug }) => slug)
49-
} catch (error) {
49+
} catch (_err) {
5050
return []
5151
}
5252
}

test/live-preview/prod/app/live-preview/(pages)/ssr-autosave/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export async function generateStaticParams() {
4646
try {
4747
const ssrPages = await getDocs<Page>(ssrAutosavePagesSlug)
4848
return ssrPages?.map(({ slug }) => slug)
49-
} catch (error) {
49+
} catch (_err) {
5050
return []
5151
}
5252
}

test/live-preview/prod/app/live-preview/(pages)/ssr/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export async function generateStaticParams() {
4646
try {
4747
const ssrPages = await getDocs<Page>(ssrPagesSlug)
4848
return ssrPages?.map(({ slug }) => slug)
49-
} catch (error) {
49+
} catch (_err) {
5050
return []
5151
}
5252
}

test/live-preview/shared.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ export const desktopBreakpoint = {
2222
}
2323

2424
export const renderedPageTitleID = 'rendered-page-title'
25+
26+
export const localizedPageTitleID = 'localized-page-title'

test/live-preview/utilities/formatLivePreviewURL.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import type { LivePreviewConfig } from 'payload'
33
export const formatLivePreviewURL: LivePreviewConfig['url'] = async ({
44
data,
55
collectionConfig,
6-
payload,
6+
req,
77
}) => {
8-
let baseURL = '/live-preview'
8+
let baseURL = `${req.protocol}//${req.host}/live-preview`
99

1010
// You can run async requests here, if needed
1111
// For example, multi-tenant apps may need to lookup additional data
1212
if (data.tenant) {
1313
try {
14-
const fullTenant = await payload
14+
const fullTenant = await req.payload
1515
.find({
1616
collection: 'tenants',
1717
where: {

0 commit comments

Comments
 (0)