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.
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CollectionSlug } from 'payload'
|
||||
import { PayloadRequest, CollectionSlug } from 'payload'
|
||||
|
||||
const collectionPrefixMap: Partial<Record<CollectionSlug, string>> = {
|
||||
posts: '/posts',
|
||||
@@ -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 = {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<{
|
||||
/>
|
||||
<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>
|
||||
|
||||
@@ -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 []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,3 +22,5 @@ export const desktopBreakpoint = {
|
||||
}
|
||||
|
||||
export const renderedPageTitleID = 'rendered-page-title'
|
||||
|
||||
export const localizedPageTitleID = 'localized-page-title'
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user