Caisy live preview with Qwik

Setting up the Live Preview in caisy

First go to your project settings in caisy and navigate to development -> previews and click on the "create preview" button

live_preview_empty

Secondly give your Preview any name and add a blueprint to it (we will use Page in this example, please use the same blueprint to follow along)

live_preview_blueprint

After that add the Preview URL: here we need to link to the an API page we’ll be creating later, we also need to include the slug parameter, this is the slug field we have on the Page blueprint, we use it to identify the part of the URL that will take us to the details of each blog article, for example /about
Further we add the project_id as a token, so we do not have to expose the the project_id as a public variable.

So the final input url for you Page preview should look like this: http://localhost:3000/{document_field.slug}?project_id={project_id} if your app is running on port 3000.

Caisy will automatically replace the params in this url and attach another param: caisy_preview_access_token to your preview url with your current token, when you open this preview in the ui.

Lastly you need to also turn on the Enable Live Preview toggle

live_preview_url

The changes for you previews save automatically


Changes to the frontend code

First of all, we need to install @caisy/live-preview-qwik libraries:

yarn add @caisy/live-preview-qwik

or

npm install @caisy/live-preview-qwik

The live preview logic for caisy consists of four main parts:

  • Setup event connection to caisy by calling using caisyLivePreview

  • Wrap you graphql response with useCaisyUpdates

  • Highlight editable components: Adding the global css for component edit editing styles and add "data-caisy-field-name" and "data-caisy-document-id" attributes to your html elements with a helper

  • Connection indicator: Showcase the current connection to the live preview data stream.


Connecting to caisy using caisyLivePreview

Here is an example that uses the nextjs router (pages) to pull the relevant params form the current page url and initates the caisyLivePreview

    const previewToken = useSignal<string | null>(null);
    const xlocale = "en";

    useVisibleTask$(async () => {
        const w = typeof window === "undefined" ? null : window;
        if (!w) return;

        const urlParams = new URLSearchParams(window.location.search);
        const caisy_preview_access_token = urlParams.get("caisy_preview_access_token");
        previewToken.value = caisy_preview_access_token;

        let close: any = null;

        const start = async () => {
            const token = previewToken.value;
            if (!token) return;

            close = livePreviewQwik.caisyLivePreview({
                projectId: props.projectId,
                token,
                locale: xlocale,
                enabled: true,
            });
        };
        start();

        return () => {
            close && close();
        };
    });

The xlocale is just a placeholder for your current locale here.

This livePreviewQwik.caisyLivePreview function initiates the live preview, and it takes 4 properties:

  • projectId: this is the ID of the project we fetch the content from - you can also fetch it from the url param (project_id)

  • token: the preview access token coming from your account in caisy, to get this token read it form the current url.

  • locale(optional, default: en): since we have the functionality to add different locales to caisy, we can sync them here to fetch the correct data from the Document

  • enabled(optional, default: true): a boolean value to dynamically enable/disable the live-preview, in the example we use a primitive NEXT_PUBLIC_USE_DRAFT_MODE env variable, but this can be more dynamic

This useVisibleTask can run anywhere in your app. But if this has not been called yet, everything else will not work.


The useCaisyUpdates hook

This is how we connect caisy with our application, it takes all the documents/components we fetch from caisy as props, it then takes care of listening to the caisy documents, and reflects the changes on the value, as a result, it returns all the same fields it takes.
In order for this to work the results need to be fetched trough graphql and every document besides it fields also needs to have the id and __typename fetched from the external api. Like you see here:

 {
	allPage{
    edges{
      node{
        id
        __typename
        components{
          ... on NewsletterSignup{
            id
            __typename
            headline
            subheadline
          }
          ... on Headline{
            id 
            __typename
            headline
            subheadline
          }
          ... on Fulltext{
            id
            __typename
            text{
              json
            }
          }
        }
      }
    }
  }
}

Somewhere the retuned value is used we need to wrap it once like this:

import { useCaisyUpdates } from "@caisy/live-preview-qwik";
...
const PreviewPage = component$((props: Props) => {
    const { liveProps, version } = useCaisyUpdates({
        Page: { ...props.Page },
        Navigation: { ...props.Navigation },
        Footer: { ...props.Footer },
    });

    return (
        <Navigation {...liveProps.value?.Navigation}>
            <main>
                <ComponentSelector key={version.value} page={liveProps.value?.Page} />
            </main>
            <Footer {...liveProps.value?.Footer} />
        </Navigation>
    );
});

The key is used to force an re-render of all components even if the object change is deeply nested.

Highlight editable components

In order to make your fields being clickable with this edit button - we need to provide the library in the html the id of the document and the field name. Therefore we use the helper function getCaisyInspectProps form "@caisy/live-preview-qwik"
This function is very simple:

function getCaisyInspectProps({ id, fieldName }: { id: string; fieldName: string }) {
    return {
        "data-caisy-document-id": id,
        "data-caisy-field-name": fieldName,
    };
}

and adds the necessary data attributes that the library needs to display this interface

Live preview example Image

but you need to add this everywhere, where you want the editor to be able to click the edit button.

For our example, we implement it like this in the a component:


import { getCaisyInspectProps } from "@caisy/live-preview-qwik";

...

      <div className="mb-8 flex flex-col justify-start items-center gap-2.5">
        {headline && (
          <h1
            {...getCaisyInspectProps({ id: id, fieldName: "headline" })}
            className="text-4xl font-bold text-left text-slate-900"
          >
            {headline}
          </h1>
        )}
        {subheadline && (
          <h4
            {...getCaisyInspectProps({ id: id, fieldName: "subheadline" })}
            className="mt-2 text-xl text-center text-gray-400"
          >
            {subheadline}
          </h4>
        )}
      </div>

in order to get the result as in seen in the screenshot.

Connection Indicator

The CaisyConnectionIndicator component can be added in order to show the connection state.

import { CaisyConnectionIndicator } from "@caisy/live-preview-qwik";
...
return (
    <>
      {livePreviewEnabled && <CaisyConnectionIndicator />}
    </>
)

And finally, back to caisy to launch the live-preview!

By clicking on the Eye Icon on the top right on any Page document will open a window with the Preview URL we set up before

live-preview-working

If everything went well, we should see all the UI elements we mentioned, and any change made on the caisy document will be reflected on your application!