cs-icon.svg

Set up Live Preview with GraphQL for Server-side Rendering (SSR)

Server-side rendering (SSR) involves a process where an application generates rendered HTML client pages on the server rather than in the browser. This guide provides a comprehensive walkthrough on how to configure Live Preview for an Server-side Rendering (SSR) website using GraphQL.

Prerequisites

Steps for Execution

Here is an overview of the steps involved in setting up Live Preview with GraphQL for your Server-side Rendering (SSR) sites:

  1. Set up the Website
  2. Host the Website
  3. Update Stack Settings
  4. Live Edit Tags for Entries (optional)

Set up the Website

You must first set up your website. To do so, follow these steps given below:

  1. Generate a preview token for configuration

    You can create a preview token within the Contentstack app by navigating to Settings > Tokens > Delivery Tokens (press “alt + O” for Windows or “option key + O” for Mac). It is recommended to use a preview token for Live Preview instead of a previously utilized, read-only management token.

    Each preview token is associated with a delivery token and shares access to the specific environment. Therefore, if a delivery token doesn't exist, you must create a new one, where you can enable the Create Preview Token toggle. For an existing delivery token, you will find an option to generate a preview token. Click + Create Preview Token and copy the resulting token.


    Create-a-Preview-Token_GIF.gif
  2. Install and initialize the Live Preview utils SDK

    The Live Preview Utils SDK operates by monitoring content updates and instructing Contentstack's delivery SDK to retrieve draft or preview content or manage real-time content changes. To achieve this functionality, you must run this SDK on the client side.

    You can install Live Preview Utils SDK using two methods:

    Via npm:

    You can install the Live Preview Utils SDK package via npm by running the following command:

    npm install @contentstack/live-preview-utils
    

    You can now proceed to initialize the Live Preview Utils SDK by utilizing the init() method. This action establishes event listeners that monitor any modifications made to the content of the currently previewed entry.

    import ContentstackLivePreview from "@contentstack/live-preview-utils";
    ContentstackLivePreview.init();
    

    Note: To avoid configuration reset errors in your Live Preview setup due to rerendering, it's crucial to encapsulate your Live Preview Utils SDK initialization code within a separate JavaScript file.

    Via script:

    To import and initialize the Live Preview Utils SDK using the script tag of the HTML file, run the following code:

    <script type='module'>
         import ContentstackLivePreview from 'https://esm.sh/@contentstack/live-preview-utils@2.0.3';
         ContentstackLivePreview.init({
            stackDetails: {
                apiKey: "your-stack-api-key",
            },
            ssr : true
        });
    </script>
    
  3. Set up a middleware

    Note: This step is not required for Live Preview SDK version 3.0.0 and above.

    When you access your website in the Live Preview panel, it obtains the hash through query parameters in SSR mode. Utilize the ContentstackLivePreview.setConfigFromParams() method to extract and store this hash, making it accessible via ContentstackLivePreview.hash.
    You'll need to use ContentstackLivePreview.hash when retrieving data from the server.

    import ContentstackLivePreview from "@contentstack/live-preview-utils";
    ContentstackLivePreview.setConfigFromParams(window.location.search) // You could use objects provided by the frameworks as well.
    
  4. Update the GraphQL URL and headers

    To ensure the proper functioning of the website within the Live Preview panel, it's necessary to change the GraphQL API’s hostname. When the website operates within a Live Preview panel, the Live Preview SDK receives a hash via the URL search params in SSR mode. Consequently, you can examine this hash to switch to the appropriate hostname.

    const graphqlUrl = new URL(
        `https://graphql.contentstack.com/stacks/${REACT_APP_CONTENTSTACK_API_KEY}?environment=${REACT_APP_CONTENTSTACK_ENVIRONMENT}`,
    );
    function getHeaders() {
        const headers = new Headers();
        headers.append("Content-Type", "application/json");
        headers.append("access_token", REACT_APP_CONTENTSTACK_DELIVERY_TOKEN);
        return headers;
    }
    const gqlRequest = async (gql) => {
        const headers = getHeaders();
        const res = await fetch(graphqlUrl.toString(), {
            method: "POST",
            headers: headers,
            body: JSON.stringify({ query: gql }),
        });
        return res;
    };
    

    Here, a simple configuration is established. You have set up graphqlUrl, which directs to the GraphQL endpoint, and have created the functions: getHeaders(), to supply the necessary headers, and gqlRequest(), to retrieve the data.
    import ContentstackLivePreview from "@contentstack/live-preview-utils";
    const graphqlUrl = new URL(
        `https://graphql.contentstack.com/stacks/${REACT_APP_CONTENTSTACK_API_KEY}?environment=${REACT_APP_CONTENTSTACK_ENVIRONMENT}`,
    );
    const GRAPHQL_HOST_NAME = "graphql.contentstack.com";
    const LIVE_PREVIEW_HOST_NAME = "graphql-preview.contentstack.com";
    function getHeaders() {
        const headers = new Headers();
        headers.append("Content-Type", "application/json");
        headers.append("access_token", REACT_APP_CONTENTSTACK_DELIVERY_TOKEN);
        return headers;
    }
    const gqlRequest = async (gql) => {
        const hash = ContentstackLivePreview.hash;
        const headers = getHeaders();
        if (hash) {
            headers.append("live_preview", hash);
            // Optionally, to enable variant support add the include_applied_variants header.
            headers.append("include_applied_variants", "true");
            headers.append("preview_token", REACT_APP_CONTENTSTACK_PREVIEW_TOKEN);
            graphqlUrl.hostname = LIVE_PREVIEW_HOST_NAME;
        } else {
            graphqlUrl.hostname = GRAPHQL_HOST_NAME;
        }
        const res = await fetch(graphqlUrl.toString(), {
            method: "POST",
            headers: headers,
            body: JSON.stringify({
                query: gql,
            }),
        });
        return res;
    };
    

    In this example, you can use ContentstackLivePreview.hash to selectively modify the hostname and headers. When it comes to enabling Live Preview, it's essential to transmit both the hash and preview_token. Optionally, to enable variant support in edit tags add the include_applied_variants header. Once you have configured this data, you can continue using GraphQL just as you normally would.

    Note: For configuring the host parameter based on your specific regions, refer to the Base URLs for Live Preview section in the GraphQL API documentation.

    To fetch content in the live preview panel, it is recommended to utilize the preview token instead of the read-only management token. For additional details, please refer to the documentation on Preview tokens.

