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
24.65k stars 1.56k forks source link

Live Preview - Async preview URLs with current form data #3922

Closed franknoel closed 10 months ago

franknoel commented 11 months ago

Link to reproduction

https://github.com/payloadcms/payload/tree/main/examples/multi-tenant

Describe the Bug

Hi Payload team, congratulations on the v2.0, and thank you for this amazing project! 🎉

The live preview works great if you know in advance the host of the URL (e.g. 1 site to manage in the CMS).

In a multi-tenant context, the live preview URL of a page needs to be returned dynamically based on the referenced tenant. Unfortunately, the default depth seems to be 1 since the data object returns the tenant ID instead of an object.

I tried to use the local API to find the tenant by ID, but I haven't been able to make it work since the url function is synchronous.

Increasing the default depth or being able to use the local API would fix this issue.

To Reproduce

Simply add a live preview url function to the pages collection that dynamically returns the URL based on the tenant reference.

Payload Version

2.2.0

Adapters and Plugins

jacobsfletch commented 11 months ago

Hey @franknoel I do see your point here, thanks for bringing this up. I think what we need to do is two things:

  1. As you said, the url() function needs to be able to be run async, so that you can perform additional API requests as needed. Depth will not be an option here, though, as all form data is shallow in the admin panel. You'll need to take the ID of the tenant and look up the slug or whatever other data you need to format your tenant's URL.
  2. The data currently being sent to the url() function is the initial page data, and not the current form data. This makes supporting your use case a little trickier, since you want to change the tenant's URL on-the-fly. To do this, we'd need reformat the URL every time form state changes, and then refresh the preview window entirely. Not impossible, but requires some rework.

For these reasons I've labeled this as a new feature. This is definitely something we can support, although its a bit more involved than it appears. What do you think about this?

franknoel commented 11 months ago

Thank you for your answer, that makes total sense. Being able to execute asynchronous logic would be ideal in my case and quite useful for more advanced use cases.

Is there anything I could do to help you with this enhancement?

jacobsfletch commented 10 months ago

The simplest path forward here is to support async urls, but only run it once using initial data. This is bc in order to re-run it on every form state change, you'd also need something like previousDoc to compare the changes to so that you are not constantly re-fetching. That part would be much more complex to implement. But this would probably work for your multi-tenant setup for now. Just if your change tenants, you'd need to save your document then reload the page or navigate back into it.

jacobsfletch commented 10 months ago

@franknoel I threw together a POC just now with #4339 and it seems to be working as I described. One caveat here is that your Live Preview window will now have to wait for a client-side request to be made to format the URL and before rendering the page.

franknoel commented 10 months ago

That's amazing, thank you! Having to wait for the client-side request makes sense since it's now async.

TechSynthesis commented 10 months ago

@franknoel can you share a gist of your multi-tenant implementation of live-preview. I've been trying for days to get it working

geekyayush commented 9 months ago

@franknoel would love if you could share the gist of your multi-tenant implementation of live-preview. thanks.

akserikawa commented 9 months ago

Hey @jacobsfletch thanks for your work done on the POC at #4339. This has served my own multitenancy implementation really well.

Just wanted to share a thought after scratching my brain a bit while dealing with different environments (local, staging & prod). In test/live-preview/utilities/formatLivePreviewURL.ts instead of calling the Tenants API like this:

const fullTenant = await fetch(
        `http://localhost:3000/api/tenants?where[id][equals]=${data.tenant}&limit=1&depth=0`,
      )
        .then((res) => res.json())
        .then((res) => res?.docs?.[0])

        // ...

I've ended up replacing the host to this:

const baseUrl = window.origin;
const fullTenant = await fetch(
        `${baseUrl}/api/tenants?where[id][equals]=${data.tenant}&limit=1&depth=0`,
      )
        .then((res) => res.json())
        .then((res) => res?.docs?.[0])

        // ...

This way my clients can fetch their own Tenant data based on the subdomain from which they are accessing the Payload instance (one instance serves many clients at once) and then the live preview is shown to them independent of the environment the request is triggered, because the payload API host varies.

Just my 2c. Again, thank you so much for your work! It sure saved me hours of mental gymnastics :D

github-actions[bot] commented 1 month ago

This issue has been automatically locked. Please open a new issue if this issue persists with any additional detail.