Live Preview
Live Preview is one of the most powerful features of Nuxt Contentstack, enabling real-time content editing and visual building capabilities. Content creators can see their changes instantly without page refreshes, creating a seamless editing experience.
Overview
Live Preview consists of several integrated features:
- Real-time Updates: See content changes instantly without page refreshes
- Visual Builder: Edit content directly on the page with visual editing tools
- Edit Buttons: Quick access to editing interfaces
- Editable Tags: Visual indicators for editable content areas
- Preview Modes: Switch between builder and preview modes
Quick Setup
1. Enable Live Preview
Configure live preview in your nuxt.config.ts:
export default defineNuxtConfig({
modules: ['nuxt-contentstack'],
'nuxt-contentstack': {
// Core configuration
apiKey: process.env.NUXT_CONTENTSTACK_API_KEY,
deliveryToken: process.env.NUXT_CONTENTSTACK_DELIVERY_TOKEN,
environment: 'preview', // Use preview environment
// Live Preview configuration
livePreview: {
enable: true,
previewToken: process.env.NUXT_CONTENTSTACK_PREVIEW_TOKEN,
editableTags: true,
editButton: true,
mode: 'builder'
}
}
})
2. Environment Variables
Add your preview token to environment variables:
NUXT_CONTENTSTACK_API_KEY=your_api_key
NUXT_CONTENTSTACK_DELIVERY_TOKEN=your_delivery_token
NUXT_CONTENTSTACK_PREVIEW_TOKEN=your_preview_token
NUXT_CONTENTSTACK_ENVIRONMENT=preview
3. Update Your Components
Add CSLP (Contentstack Live Preview) attributes to make content editable:
<script setup>
import type { Page } from "../../types";
type PageProps = Omit<Page, "$"> & {
cslp?: Page["$"];
};
const route = useRoute()
const path = `/${route.params.slug?.join('/') || ''}`
// Automatically supports live preview
const { data: page, refresh } = await useGetEntryByUrl<PageProps>({
contentTypeUid: 'page',
url: path,
referenceFieldPath: ['components', 'seo'],
jsonRtePath: ['content']
})
if (!page.value) {
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
}
</script>
<template>
<main v-if="page">
<h1 v-bind="page.cslp && page.cslp.title">{{ page.title }}</h1>
<div v-bind="page.cslp && page.cslp.content" v-html="page.content"></div>
<!-- Components with live preview support -->
<component
v-for="comp in page.components"
:key="comp.uid"
:is="comp._content_type_uid"
v-bind="comp"
/>
</main>
</template>
v-bind="page.cslp && page.cslp.fieldName" pattern adds data-cslp attributes that enable in-place editing in Contentstack's Visual Builder.Configuration Options
Basic Configuration
livePreview: {
enable: true, // Enable/disable live preview
previewToken: 'your_token', // Required preview token
editableTags: true, // Show editable content indicators
editButton: true, // Show edit button
mode: 'builder', // 'builder' or 'preview'
ssr: false // Server-side rendering mode
}
Advanced Edit Button Configuration
Customize the edit button appearance and behavior:
livePreview: {
enable: true,
previewToken: 'your_token',
editButton: {
enable: true,
position: 'top-right', // Button position
exclude: [ // Where to hide the button
'insideLivePreviewPortal',
'outsideLivePreviewPortal'
],
includeByQueryParameter: false // Show only with ?edit=true
}
}
Edit Button Positions
| Position | Description |
|---|---|
top | Top center |
bottom | Bottom center |
left | Left center |
right | Right center |
top-left | Top left corner |
top-right | Top right corner |
top-center | Top center |
bottom-left | Bottom left corner |
bottom-right | Bottom right corner |
bottom-center | Bottom center |
Live Preview Modes
Builder Mode
Builder mode provides the full visual editing experience:
livePreview: {
mode: 'builder' // Full editing capabilities
}
Features:
- Visual content editing
- Drag-and-drop components
- Real-time preview
- Component property editing
Preview Mode
Preview mode shows content updates without editing interface:
livePreview: {
mode: 'preview' // Preview only, no editing UI
}
Features:
- Real-time content updates
- No editing interface
- Clean preview experience
- Content validation
Working with Components
Basic Component Setup
Create components that work seamlessly with Live Preview by adding CSLP attributes:
<script setup>
// Define props with CSLP support
interface Props {
title: string
subtitle?: string
background_image?: {
url: string
title: string
cslp?: {
url?: any
title?: any
}
}
cta_button?: {
text: string
url: string
cslp?: {
text?: any
url?: any
}
}
cslp?: {
title?: any
subtitle?: any
}
}
const props = defineProps<Props>()
</script>
<template>
<section class="hero">
<div
v-if="background_image"
class="hero-background"
:style="{ backgroundImage: `url(${background_image.url})` }"
v-bind="background_image.cslp && background_image.cslp.url"
>
<div class="hero-content">
<h1 v-bind="props.cslp && props.cslp.title">{{ title }}</h1>
<p v-if="subtitle" v-bind="props.cslp && props.cslp.subtitle">{{ subtitle }}</p>
<NuxtLink
v-if="cta_button"
:to="cta_button.url"
class="cta-button"
v-bind="cta_button.cslp && (cta_button.cslp.text || cta_button.cslp.url)"
>
{{ cta_button.text }}
</NuxtLink>
</div>
</div>
</section>
</template>
Modular Block Components
For modular content blocks, create a component mapper:
<script setup>
defineProps<{
blocks: Array<{
_content_type_uid: string
uid: string
[key: string]: any
}>
}>()
// Map content type UIDs to component names
const componentMap = {
'hero_block': 'Hero',
'text_block': 'TextBlock',
'image_gallery': 'ImageGallery',
'cta_block': 'CtaBlock'
}
</script>
<template>
<div class="content-blocks">
<component
v-for="block in blocks"
:key="block.uid"
:is="componentMap[block._content_type_uid] || 'UnknownBlock'"
v-bind="block"
/>
</div>
</template>
Rich Text with Live Preview
Handle rich text content with live preview support. CSLP attributes are automatically added to HTML content:
<script setup>
interface Props {
content: string
replaceHtmlCslp?: boolean
cslp?: {
content?: any
}
}
const props = defineProps<Props>()
</script>
<template>
<div
class="rich-text-content"
v-bind="props.cslp && props.cslp.content"
v-html="content"
/>
</template>
<style>
/* Ensure live preview styles don't interfere */
.rich-text-content {
/* Your content styles */
}
/* CSLP attributes are automatically added to HTML elements inside rich text */
.rich-text-content [data-cslp] {
/* Live preview will handle these for in-place editing */
}
</style>
Environment-Specific Configuration
Development Setup
Enable live preview only in development:
const isDev = process.env.NODE_ENV === 'development'
export default defineNuxtConfig({
'nuxt-contentstack': {
// ... core config
livePreview: {
enable: isDev,
previewToken: process.env.NUXT_CONTENTSTACK_PREVIEW_TOKEN,
editableTags: isDev,
editButton: isDev
}
}
})
Staging Environment
Configure live preview for staging environments:
const isStaging = process.env.NODE_ENV === 'staging'
const enableLivePreview = process.env.ENABLE_LIVE_PREVIEW === 'true'
export default defineNuxtConfig({
'nuxt-contentstack': {
environment: isStaging ? 'preview' : 'production',
livePreview: {
enable: enableLivePreview,
previewToken: process.env.NUXT_CONTENTSTACK_PREVIEW_TOKEN,
editButton: {
enable: enableLivePreview,
includeByQueryParameter: true // Show only with ?edit=true
}
}
}
})
Advanced Usage
Manual Live Preview Integration
For advanced use cases, access the Live Preview SDK directly:
export const useLivePreview = () => {
const { livePreviewEnabled, ContentstackLivePreview } = useContentstack()
const initLivePreview = () => {
if (import.meta.client && livePreviewEnabled) {
// Custom live preview logic
ContentstackLivePreview.onEntryChange((data) => {
console.log('Entry changed:', data)
// Custom handling
})
}
}
return {
initLivePreview,
livePreviewEnabled
}
}
Custom Edit Buttons
Create custom edit buttons for specific content areas:
<script setup>
const props = defineProps<{
entryUid: string
contentTypeUid: string
title: string
}>()
const { livePreviewEnabled } = useContentstack()
const editUrl = computed(() => {
if (!livePreviewEnabled) return null
return `https://app.contentstack.com/#!/stack/${stackApiKey}/content-type/${props.contentTypeUid}/en-us/entry/${props.entryUid}/edit`
})
</script>
<template>
<div class="editable-section">
<div class="content-header">
<h2>{{ title }}</h2>
<a
v-if="editUrl"
:href="editUrl"
target="_blank"
class="edit-button"
>
✏️ Edit
</a>
</div>
<slot />
</div>
</template>
Best Practices
Content Structure
Modular Design:
- Create reusable component blocks
- Use consistent naming conventions
- Structure content hierarchically
Field Naming:
- Use descriptive field names
- Follow camelCase or snake_case consistently
- Avoid special characters in field names
Performance
Efficient Updates:
- Use specific reference paths
- Avoid deep nesting where possible
- Implement proper error boundaries
Caching Strategy:
- Cache static content appropriately
- Use cache invalidation for live preview
- Consider preview vs. production caching
User Experience
Visual Feedback:
- Show loading states during updates
- Provide clear editing indicators
- Handle error states gracefully
Editor Experience:
- Provide meaningful component names
- Use descriptive help text
- Implement validation feedback
Troubleshooting
Common Issues
Live Preview Not Working:
// Check configuration
const { livePreviewEnabled } = useContentstack()
console.log(livePreviewEnabled)
// Verify preview token
// Ensure environment is set to 'preview'
// Check browser console for errors
Edit Button Not Appearing:
// Verify edit button configuration
livePreview: {
editButton: {
enable: true,
position: 'top-right'
}
}
Content Not Updating:
// Ensure composables support refresh
const { data, refresh } = await useGetEntry(...)
// Check live preview event listeners
ContentstackLivePreview.onEntryChange(refresh)
Debug Mode
Enable debug mode for detailed live preview logging:
'nuxt-contentstack': {
debug: true,
livePreview: {
enable: true,
// ... other config
}
}