Skip to main content
Our docs redesign is live!

The Ultimate Guide to Contentstack Visual Building

5 min read

Everything you need to build bulletproof real-time preview experiences.

Live Preview creates a continuous feedback loop where editors see their changes materialize instantly. This guide gives you the mental models and practical knowledge to implement it correctly, debug it when things break, and adapt it to any rendering strategy.

What You'll Build

This guide uses the Contentstack Next.js Kickstart as a running example. For a variant that routes all content through a middleware API layer (keeping tokens server-side), see the Next.js Middleware Kickstart. By the end, you'll understand every line across these four files.

lib/contentstack.ts configures the SDK, initializes Live Preview, and fetches content:

import contentstack, { QueryOperation } from "@contentstack/delivery-sdk";
import ContentstackLivePreview, { IStackSdk } from "@contentstack/live-preview-utils";

// Delivery SDK instance: when the live preview hash is present, requests go to the Preview API (drafts).
export const stack = contentstack.stack({
  apiKey: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY as string,
  deliveryToken: process.env.NEXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN as string,
  environment: process.env.NEXT_PUBLIC_CONTENTSTACK_ENVIRONMENT as string,
  live_preview: {
    enable: true,
    preview_token: process.env.NEXT_PUBLIC_CONTENTSTACK_PREVIEW_TOKEN,
    host: "rest-preview.contentstack.com",
  },
});

// Run in the browser only. Wires the iframe session to your stack and enables Visual Builder chrome.
export function initLivePreview() {
  ContentstackLivePreview.init({
    ssr: false, // CSR: refetch in place instead of full reloads
    mode: "builder",
    stackSdk: stack.config as IStackSdk, // So the SDK can inject hash + content type context
    stackDetails: { apiKey: "...", environment: "..." },
    editButton: { enable: true },
  });
}

export async function getPage(url: string) {
  const result = await stack
    .contentType("page").entry().query()
    .where("url", QueryOperation.EQUALS, url)
    .find();
  const entry = result.entries?.[0];
  // Populates `entry.$` with data-cslp props for click-to-edit in preview
  if (entry) contentstack.Utils.addEditableTags(entry, "page", true);
  return entry;
}

components/Preview.tsx runs the Live Preview loop, refetching content on every editor change:

"use client";
import { useState, useEffect, useCallback } from "react";
import ContentstackLivePreview from "@contentstack/live-preview-utils";
import { getPage, initLivePreview } from "@/lib/contentstack";
import Page from "./Page";

export default function Preview({ path }: { path: string }) {
  const [page, setPage] = useState();
  const getContent = useCallback(async () => {
    setPage(await getPage(path)); // Preview API returns latest draft for current hash
  }, [path]);

  useEffect(() => {
    initLivePreview();
    // Fires on every editor change in the stack UI → refetch → re-render
    ContentstackLivePreview.onEntryChange(getContent);
  }, [path]);

  if (!page) return <p>Loading...</p>;
  return <Page page={page} />;
}

components/Page.tsx renders content with edit tags so editors can click any element to jump to its field:

import { VB_EmptyBlockParentClass } from "@contentstack/live-preview-utils";

export default function ContentDisplay({ page }: { page: Page | undefined }) {
  return (
    <main>
      {/* Spreads add data-cslp (and related attrs) so Visual Builder can target each field */}
      <h1 {...page.$?.title}>{page?.title}</h1>

      {/* VB_EmptyBlockParentClass marks the modular-blocks container when empty so VB can add blocks */}
      <div className={`blocks ${VB_EmptyBlockParentClass}`} {...page.$?.blocks}>
        {page?.blocks?.map((item, index) => (
          <section key={index} {...page.$?.[`blocks__${index}`]}>
            <h2 {...item.block.$?.title}>{item.block.title}</h2>
          </section>
        ))}
      </div>
    </main>
  );
}

app/page.tsx routes between preview and production:

import { getPage, isPreview } from "@/lib/contentstack";
import Page from "@/components/Page";
import Preview from "@/components/Preview";

export default async function Home() {
  // Preview session: client wrapper subscribes to Live Preview; production: one server fetch
  if (isPreview) return <Preview path="/" />;
  const page = await getPage("/");
  return <Page page={page} />;
}

The first file connects to Contentstack and sets up preview credentials. The second subscribes to editor changes and refetches on every keystroke. The third renders content with data-cslp edit tags and VB_EmptyBlockParentClass for empty modular blocks. The fourth decides which rendering path to use.

Who This Guide Is For

  • Frontend developers implementing Live Preview across CSR, SSR, or SSG architectures

  • Full-stack engineers working with middleware, BFFs, and database-backed content systems

  • Technical architects evaluating Live Preview for enterprise deployments

  • Anyone debugging a Live Preview issue who wants to isolate problems systematically

Guide Structure

Foundations

Rendering Strategies

Advanced Features

Operations

Prerequisites

Before diving in, you should have:

  • A working Contentstack stack with content types and entries

  • A frontend application (any framework) that fetches and renders Contentstack content

  • Basic familiarity with your framework's data fetching and rendering

  • Access to your stack's Settings to enable Live Preview and generate preview tokens

Enabling Live Preview in Your Stack

Enable Live Preview in your stack settings, set a default preview environment, and add base URLs for each locale in your environment configuration. For step-by-step instructions, see the official setup guide.

Optionally, enable the "Display Setup Status" toggle for real-time configuration feedback during setup. Enable "Always Open in New Tab" if you run SDK v4.0.0+ to preview outside the iframe.

What's Next

You now have the prerequisites in place and basic configuration done. Next, lets take a quick deep dive into how Live Preview works and how it works with different rendering strategies. Proceed to How Live Preview Works to get started.