Caisy live preview using React with Next.js

NOTE: for this example we will be using caisy’s nextjs starter template with the next pages router, feel free to clone this repository and follow the instructions to be at the same starting point. You can also use your own project, you might need to adapt some steps accordingly. While this setup focuses on the pages router, you can translate the usage to the app router, by wrapping all the live preview part in "use client".

Basic Project Setup

Starting with the template "Next.js - Simple Blog" we first create a .env.local file with the following properties:

  • CAISY_PROJECT_ID

  • CAISY_API_KEY

  • NEXT_PUBLIC_USE_DRAFT_MODE=true


We use NEXT_PUBLIC_USE_DRAFT_MODE as a way to enable the draft and live preview mode, but you can change this and use a more dynamic and maybe authorised approach if you want to restrict you preview mode to just authorised users later.

skip this next two steps if you already have your project running and you get updates form the graphql api in draft mode
Next, let’s start with what need to do on caisy in our project:

Get the CAISY_PROJECT_ID

If we go to the General Settings of our project, we can copy the projectId, and paste it under .env.local / CAISY_PROJECT_ID

project_id_nice

Then we need to create an API key to be able to communicate with caisy from an external source, we can create one by navigating to settings/development/api keys and clicking on the CREATE API KEY button:

apikey1_nice
apikey2_nice

Then copy the created key and paste it on the .env.local / CAISY_API_KEY file


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-react and @caisy/live-preview-javascript libraries:

yarn add @caisy/live-preview-react @caisy/live-preview-javascript

or

npm install @caisy/live-preview-react @caisy/live-preview-javascript

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 router = useRouter();
  const livePreviewEnabled =
    `${process.env.NEXT_PUBLIC_USE_DRAFT_MODE}` === "true";

  useEffect(() => {
    if (typeof window === "undefined") return;

    const token = router.query.caisy_preview_access_token as string;
    const projectId = router.query.project_id as string;

    if (!token || !projectId) {
      return;
    }

    const close = caisyLivePreview({
      projectId,
      token,
      locale: router.locale,
      enabled: livePreviewEnabled,
    });

    return () => {
      close && close();
    };
  }, [router.locale, router.query, livePreviewEnabled]);

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

  • projectId: this is the ID of the project we fetch the content from

  • 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 useEffect can run in the _app.tsx or any other location of you app. But if this has not been started, 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
            }
          }
        }
      }
    }
  }
}

This returned value is what we need to display on the UI, on our example we do it like this:

import { useCaisyUpdates } from "@caisy/live-preview-react";

export default function App({ Component, pageProps }: AppProps) {
  ...
  
  const liveProps = useCaisyUpdates(pageProps);

  return (
    <>
      <Navigation {...liveProps.Navigation} />
      <Component {...liveProps} />
      <Footer {...liveProps.Footer} />
    </>
  );
}

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-react"
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 Headline component:


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

...

      <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-react";
...
return (
    <>
      {livePreviewEnabled && <CaisyConnectionIndicator />}
    </>
)

Put it all together in _app.tsx

If we put all of the above together (expect the the component getCaisyInspectProps part) your _app.tsx might look like this. However you can split this up too and inject it on several levels of your app.

pages/_app.tsx
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import Head from "next/head";
import { Footer } from "../layouts/Footer";
import { Navigation } from "../layouts/Navigation";
import { useRouter } from "next/router";
import { useEffect } from "react";
import "@caisy/live-preview-react/index.css";
import {
  CaisyConnectionIndicator,
  caisyLivePreview,
  useCaisyUpdates,
} from "@caisy/live-preview-react";

export default function App({ Component, pageProps }: AppProps) {
  const router = useRouter();
  const livePreviewEnabled =
    `${process.env.NEXT_PUBLIC_USE_DRAFT_MODE}` === "true";

  useEffect(() => {
    if (typeof window === "undefined") return;

    const token = router.query.caisy_preview_access_token as string;
    const projectId = router.query.project_id as string;

    if (!token || !projectId) {
      return;
    }

    const close = caisyLivePreview({
      projectId,
      token,
      locale: router.locale,
      enabled: livePreviewEnabled,
    });

    return () => {
      close && close();
    };
  }, [router.locale, router.query, livePreviewEnabled]);

  const livePageProps = useCaisyUpdates(pageProps);

  return (
    <>
      <Head>
        <meta name="viewport" content="width=device-width" />
      </Head>
      {livePreviewEnabled && <CaisyConnectionIndicator />}
      {livePageProps.Navigation && <Navigation {...livePageProps.Navigation} />}
      <Component {...livePageProps} />
      {livePageProps.Footer && <Footer {...livePageProps.Footer} />}
    </>
  );
}

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!