Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(live-preview): supports relative urls for dynamic preview deployments #9746

Merged
merged 4 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/live-preview/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ _\* An asterisk denotes that a property is required._

The `url` property is 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 resolve it relative to the application's origin 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
7 changes: 6 additions & 1 deletion packages/next/src/views/LivePreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const LivePreviewView: PayloadServerReactComponent<EditViewComponent> = a
},
]

const url =
let url =
typeof livePreviewConfig?.url === 'function'
? await livePreviewConfig.url({
collectionConfig,
Expand All @@ -47,5 +47,10 @@ export const LivePreviewView: PayloadServerReactComponent<EditViewComponent> = a
})
: 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} />
}
4 changes: 3 additions & 1 deletion test/live-preview/app/live-preview/(pages)/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-restricted-exports */
import { notFound } from 'next/navigation.js'
import React from 'react'

Expand Down Expand Up @@ -32,10 +33,11 @@ export default async function Page({ params: paramsPromise }: Args) {

export async function generateStaticParams() {
process.env.PAYLOAD_DROP_DATABASE = 'false'

try {
const pages = await getDocs<Page>('pages')
return pages?.map(({ slug }) => slug)
} catch (error) {
} catch (_err) {
return []
}
}
4 changes: 2 additions & 2 deletions test/live-preview/app/live-preview/_api/getDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export const getDoc = async <T>(args: {
return docs[0] as T
}
} catch (err) {
console.log('Error getting doc', err)
throw new Error(`Error getting doc: ${err.message}`)
}

throw new Error('Error getting doc')
throw new Error('No doc found')
}
12 changes: 7 additions & 5 deletions test/live-preview/app/live-preview/_api/getDocs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import config from '@payload-config'
import { getPayload } from 'payload'
import { type CollectionSlug, getPayload } from 'payload'

export const getDocs = async <T>(collection: string): Promise<T[]> => {
export const getDocs = async <T>(collection: CollectionSlug): Promise<T[]> => {
const payload = await getPayload({ config })

try {
Expand All @@ -11,10 +11,12 @@ export const getDocs = async <T>(collection: string): Promise<T[]> => {
limit: 100,
})

return docs as T[]
if (docs) {
return docs as T[]
}
} catch (err) {
console.error(err)
throw new Error(`Error getting docs: ${err.message}`)
}

throw new Error('Error getting docs')
throw new Error('No docs found')
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function Footer() {
<img
alt="Payload Logo"
className={classes.logo}
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-light.svg"
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-light.svg"
/>
</picture>
</Link>
Expand All @@ -30,7 +30,7 @@ export async function Footer() {
return <CMSLink key={i} {...link} />
})}
<Link href="/admin">Admin</Link>
<Link href="https://github.com/payloadcms/payload/tree/main/templates/ecommerce">
<Link href="https://github.com/payloadcms/payload/tree/main/test/live-preview">
Source Code
</Link>
<Link href="https://github.com/payloadcms/payload">Payload</Link>
Expand Down
16 changes: 9 additions & 7 deletions test/live-preview/app/live-preview/_components/Link/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import LinkWithDefault from 'next/link.js'
import NextLinkImport from 'next/link.js'
import React from 'react'

import type { Page, Post } from '../../../../payload-types.js'
import type { Props as ButtonProps } from '../Button/index.js'

import { Button } from '../Button/index.js'

const Link = (LinkWithDefault.default || LinkWithDefault) as typeof LinkWithDefault.default
const NextLink = (NextLinkImport.default || NextLinkImport) as typeof NextLinkImport.default