Host the Website

To host a website, you can simply use ngrok or any other website hosting service.

Note: Make sure your website is HTTPS enabled.

Update Stack Settings

To enable Live Preview through the stack settings in Contentstack, follow the steps given below:

  1. Go to Settings.
  2. Create a new environment if there are no existing environments in your stack.
  3. Add your hosted website URL as the base URL for the environment created.Add-Base-URL
  4. Navigate to the Live Preview section under stack's "Settings".
  5. Select the Enable Live Preview checkbox.
  6. Select the Default Preview Environment from the dropdown. This helps avoid having to add the preview settings manually across multiple entries.
  7. Save the settings.
    Live_Preview_Settings_in_Stack.png
    You will now be able to see the Live Preview icon within all the entries of your stack and the feature will preview data from the hosted website.

Note: Since the preview service operates with draft data that hasn't been published, it cannot provide publish_details in its response.

Live Edit Tags for Entries (optional)

Live edit tags allow you to navigate to the field that contains the website content being previewed within the Live Preview pane. When you click on the "Edit" button beside a content block in the preview pane, you will be redirected to the corresponding field within the entry. If the field holds reference to another entry, you will be redirected to the referenced entry's editor page.

Track-and-Edit-Content-in-Preview-Pane.gif

Edit tags contain the location where the corresponding field lies within the entry. The Live Preview Utils SDK searches for the elements which contain the edit tags referred to as data-cslp.

