fix(next): allows relative live preview urls (#11083)
We now properly allow relative live preview URLs which is handy if you're deploying on a platform like Vercel and do not know what the preview domain is going to end up being at build time. This PR also removes some problematic code in the website template which hard-codes the protocol to `https://` in production even if you're running locally. Fixes #11070
This commit is contained in:
@@ -109,7 +109,9 @@ The following arguments are provided to the `url` function:
|
||||
| **`globalConfig`** | The Global Admin Config of the Document being edited. [More details](../configuration/globals#admin-options). |
|
||||
| **`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:
|
||||
You can return either an absolute URL or relative URL from this function. If you don't know the URL of your frontend at build-time, you can return a relative URL, and in that case, Payload will automatically construct an absolute URL by injecting the protocol, domain, and port from your browser window. Returning a relative URL is helpful for platforms like Vercel where you may have preview deployment URLs that are unknown at build time.
|
||||
|
||||
If your application requires a fully qualified URL, or you are attempting to preview with a frontend on a different domain, you can use the `req` property to build this URL:
|
||||
|
||||
```ts
|
||||
url: ({ data, req }) => `${req.protocol}//${req.host}/${data.slug}` // highlight-line
|
||||
|
||||
@@ -61,6 +61,14 @@ type Props = {
|
||||
readonly serverURL: string
|
||||
} & DocumentSlots
|
||||
|
||||
const getAbsoluteUrl = (url) => {
|
||||
try {
|
||||
return new URL(url, window.location.origin).href
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
const PreviewView: React.FC<Props> = ({
|
||||
collectionConfig,
|
||||
config,
|
||||
@@ -552,7 +560,7 @@ export const LivePreviewClient: React.FC<
|
||||
readonly url: string
|
||||
} & DocumentSlots
|
||||
> = (props) => {
|
||||
const { breakpoints, url } = props
|
||||
const { breakpoints, url: incomingUrl } = props
|
||||
const { collectionSlug, globalSlug } = useDocumentInfo()
|
||||
|
||||
const {
|
||||
@@ -564,6 +572,11 @@ export const LivePreviewClient: React.FC<
|
||||
getEntityConfig,
|
||||
} = useConfig()
|
||||
|
||||
const url =
|
||||
incomingUrl.startsWith('http://') || incomingUrl.startsWith('https://')
|
||||
? incomingUrl
|
||||
: getAbsoluteUrl(incomingUrl)
|
||||
|
||||
const { isPopupOpen, openPopupWindow, popupRef } = usePopupWindow({
|
||||
eventType: 'payload-live-preview',
|
||||
url,
|
||||
|
||||
@@ -11,7 +11,7 @@ type Props = {
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
export const generatePreviewPath = ({ collection, slug, req }: Props) => {
|
||||
export const generatePreviewPath = ({ collection, slug }: Props) => {
|
||||
const encodedParams = new URLSearchParams({
|
||||
slug,
|
||||
collection,
|
||||
@@ -19,12 +19,7 @@ export const generatePreviewPath = ({ collection, slug, req }: Props) => {
|
||||
previewSecret: process.env.PREVIEW_SECRET || '',
|
||||
})
|
||||
|
||||
const isProduction =
|
||||
process.env.NODE_ENV === 'production' || Boolean(process.env.VERCEL_PROJECT_PRODUCTION_URL)
|
||||
|
||||
const protocol = isProduction ? 'https:' : req.protocol
|
||||
|
||||
const url = `${protocol}//${req.host}/next/preview?${encodedParams.toString()}`
|
||||
const url = `/next/preview?${encodedParams.toString()}`
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ type Props = {
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
export const generatePreviewPath = ({ collection, slug, req }: Props) => {
|
||||
export const generatePreviewPath = ({ collection, slug }: Props) => {
|
||||
const encodedParams = new URLSearchParams({
|
||||
slug,
|
||||
collection,
|
||||
@@ -19,12 +19,7 @@ export const generatePreviewPath = ({ collection, slug, req }: Props) => {
|
||||
previewSecret: process.env.PREVIEW_SECRET || '',
|
||||
})
|
||||
|
||||
const isProduction =
|
||||
process.env.NODE_ENV === 'production' || Boolean(process.env.VERCEL_PROJECT_PRODUCTION_URL)
|
||||
|
||||
const protocol = isProduction ? 'https:' : req.protocol
|
||||
|
||||
const url = `${protocol}//${req.host}/next/preview?${encodedParams.toString()}`
|
||||
const url = `/next/preview?${encodedParams.toString()}`
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
2
test/live-preview/next-env.d.ts
vendored
2
test/live-preview/next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -135,6 +135,9 @@ export interface Page {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
@@ -169,6 +172,9 @@ export interface Page {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('default' | 'primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
@@ -202,12 +208,18 @@ export interface Page {
|
||||
value: string | Post;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* This field is auto-populated after-read
|
||||
*/
|
||||
populatedDocs?:
|
||||
| {
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* This field is auto-populated after-read
|
||||
*/
|
||||
populatedDocsTotal?: number | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
@@ -367,6 +379,9 @@ export interface Post {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
@@ -401,6 +416,9 @@ export interface Post {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('default' | 'primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
@@ -434,12 +452,18 @@ export interface Post {
|
||||
value: string | Post;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* This field is auto-populated after-read
|
||||
*/
|
||||
populatedDocs?:
|
||||
| {
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* This field is auto-populated after-read
|
||||
*/
|
||||
populatedDocsTotal?: number | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
@@ -510,6 +534,9 @@ export interface Ssr {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
@@ -544,6 +571,9 @@ export interface Ssr {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('default' | 'primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
@@ -577,12 +607,18 @@ export interface Ssr {
|
||||
value: string | Post;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* This field is auto-populated after-read
|
||||
*/
|
||||
populatedDocs?:
|
||||
| {
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* This field is auto-populated after-read
|
||||
*/
|
||||
populatedDocsTotal?: number | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
@@ -641,6 +677,9 @@ export interface SsrAutosave {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
@@ -675,6 +714,9 @@ export interface SsrAutosave {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('default' | 'primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
@@ -708,12 +750,18 @@ export interface SsrAutosave {
|
||||
value: string | Post;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* This field is auto-populated after-read
|
||||
*/
|
||||
populatedDocs?:
|
||||
| {
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* This field is auto-populated after-read
|
||||
*/
|
||||
populatedDocsTotal?: number | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
@@ -1345,6 +1393,9 @@ export interface Header {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('default' | 'primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
@@ -1375,6 +1426,9 @@ export interface Footer {
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('default' | 'primary' | 'secondary') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
|
||||
@@ -5,7 +5,7 @@ export const formatLivePreviewURL: LivePreviewConfig['url'] = async ({
|
||||
collectionConfig,
|
||||
req,
|
||||
}) => {
|
||||
let baseURL = `${req.protocol}//${req.host}/live-preview`
|
||||
let baseURL = `/live-preview`
|
||||
|
||||
// You can run async requests here, if needed
|
||||
// For example, multi-tenant apps may need to lookup additional data
|
||||
|
||||
Reference in New Issue
Block a user