type CMSLinkType = {
appearance?: ButtonProps['appearance']
Expand Down Expand Up @@ -36,20 +36,22 @@ export const CMSLink: React.FC<CMSLinkType> = ({
}) => {
const href =
type === 'reference' && typeof reference?.value === 'object' && reference.value.slug
? `/${reference.value.slug}`
? `/live-preview/${reference.value.slug}`
: url

if (!href) return null
if (!href) {
return null
}

if (!appearance) {
const newTabProps = newTab ? { rel: 'noopener noreferrer', target: '_blank' } : {}

if (href || url) {
return (
<Link {...newTabProps} className={className} href={href || url || ''}>
<NextLink {...newTabProps} className={className} href={href || url || ''}>
{label && label}
{children && children}
</Link>
{children || null}
</NextLink>
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-restricted-exports */
import { notFound } from 'next/navigation.js'
import React from 'react'

Expand Down Expand Up @@ -32,10 +33,11 @@ export default async function Page({ params: paramsPromise }: Args) {

export async function generateStaticParams() {
process.env.PAYLOAD_DROP_DATABASE = 'false'

try {
const pages = await getDocs<Page>('pages')
return pages?.map(({ slug }) => slug)
} catch (error) {
} catch (_err) {
return []
}
}
4 changes: 2 additions & 2 deletions test/live-preview/prod/app/live-preview/_api/getDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export const getDoc = async <T>(args: {
return docs[0] as T
}
} catch (err) {
console.log('Error getting doc', err)
throw new Error(`Error getting doc: ${err.message}`)
}

throw new Error('Error getting doc')
throw new Error('No doc found')
}
12 changes: 7 additions & 5 deletions test/live-preview/prod/app/live-preview/_api/getDocs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import config from '@payload-config'
import { getPayload } from 'payload'
import { type CollectionSlug, getPayload } from 'payload'

export const getDocs = async <T>(collection: string): Promise<T[]> => {
export const getDocs = async <T>(collection: CollectionSlug): Promise<T[]> => {
const payload = await getPayload({ config })

try {
Expand All @@ -11,10 +11,12 @@ export const getDocs = async <T>(collection: string): Promise<T[]> => {
limit: 100,
})

return docs as T[]
if (docs) {
return docs as T[]
}
} catch (err) {
console.error(err)
throw new Error(`Error getting docs: ${err.message}`)
}

throw new Error('Error getting docs')
throw new Error('No docs found')
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function Footer() {
<img
alt="Payload Logo"
className={classes.logo}
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/admin/assets/images/payload-logo-light.svg"
src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/ui/src/assets/payload-logo-light.svg"
/>
</picture>
</Link>
Expand All @@ -30,7 +30,7 @@ export async function Footer() {
return <CMSLink key={i} {...link} />
})}
<Link href="/admin">Admin</Link>
<Link href="https://github.com/payloadcms/payload/tree/main/templates/ecommerce">
<Link href="https://github.com/payloadcms/payload/tree/main/test/live-preview">
Source Code
</Link>
<Link href="https://github.com/payloadcms/payload">Payload</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import LinkWithDefault from 'next/link.js'
import NextLinkImport from 'next/link.js'
import React from 'react'

import type { Page, Post } from '../../../../../payload-types.js'
import type { Props as ButtonProps } from '../Button/index.js'

import { Button } from '../Button/index.js'

const Link = (LinkWithDefault.default || LinkWithDefault) as typeof LinkWithDefault.default
const NextLink = (NextLinkImport.default || NextLinkImport) as typeof NextLinkImport.default

type CMSLinkType = {
appearance?: ButtonProps['appearance']
Expand Down Expand Up @@ -36,20 +36,22 @@ export const CMSLink: React.FC<CMSLinkType> = ({
}) => {
const href =
type === 'reference' && typeof reference?.value === 'object' && reference.value.slug
? `/${reference.value.slug}`
? `/live-preview/${reference.value.slug}`
: url

if (!href) return null
if (!href) {
return null
}

if (!appearance) {
const newTabProps = newTab ? { rel: 'noopener noreferrer', target: '_blank' } : {}

if (href || url) {
return (
<Link {...newTabProps} className={className} href={href || url || ''}>
<NextLink {...newTabProps} className={className} href={href || url || ''}>
{label && label}
{children && children}
</Link>
{children || null}
</NextLink>
)
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/live-preview/seed/posts-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { postsSlug } from '../shared.js'

export const postsPage: Partial<Page> = {
title: 'Posts',
slug: 'live-preview/posts',
slug: 'posts',
meta: {
title: 'Payload Website Template',
description: 'An open-source website built with Payload and Next.js.',
Expand Down
3 changes: 2 additions & 1 deletion test/live-preview/utilities/formatLivePreviewURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const formatLivePreviewURL: LivePreviewConfig['url'] = async ({
collectionConfig,
payload,
}) => {
let baseURL = 'http://localhost:3000/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
Expand All @@ -25,6 +25,7 @@ export const formatLivePreviewURL: LivePreviewConfig['url'] = async ({
.then((res) => res?.docs?.[0])

if (fullTenant?.clientURL) {
// Note: appending a fully-qualified URL here won't work for preview deployments on Vercel
baseURL = `${fullTenant.clientURL}/live-preview`
}
} catch (e) {
Expand Down
Loading