Skip to content

feat: ssr live preview#6239

Merged
jacobsfletch merged 19 commits intobetafrom
feat/ssr-lp
May 8, 2024
Merged

feat: ssr live preview#6239
jacobsfletch merged 19 commits intobetafrom
feat/ssr-lp

Conversation

@jacobsfletch
Copy link
Copy Markdown
Member

@jacobsfletch jacobsfletch commented May 6, 2024

Description

Supports server-rendered Live Preview for use with purely server components. To make this work, the Live Preview view now subscribes to the DocumentEventsProvider to send post message events to your application's window as the document is saved. When your app receives this message, it makes a roundtrip to the server using whatever mechanisms your framework provides. In Next.js, this means simply calling router.refresh() which will hydrate the HTML using new data straight from the Local API.

To do this, render the RefreshRouteOnSave component anywhere in your page.tsx. Here's an example:

page.tsx:

import { RefreshRouteOnSave } from './RefreshRouteOnSave.tsx'

export default async function Page() {
  // get payload, etc.
  const page = await payload.find({ ... })
  
  return (
    <>
      <RefreshRouteOnSave />
      <h1>{page.title}</h1>
    </>
  )
}

RefreshRouteOnSave.tsx:

'use client'

import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react'
import { useRouter } from 'next/navigation.js'
import React from 'react'

export const RefreshRouteOnSave: React.FC = () => {
  const router = useRouter()
  return <PayloadLivePreview refresh={router.refresh} serverURL={process.env.PAYLOAD_SERVER_URL} />
}

Setting up Live Preview in this way is technically far more efficient than client-side via the useLivePreview() hook, because there no secondary merging of data, and zero additional requests made to populate relationships.

Events are currently triggered by autosave, draft save, etc. meaning your collection must have drafts enabled at the very least, with autosave enabled if you want to see updates as you type. Its possible in the future we can tie autosave directly to debounced form state so that SSR Live Preview feels more realtime.

This PR also removes unnecessarily committed built .d.ts files.

  • I have read and understand the CONTRIBUTING.md document in this repository.

@jacobsfletch jacobsfletch marked this pull request as ready for review May 6, 2024 18:43
@jacobsfletch jacobsfletch requested a review from denolfe as a code owner May 6, 2024 18:43
@jacobsfletch jacobsfletch merged commit 731f023 into beta May 8, 2024
@jacobsfletch jacobsfletch deleted the feat/ssr-lp branch May 8, 2024 15:08
@adesugbaa
Copy link
Copy Markdown

What packages need to be installed to try this out? I tried

"@payloadcms/live-preview": "3.0.0-beta.16",
"@payloadcms/live-preview-react": "3.0.0-beta.16",

I also tried

"@payloadcms/live-preview": "0.2.2",
"@payloadcms/live-preview-react": "0.2.2",

3.0.0-beta.26 is not availeble for either. I am unable to find RefreshRouteOnSave export.

Thanks

@adesugbaa
Copy link
Copy Markdown

It would be nice to get sample guidance code. I tried beta-27 - it recognizes the package but I get a code breaking error:

