The Ultimate Guide to Contentstack Visual Building
This guide has everything you need to know 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";
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",
},
});
export function initLivePreview() {
ContentstackLivePreview.init({
ssr: false,
mode: "builder",
stackSdk: stack.config as IStackSdk,
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];
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));
}, [path]);
useEffect(() => {
initLivePreview();
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>
<h1 {...page.$?.title}>{page?.title}</h1>
<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() {
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
Which Rendering Strategy Are You Using?
Your rendering strategy determines how Live Preview operates. Pick your chapter:
Strategy | How it handles "show me the new state" | SDK config | Chapter |
|---|---|---|---|
CSR | Refetch data in the browser, re-render in place | ssr: false | |
SSR | Reload the iframe, re-render on the server | ssr: true | |
SSG | Bypass static output via preview mode, behave like SSR | ssr: true |
Not sure which you're using?
CSR: Initial load shows a spinner while content fetches. Navigation doesn't trigger full page reloads. Content is fetched in useEffect or mounted().
SSR: View source shows real content. Your framework uses getServerSideProps, asyncData, or loaders. Content is fetched before HTML is sent.
SSG: Pages are built during npm run build. Content updates require a rebuild and redeploy.
Hybrid architectures (e.g., Next.js mixing strategies): each page follows one strategy. Configure Live Preview based on what that specific page uses.
Guide Structure
Foundations
1. How Live Preview Works: The mental model, architecture, session lifecycle, and APIs
Rendering Strategies
2. Client-Side Rendering: SDK setup, subscriptions, and refetch patterns for SPAs
3. Server-Side Rendering: Reload-based preview, request-scoped clients, hash propagation
4. Static Site Generation: Preview mode, framework escape hatches
5. Middleware and Complex Architectures: BFF, edge middleware, database caching
Advanced Features
6. Edit Tags and Visual Builder: Click-to-edit, field paths, Visual Builder integration
Operations
7. Debugging and Best Practices: Systematic debugging, common pitfalls, checklists
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
Navigate to Settings > Live Preview in your stack
Select the "Enable Live Preview" checkbox
Select the Default Preview Environment from the dropdown
Navigate to Settings > Environments > select your environment
Add the Base URL for each locale (e.g., https://localhost:3000 for development)
Save your changes
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.
If you're starting from scratch, the Contentstack documentation includes quickstart guides for common frameworks.