The structure of the edit tag (field location in the entry) you can pass against the data-cslp attribute is as follows:

{content_type_uid}.{entry_uid}.{locale}.{field_uid}

Here's a sample field path:

home.blt80654132ff521260.en-us.modular_blocks.block_1.media_group_uid.image_uid

Note: If the field is nested within another complex field, such as Modular Blocks, provide the field path as follows: {modular_block_field_UID}.{block_UID}.{field_UID}.

For a website built using Contentstack's JavaScript Delivery SDK, you can use the addEditableTags() method to automatically generate the edit tag for you. The following section explains how you can set up live edit tags using addEditableTags().

Set Up Live Editing Using the addEditableTags Method

  1. Process entry for live editing

    Locate the section of your website's code responsible for fetching content from Contentstack. To ensure proper functionality of live editing, it's crucial to retrieve system {uid} from GraphQL at the root of the query and system {uid, content_type_uid} for all references.

    Here is an example of the gqlRequest() method for your reference:

    const query = `
      query HeaderQuery {
        all_header {
          items {
              page_Connection {
               edges {
                 node {
                   ... on Page {
                     title
                     system {
                       uid
                       content_type_uid
                     }
                   }
                 }
               }
             }
          system {
            uid
          }
        }
      }
    }
    `;
    let entry = gqlRequest(query);
    

    Next, include _content_type_uid and uid alongside system, and assign the values from system.content_type_uid to _content_type_uid and system.uid to uid. As a result, the final output will appear as follows:

    {
      "all_header": {
        "items": [
          {
            "page_referenceConnection": {
              "totalCount": 1,
              "edges": [
                {
                  "node": {
                    "title": "Home",
                    "url": "/",
                    "uid": "blt336e63b68a71c1cb",
                    "_content_type_uid": "page",
                    "system": {
                      "uid": "blt336e63b68a71c1cb",
                      "content_type_uid": "page"
                    }
                  }
                }
              ]
            },
            "system": {
              "uid": "bltf9961a71ea3fd917"
            },
            "uid": "bltf9961a71ea3fd917"
          }
        ]
      }
    } 
    
  2. Import the addEditableTags() method

    Install Contentstack Utils from npm using the following command:

    npm i @contentstack/utils
    

    Now, import the addEditableTags() method from the Contentstack SDK:

    import {addEditableTags} from "@contentstack/utils"
    import Contentstack from 'contentstack'
    
  3. Generate edit tags for previewed entry content

    After retrieving data from Contentstack, pass the resultant entry within the addEditableTags() function to add edit tags to the previewed entry content:

    addEditableTags(entry, content_type_uid, tagsAsObject, locale)
    

    Here, entry is the actual entry you get from the SDK, content_type_uid is the unique ID of the current entry’s content type, and tagsAsObject determines the format in which the edit tag would be added.

    Note: The addEditableTags() method does not return any value. It only modifies the entry passed as the first argument to the method.

    By default, tagsAsObject is set to false, and it appends data-cslp in the form of a string as follows:

    'data-cslp=path.to.field'
    

    Note: This option is provided for React-based apps as you cannot directly add any attributes in string format. Instead, you need to destructure an object.

    If tagsAsObject is set to true, the data-cslp attribute is returned in object format as follows:

    { 'data-cslp': 'path.to.field'}
    

    Here's a sample that shows how the code would look once you add the addEditableTags() method:

    let entry = gqlRequest(request);
    addEditableTags(entry[0][0], "content_type_uid", false)
    

    When you use the addEditableTags() method, a new key-value pair is returned at every level in the existing entry schema. This pair is known as the edit tag and is denoted by a dollar sign ($).

    For example, consider that you passed the following entry schema against the first parameter in the addEditableTags() method:

    {
        "name": "John Doe",
        "description": {
            "occupation": [{
                "name": "Plumber",
                "since": 1920
            }],
            "height": "5ft"
        }
    }
    

    Once the addEditableTags() method is executed, the entry passed against the first parameter is updated as follows:

    {
        "name": "John Doe",
        "$": {
            "name": "profile.blt8tvsk328dbw258.en-us.name"
        },
        "description": {
            "$": {
                "occupation": "profile.blt8tvsk328dbw258.en-us.description.occupation",
                "height": "profile.blt8tvsk328dbw258.en-us.description.height"
            },
            "occupation": [{
                "$": {
                    "name": "profile.blt8tvsk328dbw258.en-us.description.occupation.name",
                    "since": "profile.blt8tvsk328dbw258.en-us.description.occupation.since"
                },
                "name": "Plumber",
                "since": 1920
            }],
            "height": "5ft"
        }
    }
    
  4. Set up the Live Preview Utils SDK

    Live Preview requires the stack API key and host URL to perform appropriate redirection to the relevant stack.

    ContentstackLivePreview.init({
        ...
        stackDetails: {
           apiKey: "your api key",
           environment: "your environment",
           branch: "your branch"
    },
       clientUrlParams: {
           host: "app.contentstack.com",
       },
    })
    

    Here, the clientUrlParams key is optional and is set for the North America region. For Europe region, you can use the following config for clientUrlParams:

    {
       host: "eu-app.contentstack.com"
    }
    

    For Azure NA region, use the following config:

    {
       host: "azure-na-app.contentstack.com"
    }
    

    For Azure EU region, use the following config:

    {
       host: "azure-eu-app.contentstack.com"
    }
    
  5. Configure live edit tags for each webpage

    Now, navigate to the section in your website's front-end HTML code where you need to pass the edit tags as attributes. To access an edit tag, fetch the path to a field in the entry and add a dollar sign ($) before the last field in the field depth hierarchy.

    For example, if the path to your entry data is data.description.height, then the corresponding edit tag will be data.description.$.height.

    Once you add the edit tag, content managers will be able to see the "Edit" icon whenever they hover over the corresponding content block on the website.

    <header class="text-center">
        <div class="author">
            <img {{ data.author.profile_image.$.url }} src="{{ data.author.profile_image.url }}" alt="{{ data.author.title }}" />
        </div>
        <h1 {{ data.author.$.title }}>{{ data.author.title }}</h1>
        <h2 class="author-job" {{ data.author.$.job_title }}>{{ data.author.job_title }}</h2>
        <p class="author-bio" {{ data.author.$.biography }}>{{ data.author.biography }}</p>
        <div class="author-social">
            <a href="mailto:{{ data.author.social.email }}"><ion-icon name="mail-outline"></ion-icon></a>
            <a href="https://www.twitter.com/{{ data.author.social.twitter }}"><ion-icon name="logo-twitter"></ion-icon></a>
            <a href="https://www.instagram.com/{{ data.author.social.instagram }}"><ion-icon name="logo-instagram"></ion-icon></a>
        </div>
    </header>
    

    For React-based applications, you can generate edit tags by setting the tagsAsObject parameter to true. When set to true, this parameter returns the edit tag in object format. You need to destructure the object while passing it within the JSX element.

    Here is an example of an edit tag that is returned in object format:

    <h1 {...data.$.name}>{data.name}</h1>
    <p {...data.description.$.height}>{data.description.height}</p>
    

    Note: This setup only works for generic websites that use basic JavaScript frontend code. For websites working on other programming languages, you need to provide the entire path to the specific field.

  6. Add CSS to display edit buttons in the project

    Note: This step is not required for Live Preview SDK version 2.0.0 and above.

    The styles for the live edit tags are available in the @contentstack/live-preview-utils/dist/main.css file. You can import these styles in your main index.js file as follows:

    import "@contentstack/live-preview-utils/dist/main.css";
    

    Alternatively, you can directly import the CSS within the HTML using the following code:

    <link rel="stylesheet" href="https://unpkg.com/@contentstack/live-preview-utils@1.4.3/dist/main.css">
    

Once you have configured the settings, you will be able to see the Edit icon whenever you hover over a content block in your preview panel.

live_preview_panel
Was this article helpful?
^