TypeError
(0,_payloadcms_live_preview__WEBPACK_IMPORTED_MODULE_0__.isDocumentEvent) is not a function. (In '(0,_payloadcms_live_preview__WEBPACK_IMPORTED_MODULE_0__.isDocumentEvent)(event, serverURL)', '(0,_payloadcms_live_preview__WEBPACK_IMPORTED_MODULE_0__....

I tried importing @payloadcms/live-preview-react only, then added @payloadcms/live-preview. I get the error above - I am able to view live preview initially but any changes trigger the error and prevent any live change updates.

I do not have any public repo, but I am willing to get on some sharing platform and work with the team.

@jacobsfletch
Copy link
Copy Markdown
Member Author

Hey @adesugbaa you're quick 😄 we have already patched this in beta.28. Check out #6268 for more info.

@adesugbaa
Copy link
Copy Markdown

I am your #1 tester - I love the platform and I am really looking forward to v3.

I have a client project I am working on, so I am constantly updating the packages as we approach RC, and reporting errors.

For Beta 28, I can verify that the error is gone. Is the expectation that the live preview is constantly changing as a user updates a given page within the admin portal? I had to publish the changes and refresh before I saw the live preview changes.

I am not sure if this is specific to my installation but I have always had the following errors in the browser console:

Source Map loading errors
[Error] Failed to load resource: the server responded with a status of 404 (Not Found) (react-toastify.esm.mjs.map, line 0)

There are some strange MongoDB errors that I see regularly.

@adesugbaa
Copy link
Copy Markdown

I went back to the sample code and see some subtle differences.

I used the following:

import configPromise from '@payload-config'
import { getPayloadHMR } from '@payloadcms/next/utilities'

const payload = await getPayloadHMR({ config: configPromise })

I noticed that the sample code has draft hardcoded to true. This was previously derived from

import { draftMode } from 'next/headers'

const { isEnabled: isDraftMode } = draftMode()
console.log('isDraftMode', isDraftMode)

That seems to always return false now. So with the hardcoded draft, the live website will show draft content. What changed with draftMode?

@jacobsfletch
Copy link
Copy Markdown
Member Author

@adesugbaa we use draft: true because the ssr live-preview works by saving drafts. If you're using Next.js draft mode to conditionally set this (as you should) then it sounds like you are just stuck in draft mode and need to clear your cookies (or expose a button that exits draft mode for you). We have an example showing how to do this in depth, check it out: https://github.com/payloadcms/payload/tree/main/examples/draft-preview/payload. That example also demonstrates how to protect access from drafts to only permitted users.

@quornik
Copy link
Copy Markdown

quornik commented May 10, 2024

This is so great! I'll jump in to try it over the weekend. One crucial question: Does it work over rest / gql as well as with internal API?

JessRynkar pushed a commit that referenced this pull request May 13, 2024
JessRynkar pushed a commit that referenced this pull request May 14, 2024
@cbratschi
Copy link
Copy Markdown

There is another way to implement this using the realtime data in server components, not just the last saved draft. We implemented this for Payload 2.x and Next.js where we render mostly server components.

Basically we pass all data as searchParams to page.tsx. In page.tsx we render the preview in our server component and setup a client component which uses the useLivePreview() hook. Whenever the hook delivers new data we navigate to the preview page with modified URL search parameters. In this case Next.js renders the preview content again and displays is right away.

This works fine and only has one drawback: the preview data can exceed the default search params limit. The workaround is to increase the limit in package.json:

"next:dev": "NODE_OPTIONS='--max-http-header-size=524288' next dev",

The advantage is that it does not rely on draft mode or any save intervals. If there's interest I can share more code.

@adesugbaa
Copy link
Copy Markdown

Thanks for the insights @cbratschi.

I would be interested in any code that highlights the setup of this mode.

Thanks again.

@eddyhdzg-solarx
Copy link
Copy Markdown

Does this work in production? or only locally?

@jacobsfletch
Copy link
Copy Markdown
Member Author

Does this work in production? or only locally?

This works in production. There are no restrictions here. Just need to be on Beta 3.0.

@eddyhdzg-solarx
Copy link
Copy Markdown

The 'RefreshRouteOnSave' isn't functioning correctly for me. I attempted to switch from 'useLivePreview', but was unable to resolve the issue.

@eddyhdzg-solarx
Copy link
Copy Markdown

Tried this but no good

"use client";

import { RefreshRouteOnSave as PayloadLivePreview } from "@payloadcms/live-preview-react";
import { useRouter } from "next/navigation.js";

export const RefreshRouteOnSave: React.FC = () => {
  const router = useRouter();

  return (
    <PayloadLivePreview
      refresh={router.refresh}
      serverURL="http://localhost:3000"
    />
  );
};
export default async function Page({ params: { slug = "index" } }: PageParams) {
  const page = await getPage(slug);

  if (page === null) {
    return notFound();
  }

  return (
    <>
      <RefreshRouteOnSave />
      <main>
        <RichText content={page?.content} />
      </main>
    </>
  );
}

@jacobsfletch
Copy link
Copy Markdown
Member Author

@eddyhdzg-solarx the beta branch includes a working example of server-side Live Preview here: https://github.com/payloadcms/payload/tree/beta/examples/live-preview. You'll want to look at specifically this page: https://github.com/payloadcms/payload/blob/beta/examples/live-preview/payload/src/app/(app)/%5Bslug%5D/page.tsx and this component: https://github.com/payloadcms/payload/blob/beta/examples/live-preview/payload/src/app/(app)/%5Bslug%5D/RefreshRouteOnSave.tsx. There's also docs on this branch, but they're not rendered on our site yet: https://github.com/payloadcms/payload/blob/beta/docs/live-preview/server.mdx

Try setting your refresh function as an arrow function like this:

<PayloadLivePreview
  refresh={() => router.refresh()}
  serverURL="http://localhost:3000"
/>

I've experienced issues with this in the past which is why the example is also written this way.

@eddyhdzg-solarx
Copy link
Copy Markdown

Couldn't make it work, I don't know why...

@sriechersrc
Copy link
Copy Markdown

Couldn't make it work, I don't know why...

Did you try to enable drafts on your fetch function as well? I had the same problem, because I only fetched published docs and not the drafts.

  const page = await payload
    .find({
      collection: 'pages',
      locale,
      where: {
        slug: {
          equals: getSlug(slugs),
        },
      },
      draft: true, // I forgot to set this option
    })
    .then(({ docs }) => docs[0])

@benjick
Copy link
Copy Markdown
Contributor

benjick commented Jun 23, 2024

@cbratschi I'm trying to do the same thing with Payload 2, would love some code examples if you could share!

@cbratschi
Copy link
Copy Markdown

@cbratschi I'm trying to do the same thing with Payload 2, would love some code examples if you could share!

Here is the code we are using:

https://gist.github.com/cbratschi/c3e67dcf9ea8387fd722eab83d5d9425

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants