Composables
Nuxt Contentstack provides a comprehensive set of Vue composables that make fetching content from Contentstack simple, type-safe, and performant. All composables support caching, live preview, personalization, and include built-in error handling.
Overview
The module provides seven composables for different scenarios:
| Composable | Purpose | Return Type |
|---|---|---|
useContentstack | Access SDK instances and runtime state | ContentstackContext |
useGetEntry | Fetch a single entry by UID | AsyncData<T | null> |
useGetEntryByUrl | Query entries by URL field | AsyncData<T | null> |
useGetEntries | Fetch multiple entries with filtering | AsyncData<{entries: T[], count?: number} | null> |
useGetAsset | Fetch a single asset by UID | AsyncData<Asset | null> |
useGetAssets | Fetch multiple assets with filtering | AsyncData<{assets: T[], count?: number} | null> |
useImageTransform | Transform Contentstack images dynamically | {transformedUrl, transformOptions, updateTransform, resetTransform} |
useImageTransform is documented in detail in the Image Optimization guide.useGetEntry
Fetch a single entry by its unique identifier (UID).
Basic Usage
<script setup>
// Fetch a single blog post
const { data: post, status, refresh } = await useGetEntry({
contentTypeUid: 'blog_post',
entryUid: 'blt123456789',
locale: 'en-us'
})
</script>
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<div v-html="post.content"></div>
</article>
</template>
With References
Include referenced content like authors, categories, or related entries:
<script setup>
const { data: post } = await useGetEntry({
contentTypeUid: 'blog_post',
entryUid: 'blt123456789',
referenceFieldPath: ['author', 'category', 'related_posts'],
jsonRtePath: ['content', 'description'],
locale: 'en-us'
})
</script>
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<!-- Author information -->
<div v-if="post.author">
<p>By {{ post.author.name }}</p>
<img :src="post.author.avatar.url" :alt="post.author.name">
</div>
<!-- Category -->
<span v-if="post.category" class="category">
{{ post.category.title }}
</span>
<!-- Content with converted rich text -->
<div v-html="post.content"></div>
<!-- Related posts -->
<div v-if="post.related_posts?.length">
<h3>Related Posts</h3>
<div v-for="related in post.related_posts" :key="related.uid">
<NuxtLink :to="`/blog/${related.url}`">
{{ related.title }}
</NuxtLink>
</div>
</div>
</article>
</template>
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
contentTypeUid | string | ✅ | - | Content type identifier |
entryUid | string | ✅ | - | Unique entry identifier |
referenceFieldPath | string[] | ❌ | [] | Reference fields to include |
jsonRtePath | string[] | ❌ | [] | Rich text fields to convert |
locale | string | ❌ | 'en-us' | Content locale |
replaceHtmlCslp | boolean | ❌ | editableTags | Replace $ CSLP keys with cslp in response data. Defaults to the editableTags setting |
useGetEntryByUrl
Query entries that have a URL field, perfect for page routing and SEO-friendly URLs.
Basic Usage
<script setup>
const route = useRoute()
const path = Array.isArray(route.params.slug)
? `/${route.params.slug.join('/')}`
: `/${route.params.slug}`
const { data: page } = await useGetEntryByUrl({
contentTypeUid: 'page',
url: path,
referenceFieldPath: ['seo', 'components'],
jsonRtePath: ['content']
})
// Handle 404 if page not found
if (!page.value) {
throw createError({
statusCode: 404,
statusMessage: 'Page Not Found'
})
}
</script>
<template>
<main v-if="page">
<Head>
<Title>{{ page.seo?.title || page.title }}</Title>
<Meta name="description" :content="page.seo?.description" />
</Head>
<h1>{{ page.title }}</h1>
<div v-html="page.content"></div>
<!-- Dynamic components -->
<div v-for="component in page.components" :key="component.uid">
<component :is="component._content_type_uid" v-bind="component" />
</div>
</main>
</template>
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
contentTypeUid | string | ✅ | - | Content type identifier |
url | string | ✅ | - | URL to match against URL field |
referenceFieldPath | string[] | ❌ | [] | Reference fields to include |
jsonRtePath | string[] | ❌ | [] | Rich text fields to convert |
locale | string | ❌ | 'en-us' | Content locale |
replaceHtmlCslp | boolean | ❌ | editableTags | Replace $ CSLP keys with cslp in response data. Defaults to the editableTags setting |
useGetEntries
Fetch multiple entries with powerful filtering, sorting, and pagination capabilities.
Basic Usage
<script setup>
const { data: result } = await useGetEntries({
contentTypeUid: 'blog_post',
referenceFieldPath: ['author'],
limit: 10,
orderBy: 'created_at',
includeCount: true
})
const posts = computed(() => result.value?.entries || [])
const totalCount = computed(() => result.value?.count || 0)
</script>
<template>
<div>
<h1>Blog Posts ({{ totalCount }})</h1>
<article v-for="post in posts" :key="post.uid">
<h2>
<NuxtLink :to="`/blog/${post.url}`">
{{ post.title }}
</NuxtLink>
</h2>
<p>By {{ post.author?.name }}</p>
<p>{{ post.excerpt }}</p>
</article>
</div>
</template>
Advanced Filtering
Use query operators for complex filtering:
<script setup>
const currentYear = new Date().getFullYear()
const { data: result } = await useGetEntries({
contentTypeUid: 'blog_post',
referenceFieldPath: ['author', 'tags'],
limit: 20,
skip: 0,
orderBy: 'published_at',
includeCount: true,
where: {
// Exact match
status: 'published',
// Array contains
tags: ['vue', 'nuxt'],
// Comparison operators
view_count: { $gt: 1000 },
published_at: {
$gte: `${currentYear}-01-01`,
$lt: `${currentYear + 1}-01-01`
},
// Existence checks
featured_image: { $exists: true },
// Pattern matching
title: { $regex: 'nuxt.*contentstack' },
// Not equal
author: { $ne: 'guest' }
}
})
</script>
Pagination
Implement pagination with skip and limit:
<script setup>
const route = useRoute()
const page = parseInt(route.params.page) || 1
const limit = 10
const skip = (page - 1) * limit
const { data: result } = await useGetEntries({
contentTypeUid: 'blog_post',
limit,
skip,
includeCount: true,
orderBy: 'created_at'
})
const posts = computed(() => result.value?.entries || [])
const totalCount = computed(() => result.value?.count || 0)
const totalPages = computed(() => Math.ceil(totalCount.value / limit))
</script>
<template>
<div>
<div v-for="post in posts" :key="post.uid">
<!-- Post content -->
</div>
<!-- Pagination -->
<nav class="pagination">
<NuxtLink
v-if="page > 1"
:to="`/blog/page/${page - 1}`"
>
Previous
</NuxtLink>
<span>Page {{ page }} of {{ totalPages }}</span>
<NuxtLink
v-if="page < totalPages"
:to="`/blog/page/${page + 1}`"
>
Next
</NuxtLink>
</nav>
</div>
</template>
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
contentTypeUid | string | ✅ | - | Content type identifier |
referenceFieldPath | string[] | ❌ | [] | Reference fields to include |
jsonRtePath | string[] | ❌ | [] | Rich text fields to convert |
locale | string | ❌ | 'en-us' | Content locale |
replaceHtmlCslp | boolean | ❌ | editableTags | Replace $ CSLP keys with cslp in response data. Defaults to the editableTags setting |
limit | number | ❌ | 10 | Number of entries to fetch |
skip | number | ❌ | 0 | Number of entries to skip |
orderBy | string | ❌ | - | Field to sort by |
includeCount | boolean | ❌ | false | Include total count in response |
where | object | ❌ | {} | Query conditions |
Query Operators
Use these operators in the where parameter for advanced filtering:
| Operator | Description | Example |
|---|---|---|
| Direct value | Exact match | status: 'published' |
| Array | Contains any value | tags: ['vue', 'nuxt'] |
$gt | Greater than | views: { $gt: 1000 } |
$gte | Greater than or equal | date: { $gte: '2024-01-01' } |
$lt | Less than | price: { $lt: 100 } |
$lte | Less than or equal | date: { $lte: '2024-12-31' } |
$ne | Not equal | author: { $ne: 'guest' } |
$exists | Field exists | image: { $exists: true } |
$regex | Pattern match | title: { $regex: 'vue.*guide' } |
useGetAsset & useGetAssets
Fetch Contentstack assets with filtering capabilities.
Single Asset
<script setup>
const { data: asset } = await useGetAsset({
assetUid: 'blt123456789',
locale: 'en-us'
})
</script>
<template>
<div v-if="asset">
<img :src="asset.url" :alt="asset.title">
<p>{{ asset.description }}</p>
</div>
</template>
Multiple Assets
<script setup>
const { data: result } = await useGetAssets({
locale: 'en-us',
limit: 20,
includeCount: true,
where: {
content_type: 'image/jpeg'
}
})
const images = computed(() => result.value?.assets || [])
const totalCount = computed(() => result.value?.count || 0)
</script>
<template>
<div class="gallery">
<p>Total: {{ totalCount }}</p>
<div v-for="image in images" :key="image.uid">
<img :src="image.url" :alt="image.title">
</div>
</div>
</template>
Parameters
useGetAsset Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
assetUid | string | ✅ | - | Unique asset identifier |
locale | string | ❌ | 'en-us' | Content locale |
useGetAssets Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
locale | string | ❌ | 'en-us' | Content locale |
limit | number | ❌ | 10 | Number of assets to fetch |
skip | number | ❌ | 0 | Number of assets to skip |
orderBy | string | ❌ | - | Field to sort by (e.g., 'created_at', 'updated_at') |
includeCount | boolean | ❌ | false | Include total count in response |
where | object | ❌ | {} | Query conditions (note: most filters are client-side) |
content_type is filtered server-side via the Contentstack API. All other where filters (like $gt, $lt, $exists, $regex, exact match, and array containment) are applied client-side after fetching. Keep this in mind when working with large asset libraries.Advanced Asset Filtering
<script setup>
const { data: result } = await useGetAssets({
locale: 'en-us',
limit: 50,
skip: 0,
orderBy: 'created_at',
includeCount: true,
where: {
content_type: 'image/jpeg',
// Note: content_type is filtered server-side, other filters are applied client-side
file_size: { $gt: '10000' },
title: { $regex: 'hero' }
}
})
const totalAssets = computed(() => result.value?.assets?.length || 0)
</script>
Automatic Features
All entry composables automatically call includeFallback() and includeEmbeddedItems() on queries, providing:
- Locale Fallback: If content isn't available in the requested locale, the API falls back to the default locale
- Embedded Items: Referenced items embedded in Rich Text Editor fields are automatically included in responses
Live Preview Integration
All composables automatically support live preview when enabled:
<script setup>
const { data: page, refresh } = await useGetEntryByUrl({
contentTypeUid: 'page',
url: '/about'
})
// refresh() is automatically called when content changes in live preview
</script>
Caching
All composables leverage Nuxt's built-in caching system for optimal performance. Understanding how caching works helps you optimize your application and handle cache invalidation properly.
How Caching Works
Nuxt Contentstack composables use Nuxt's useAsyncData under the hood, which provides:
- Automatic request deduplication: Multiple calls to the same composable with the same parameters share a single request
- SSR caching: Responses are cached during server-side rendering
- Client-side caching: Responses are cached in the browser
- Revalidation: Automatic cache revalidation on navigation
Cache Keys
Cache keys are automatically generated based on composable parameters. For example:
useGetEntry:{contentTypeUid}-{entryUid}-{locale}-{variantAlias}useGetEntryByUrl:{contentTypeUid}-{url}-{locale}-{variantAlias}useGetEntries:{contentTypeUid}-entries-{locale}-{limit}-{skip}-{where}-{variantAlias}useGetAsset:asset-{assetUid}-{locale}useGetAssets:assets-{locale}-{limit}-{skip}-{where}
This means identical queries share the same cache:
<!-- Both components share the same cache -->
<ComponentA>
const { data } = await useGetEntry({ contentTypeUid: 'page', entryUid: 'blt123' })
</ComponentA>
<ComponentB>
const { data } = await useGetEntry({ contentTypeUid: 'page', entryUid: 'blt123' })
</ComponentB>
Cache Invalidation
Manual Refresh
Use the refresh() method to manually invalidate and refetch:
<script setup>
const { data: page, refresh } = await useGetEntryByUrl({
contentTypeUid: 'page',
url: '/about'
})
// Refresh content manually
const handleRefresh = async () => {
await refresh()
}
</script>
Live Preview Auto-Refresh
When Live Preview is enabled, composables automatically refresh when content changes:
<script setup>
// Automatically refreshes when content changes in Contentstack
const { data: page, refresh } = await useGetEntryByUrl({
contentTypeUid: 'page',
url: '/about'
})
// refresh() is called automatically by Live Preview SDK
</script>
Programmatic Cache Control
For advanced cache control, use Nuxt's refreshCookie:
// Force refresh all cached data
await refreshCookie('contentstack')
// Or use the composable's refresh method
await refresh()
Cache Configuration
Custom Cache TTL
Control cache duration (requires Nuxt configuration):
export default defineNuxtConfig({
routeRules: {
'/blog/**': {
// Cache blog pages for 1 hour
swr: 3600
}
}
})
Cache Strategies
Static Content
For content that rarely changes, rely on default caching:
<script setup>
// Cached automatically, refreshed on navigation
const { data: aboutPage } = await useGetEntryByUrl({
contentTypeUid: 'page',
url: '/about'
})
</script>
Dynamic Content
For frequently updated content, implement manual refresh:
<script setup>
const { data: posts, refresh } = await useGetEntries({
contentTypeUid: 'blog_post',
limit: 10
})
// Refresh every 5 minutes
const interval = setInterval(() => {
refresh()
}, 5 * 60 * 1000)
onUnmounted(() => clearInterval(interval))
</script>
Live Preview Mode
In preview environments, disable aggressive caching:
const isPreview = process.env.NUXT_CONTENTSTACK_ENVIRONMENT === 'preview'
export default defineNuxtConfig({
routeRules: {
// Disable caching in preview mode
'/**': isPreview ? { swr: false } : { swr: 3600 }
}
})
Cache Best Practices
- Use appropriate cache strategies: Static content can be cached longer, dynamic content needs frequent updates
- Invalidate on mutations: If you modify content via API, manually refresh affected composables
- Monitor cache performance: Use browser DevTools to monitor cache hits
- Consider ISR: For production, consider Incremental Static Regeneration patterns
- Handle stale data: Show loading states during cache revalidation
- Use Live Preview: Automatically handles cache invalidation during content editing
Troubleshooting Cache Issues
Content not updating?
- Check if Live Preview is enabled and working
- Manually call
refresh()to force update - Clear browser cache or use incognito mode
- Verify cache keys are different for different queries
Too many requests?
- Ensure identical queries share cache (check parameters match exactly)
- Use
server: falsefor client-only content - Implement proper loading states
Stale data in production?
- Configure appropriate
swrvalues inrouteRules - Implement manual refresh mechanisms
- Consider using webhooks for cache invalidation
TypeScript Support
Nuxt Contentstack provides full TypeScript support for type-safe content management. You can define interfaces manually or generate types automatically from your Contentstack content models.
Manual Type Definitions
Define interfaces for your content types manually:
export interface BlogPost {
uid: string
title: string
url: string
content: string
excerpt: string
published_at: string
author?: {
uid: string
name: string
avatar?: {
url: string
title: string
}
}
tags?: string[]
featured_image?: {
url: string
title: string
}
}
export interface Page {
uid: string
title: string
url: string
content: string
seo?: {
title: string
description: string
og_image?: {
url: string
}
}
}
<script setup lang="ts">
// Type-safe usage
const { data: post } = await useGetEntry<BlogPost>({
contentTypeUid: 'blog_post',
entryUid: 'blt123456789',
})
// TypeScript knows the structure of post.value
if (post.value) {
console.log(post.value.title) // ✅ Type-safe
console.log(post.value.author?.name) // ✅ Type-safe with optional chaining
}
</script>
Automatic Type Generation
Use the Contentstack CLI tsgen plugin to automatically generate TypeScript types from your Contentstack content models.
Installation
npm install -g @contentstack/cli
Configuration
- Login to Contentstack CLI:
csdx auth:login
- Initialize Type Generation:
csdx plugins:install @contentstack/cli-plugin-tsgen
Generate Types
Generate types for your stack:
# Generate types for all content types
csdx tsgen --stack-api-key YOUR_API_KEY --output-dir ./types/contentstack
# Generate types for specific content types
csdx tsgen --stack-api-key YOUR_API_KEY --content-types blog_post,page --output-dir ./types/contentstack
# Generate types with specific environment
csdx tsgen --stack-api-key YOUR_API_KEY --environment production --output-dir ./types/contentstack
Using Generated Types
After generation, import and use the types:
// Auto-generated types
export type BlogPost = {
uid: string
title: string
// ... other fields from Contentstack
}
export type Page = {
uid: string
title: string
// ... other fields from Contentstack
}
<script setup lang="ts">
import type { BlogPost } from '~/types/contentstack'
const { data: post } = await useGetEntry<BlogPost>({
contentTypeUid: 'blog_post',
entryUid: 'blt123456789',
})
</script>
Type Generation Workflow
Create a script to automate type generation:
{
"scripts": {
"generate:types": "csdx tsgen --stack-api-key $NUXT_CONTENTSTACK_API_KEY --output-dir ./types/contentstack",
"dev": "npm run generate:types && nuxt dev"
}
}
Type Safety Best Practices
Use Generic Types
Always use generic types with composables:
// ✅ Good - Type-safe
const { data: post } = await useGetEntry<BlogPost>({
contentTypeUid: 'blog_post',
entryUid: 'blt123'
})
// ❌ Bad - No type safety
const { data: post } = await useGetEntry({
contentTypeUid: 'blog_post',
entryUid: 'blt123'
})
Handle Nullable Types
Composables return T | null, always check for null:
const { data: post } = await useGetEntry<BlogPost>({
contentTypeUid: 'blog_post',
entryUid: 'blt123'
})
// ✅ Safe - Check for null
if (post.value) {
console.log(post.value.title)
}
// ✅ Safe - Use optional chaining
console.log(post.value?.title)
Type References
Type reference fields properly:
interface BlogPost {
uid: string
title: string
author: {
uid: string
name: string
}
related_posts?: BlogPost[] // Self-referencing type
}
Type Guards
Create type guards for runtime type checking:
function isBlogPost(entry: unknown): entry is BlogPost {
return (
typeof entry === 'object' &&
entry !== null &&
'uid' in entry &&
'title' in entry &&
'content' in entry
)
}
const { data: entry } = await useGetEntry({
contentTypeUid: 'blog_post',
entryUid: 'blt123'
})
if (entry.value && isBlogPost(entry.value)) {
// TypeScript knows entry.value is BlogPost
console.log(entry.value.title)
}
Type Definitions Structure
Generated types typically include:
- Entry fields: All fields from your content type
- Metadata:
_metadataobject with UID, created_at, updated_at - References: Typed reference fields
- Assets: Typed asset fields with URL and metadata
- Modular Blocks: Typed modular block structures
- Live Preview: CSLP attributes (when enabled)
Troubleshooting Types
Types not updating?
- Regenerate types after content model changes
- Clear TypeScript cache:
rm -rf .nuxt .output node_modules/.cache - Restart TypeScript server in your IDE
Missing fields?
- Ensure content type is published in Contentstack
- Check environment matches your configuration
- Verify API key has access to content types
Type errors?
- Check for nullable fields (
field?: typevsfield: type) - Verify reference types match actual content types
- Ensure modular block types are properly defined
Error Handling
All composables include built-in error handling and return standard Nuxt AsyncData objects with status, error, and data properties.
Basic Error Handling
<script setup>
const { data: post, status, error } = await useGetEntry({
contentTypeUid: 'blog_post',
entryUid: 'invalid-uid'
})
// Handle different states
</script>
<template>
<div>
<div v-if="status === 'pending'">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else-if="post">{{ post.title }}</div>
<div v-else>No content found</div>
</div>
</template>
Error Types
The module uses standardized error codes internally:
| Error Code | Description | Common Causes |
|---|---|---|
NETWORK_ERROR | Failed API requests | Network issues, connection refused, timeouts, fetch failures |
NOT_FOUND | Content doesn't exist | Invalid UID, deleted entry, wrong content type, 404 response |
VALIDATION_ERROR | Invalid query parameters | Wrong field names, invalid operators, 400/422 responses |
UNKNOWN | Unclassified errors | Unexpected failures, server errors |
Advanced Error Handling
Handle specific error types and implement retry logic:
<script setup>
const { data: post, error, status, refresh } = await useGetEntry({
contentTypeUid: 'blog_post',
entryUid: 'blt123456789'
})
// Check for specific error types
const handleError = () => {
if (!error.value) return
// Network errors - retry logic
if (error.value.message?.includes('network') || error.value.message?.includes('fetch')) {
console.warn('Network error, retrying...')
setTimeout(() => refresh(), 2000)
return
}
// Authentication errors - redirect to error page
if (error.value.statusCode === 401 || error.value.statusCode === 403) {
throw createError({
statusCode: 401,
statusMessage: 'Authentication failed'
})
}
// Not found - show 404
if (error.value.statusCode === 404) {
throw createError({
statusCode: 404,
statusMessage: 'Content not found'
})
}
}
watch(error, handleError, { immediate: true })
</script>
Error Boundaries
Create reusable error handling components:
<script setup>
interface Props {
error: Error | null
status: 'idle' | 'pending' | 'error' | 'success'
onRetry?: () => void
}
const props = defineProps<Props>()
const errorMessage = computed(() => {
if (!props.error) return 'Unknown error'
if (props.error.statusCode === 404) {
return 'Content not found'
}
if (props.error.statusCode === 401 || props.error.statusCode === 403) {
return 'Access denied'
}
return props.error.message || 'Failed to load content'
})
</script>
<template>
<div v-if="status === 'error'" class="error-boundary">
<div class="error-content">
<h3>Error Loading Content</h3>
<p>{{ errorMessage }}</p>
<button v-if="onRetry" @click="onRetry" class="retry-button">
Try Again
</button>
</div>
</div>
</template>
Retry Strategies
Implement retry logic for transient errors:
export const useRetryableContent = <T>(
composable: () => Promise<AsyncData<T | null, Error | null>>,
maxRetries = 3
) => {
const result = ref<AsyncData<T | null, Error | null> | null>(null)
const retryCount = ref(0)
const fetchWithRetry = async () => {
try {
result.value = await composable()
if (result.value.error && retryCount.value < maxRetries) {
retryCount.value++
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount.value))
return fetchWithRetry()
}
} catch (error) {
if (retryCount.value < maxRetries) {
retryCount.value++
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount.value))
return fetchWithRetry()
}
throw error
}
}
return {
result: readonly(result),
retryCount: readonly(retryCount),
fetchWithRetry
}
}
Best Practices
- Always check error state: Use
status === 'error'or checkerrorvalue before accessingdata - Handle 404 gracefully: Show user-friendly messages for missing content
- Implement retry logic: For network errors, implement exponential backoff
- Log errors: Use error tracking services for production debugging
- Provide fallbacks: Show default content or placeholders when content fails to load
- Validate input: Check UIDs and parameters before making requests
Best Practices
Performance
- Use appropriate
limitvalues to avoid over-fetching - Include only necessary reference fields
- Enable caching for frequently accessed content
SEO
- Use
useGetEntryByUrlfor page routing - Include structured data and meta tags
- Handle 404 cases gracefully
Content Management
- Enable live preview for content teams
- Structure reference fields logically
Development
- Use TypeScript interfaces for content types
- use contentstack CLI tsgen plugin to fetch types.
- Handle loading and error states
- Test with different content scenarios
Runtime Utilities
Access the Contentstack SDK and runtime utilities through the useContentstack() composable or the $contentstack plugin object.
useContentstack Composable
The useContentstack() composable provides typed access to all Contentstack runtime objects:
<script setup>
const {
stack, // Contentstack Delivery SDK Stack instance
livePreviewEnabled, // Whether live preview is active
editableTags, // Whether editable tags are enabled
variantAlias, // Current personalization variant alias (reactive)
ContentstackLivePreview, // Live Preview SDK instance
personalizeSdk, // Personalize Edge SDK instance (client-side only, null on server)
VB_EmptyBlockParentClass, // CSS class for Visual Builder empty blocks
} = useContentstack()
</script>
Direct SDK Access
Use the Contentstack Delivery SDK directly for advanced use cases:
const { stack } = useContentstack()
// Create custom queries using the Stack instance
const query = stack.contentType('blog_post')
.entry()
.locale('en-us')
.includeFallback()
.includeEmbeddedItems()
const result = await query.query().find()
Live Preview SDK
Access Live Preview SDK directly:
const { livePreviewEnabled, ContentstackLivePreview } = useContentstack()
if (livePreviewEnabled && import.meta.client) {
// Custom live preview handlers
ContentstackLivePreview.onEntryChange((data) => {
console.log('Entry changed:', data)
// Custom handling
})
}
Personalization SDK
Access Personalization SDK directly:
const { personalizeSdk, variantAlias } = useContentstack()
if (personalizeSdk) {
// Set custom attributes
await personalizeSdk.set({
custom_attribute: 'value'
})
// Get variants
const variants = personalizeSdk.getVariants()
// Trigger custom events
await personalizeSdk.triggerEvent('custom_event')
// Check current variant alias
console.log('Current variant:', variantAlias?.value)
}
Custom Utilities
Create custom utilities using the composable:
export const useContentstackUtils = () => {
const { stack, livePreviewEnabled, personalizeSdk } = useContentstack()
const getStackInstance = () => stack
const isLivePreviewEnabled = () => livePreviewEnabled
const isPersonalizationEnabled = () => !!personalizeSdk
return {
getStackInstance,
isLivePreviewEnabled,
isPersonalizationEnabled
}
}