payloadcms / payload

Payload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.
https://payloadcms.com
MIT License
23.65k stars 1.51k forks source link

Live Preview breaks when preview frame context changes #4855

Open damnsamn opened 8 months ago

damnsamn commented 8 months ago

Link to reproduction

No response

Describe the Bug

TL;DR

Live Preview stops functioning when the preview frame changes context, either by navigating (away and back to the same page) or by refreshing.

Any changes after a context change result in a console warning and an empty data object being returned. Warning:

Payload Live Preview: No `fieldSchemaJSON` was received from the parent window. Unable to merge data.

This occurs both for @payloadcms/live-preview and @payloadcms/live-preview-react, both exhibiting the same issue. This appears to be a Payload bug, rather than anything specific to the front-end.

Context

My Next (app router) site uses the useLivePreview hook to watch for when data.updatedAt changes, then calls router.refresh()- using Next's dynamic rendering via Draft Mode to fetch the new version of the document, rather than hot-swapping out the page data onChange. I've gone with this strategy to avoid having page-level client components - this is a pre-existing Next app we're migrating from a different CMS, and I'd like to avoid structural changes to the front-end's architecture.

To achieve this, I have a <DraftIndicator> client component that renders in app/layout.tsx if Next is in Draft Mode. Live Preview hits an api route to enable Draft Mode. This component uses the following code to watch for changes to data.updatedAt, refreshing when it changes.

const lastUpdatedAt = useRef<string>();
const { data } = useLivePreview({
  serverURL: process.env.NEXT_PUBLIC_PAYLOAD_URL ?? "",
  initialData: {} as { [key: string]: any },
});

useEffect(() => {
  if (!lastUpdatedAt.current && data.updatedAt) {
    lastUpdatedAt.current = data.updatedAt;
    return;
  }

  if (data.updatedAt && lastUpdatedAt.current !== data.updatedAt) {
    lastUpdatedAt.current = data.updatedAt;
    console.log("Change in `updatedAt` detected, refreshing page.");
    router.refresh();
  }
}, [data]);

I recognise that this is a non-standard and likely unexpected use of the useLivePreview hook, but I believe the underlying bug of frame context changes breaking Payload's Live Preview functionality to still be a valid issue worth resolving. End users will not necessarily intuit that navigating away and then back to the same document would stop the Live Preview from updating.

To Reproduce

  1. Setup a Payload instance, with a collection that utilises Live Preview.
  2. Hook up a front-end that utilises the useLivePreview hook.
  3. Within Payload's admin UI preview frame:
    • Navigate away and back again
    • Right Click > Reload Frame
  4. Update some document data and check the console for the following warning:
    Payload Live Preview: No `fieldSchemaJSON` was received from the parent window. Unable to merge data.
  5. Observe that changes made in the Admin UI are no longer reflected in the preview frame

Payload Version

2.8.2

Adapters and Plugins

@payloadcms/plugin-cloud, @payloadcms/live-preview-react

damnsamn commented 8 months ago

I have observed that the preview's iframe seems to re-render and hard navigate when hosted (Payload Cloud), which does not happen locally - I believe this might be related. This is true even pointing to the same hosted front-end. This issue seems to only occur when hard navigation takes place.

If Payload is hosted locally, all soft navigation stays soft, and this issue does not occur. But if Payload is hosted, all soft navigation becomes hard navigation, and this issue occurs.

andr-ec commented 5 months ago

A workaround that worked in my case was to use a nested iframe. Then even with a reload on navigation on that page would only happen in the nested iframe. and the iframe that payload uses wouldn't navigate.