Nuxt SDK

The Uniform SDK for Nuxt (@uniformdev/uniform-nuxt) provides a first-class integration between Uniform's visual workspace and Nuxt. It handles route resolution, component rendering, personalization, A/B testing, and visual editing -- all through a dedicated Nuxt module and Vue composables.

Understanding the request lifecycle helps you reason about configuration choices and debug issues effectively:

  1. A visitor requests a page -- When a visitor navigates to a URL (e.g., /about), Nuxt's catch-all route receives the request and resolves the path against Uniform's Project Map.

  2. The composition is fetched -- The useUniformComposition composable fetches the full composition data from the Uniform API based on the resolved Project Map node path.

  3. Components render via the composition tree -- UniformComposition walks the composition tree and uses your resolveRenderer function to map each Uniform component type to a Vue component. Parameters are flattened and passed directly as props.

  4. Personalization and testing are evaluated -- The UniformContextProvider (set up automatically by the Nuxt module) evaluates personalization rules and A/B tests client-side or server-side, depending on your outputType configuration.

  5. Visual editing works out of the box -- When previewing in the Uniform dashboard, the SDK automatically enables contextual editing, inline text editing, and live preview via the playground route.


  • Nuxt 4+ (Vue 3)
  • Node.js 18+
  • A Uniform project with compositions set up
npm install @uniformdev/uniform-nuxt@latest @uniformdev/canvas@latest @uniformdev/canvas-vue@latest @uniformdev/context@latest @uniformdev/context-vue@latest @uniformdev/project-map@latest

You also need the Uniform CLI as a dev dependency for syncing content and downloading manifests:

npm install -D @uniformdev/cli@latest

Create a .env file in your project root:

UNIFORM_API_KEY=your-api-key UNIFORM_PROJECT_ID=your-project-id

note

You can find these values in your Uniform project under Settings > API Keys. See the API keys guide for more information.


The Uniform SDK requires a specific set of files to wire up routing, rendering, and preview. Here is the minimal file structure:

your-nuxt-app/ ├── app.vue # Root app (standard Nuxt) ├── pages/ │ ├── [...path].vue # Catch-all route for compositions │ └── playground.vue # Playground route (visual editing) ├── components/ │ ├── canvasComponents.ts # Maps Uniform types to Vue components │ ├── Page.vue # Example page component │ └── Hero.vue # Example hero component ├── lib/ │ └── uniform/ │ └── contextManifest.json # Downloaded personalization manifest ├── server/ │ └── middleware/ │ └── cors.ts # CORS middleware (required for preview) ├── nuxt.config.ts # Nuxt config with Uniform module └── uniform.config.ts # Uniform CLI configuration

The following sections walk through each file.

Register the @uniformdev/uniform-nuxt module and configure it in nuxt.config.ts:

// nuxt.config.ts import manifest from "./lib/uniform/contextManifest.json"; import type { ManifestV2 } from "@uniformdev/context"; export default defineNuxtConfig({ compatibilityDate: "2025-05-14", modules: ["@uniformdev/uniform-nuxt"], uniform: { projectId: process.env.UNIFORM_PROJECT_ID, readOnlyApiKey: process.env.UNIFORM_API_KEY, apiHost: process.env.UNIFORM_CLI_BASE_URL, edgeApiHost: process.env.UNIFORM_CLI_BASE_EDGE_URL, manifest: manifest as ManifestV2, defaultConsent: true, outputType: "standard", playgroundPath: "/playground", }, });

The Nuxt module automatically:

  • Sets up the UniformContextProvider around your app
  • Registers UniformComposition, UniformPlayground, UniformSlot, UniformText, and UniformRichText as global components (no imports needed in templates)
  • Provides the useUniformComposition composable
  • Exposes the Uniform configuration via useRuntimeConfig().public.$uniform
OptionTypeDefaultDescription
projectIdstring--Your Uniform project ID
readOnlyApiKeystring--API key with read access
apiHoststringundefinedCustom API host URL
edgeApiHoststringundefinedCustom edge API host URL
manifestManifestV2--The personalization manifest object
defaultConsentbooleanfalseDefault storage consent for new visitors
outputType"standard" | "edge""standard"How personalization variants are output
playgroundPathstring"/playground"Path to the playground page handler

note

The outputType setting controls how personalization and A/B test variants are rendered:

  • standard -- Evaluates personalization server-side during SSR or client-side, outputs only the winning variant. Use for standard SSR/SSG deployments.
  • edge -- Outputs all variants in the HTML with metadata, suitable for edge-side personalization where an edge worker selects the winning variant.

Create pages/[...path].vue. This catch-all route handles all Uniform-managed pages:

<!-- pages/[...path].vue --> <script lang="ts" setup> const route = useRoute(); const path = Array.isArray(route.params.path) ? `/${route.params.path.join("/")}` : `/${route.params.path}`; const { composition, error } = await useUniformComposition({ projectMapNodePath: path, }); if (error?.value) { console.error("Error fetching composition from Uniform", error.value); } </script> <template> <UniformComposition v-if="composition" :data="composition" :resolveRenderer="resolveRenderer" /> </template>

Key points:

  • useUniformComposition is an async composable provided by the Nuxt module. It fetches the composition data for the given Project Map node path.
  • The returned composition is a reactive ref containing the RootComponentInstance data.
  • If the composition cannot be found, error will contain the error details.
OptionTypeDescription
projectMapNodePathstringThe Project Map node path to resolve (e.g., /about)

Create pages/playground.vue. The playground route enables visual editing and previewing individual compositions from the Uniform dashboard:

<!-- pages/playground.vue --> <script lang="ts" setup> import { resolveRenderer } from "../components/canvasComponents"; </script> <template> <UniformPlayground :resolveRenderer="resolveRenderer" /> </template>

The UniformPlayground component renders an empty composition shell that the Uniform Canvas editor populates during contextual editing sessions. It uses the same resolveRenderer function as your main composition route.

Create components/canvasComponents.ts. This function maps Uniform component types (defined in your Uniform project) to Vue components:

// components/canvasComponents.ts import type { ComponentInstance } from "@uniformdev/canvas"; import { DefaultNotImplementedComponent, type ResolveRenderer, } from "@uniformdev/canvas-vue"; import Page from "./Page.vue"; import Hero from "./Hero.vue"; const componentMap = { hero: Hero, page: Page, }; export const resolveRenderer: ResolveRenderer = (component) => { return ( componentMap[component.type as keyof typeof componentMap] ?? DefaultNotImplementedComponent ); };

The resolveRenderer function receives a ComponentInstance and returns the Vue component to render. It is called for every component in the composition tree.

DefaultNotImplementedComponent is a built-in fallback that renders a helpful message when a component type has no registered implementation. Always include it as a fallback to gracefully handle unknown component types during development.

warning

The resolveRenderer function signature differs from the Next.js SDK. In Nuxt, it returns a Vue Component directly (not a { component } result object).

Create server/middleware/cors.ts. This server middleware is required for the Uniform Canvas visual editor to communicate with your Nuxt app during preview:

// server/middleware/cors.ts export default defineEventHandler((event) => { setResponseHeaders(event, { "Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Headers": "*", "Access-Control-Expose-Headers": "*", }); if (getMethod(event) === "OPTIONS") { event.res.statusCode = 204; event.res.statusMessage = "No Content."; return "OK"; } });

Your root app.vue is a standard Nuxt app file. The Uniform module handles context setup automatically, so no Uniform-specific components are needed here:

<!-- app.vue --> <template> <NuxtPage /> </template>

Uniform components in Nuxt receive flattened props -- parameter values are extracted from their ComponentParameter wrappers and spread directly onto the component. For example, if a Uniform component has a title parameter of type text, your Vue component receives a title prop with the string value directly.

Every component also receives a component prop containing the full ComponentInstance, which provides access to raw parameter data, component metadata, and slots.

Every component rendered via resolveRenderer receives these props:

PropTypeDescription
componentComponentInstanceThe full component instance with raw parameters, slots, and metadata
[parameterName]flattened valueEach parameter's .value is spread as a top-level prop

Parameters defined in the Uniform dashboard are passed as flattened props. Define your component's props interface using the expected value types:

<!-- components/Hero.vue --> <script lang="ts" setup> import type { ComponentInstance } from "@uniformdev/canvas"; import type { UniformRichTextNodeProps } from "@uniformdev/canvas-vue"; interface Props { title: string; description?: UniformRichTextNodeProps["node"]; component: ComponentInstance; } defineProps<Props>(); </script> <template> <div> <UniformText parameterId="title" as="h1" class="title" placeholder="Enter a title" /> <UniformRichText parameter-id="description" as="div" class="description" placeholder="Enter a description" /> </div> </template>

note

Props are flattened automatically. Unlike the Next.js SDK where you access parameters.title?.value, in Nuxt your component receives title directly as a prop with the raw value. The component prop gives you access to the full ComponentInstance when you need raw parameter metadata.

When you need the full parameter metadata (e.g., for conditional logic based on parameter type or contextual editing state), access the component prop:

<script lang="ts" setup> import type { ComponentInstance } from "@uniformdev/canvas"; interface Props { title: string; linkUrl?: string; isVisible?: boolean; component: ComponentInstance; } const props = defineProps<Props>(); // Access the raw parameter with full metadata const titleParam = props.component.parameters?.title; const titleValue = titleParam?.value as string; // Or use the flattened prop directly const displayTitle = props.title ?? "Default title"; </script> <template> <a v-if="isVisible !== false" :href="linkUrl ?? '#'"> <h2>{{ displayTitle }}</h2> </a> </template>

UniformText renders text parameters with built-in support for inline editing in the Uniform visual editor. It is registered globally by the Nuxt module, so no import is needed in templates:

<UniformText parameterId="title" <!-- Required: the parameter ID string --> as="h1" <!-- Optional: HTML element (default: "span") --> class="text-xl" <!-- Optional: CSS class --> placeholder="Enter title" <!-- Optional: placeholder shown in editor when empty --> :isMultiline="false" <!-- Optional: enables multi-line editing (default: false) --> />
PropTypeDefaultDescription
parameterIdstring--Required. The ID of the parameter to render
asstring"span"The HTML element to render
isMultilinebooleanfalseWhen true, adds white-space: pre-wrap to support line breaks
placeholderstring | ((param: { id: string }) => string)undefinedPlaceholder text shown in the Canvas editor when the value is empty

UniformText supports a default scoped slot for custom value rendering:

<UniformText parameterId="title" as="h1"> <template #default="value"> {{ value?.toUpperCase() }} </template> </UniformText>

UniformRichText renders rich text parameters with full formatting support. It is also globally registered:

<UniformRichText parameter-id="description" <!-- Required: the parameter ID --> as="div" <!-- Optional: wrapper element --> class="prose" <!-- Optional: CSS class --> placeholder="Enter description" <!-- Optional: placeholder in editor --> :resolveRichTextRenderer="customResolver" <!-- Optional: custom node renderers --> />
PropTypeDefaultDescription
parameterIdstring--Required. The ID of the rich text parameter
askeyof HTMLElementTagNameMapundefinedOptional wrapper element. If not set, no wrapper is rendered
placeholderstring | ((param: { id: string }) => string)undefinedPlaceholder text shown when the value is empty
resolveRichTextRendererRenderRichTextComponentResolverundefinedCustom function to override default rich text node renderers

Override how specific rich text node types are rendered using resolveRichTextRenderer:

<script lang="ts" setup> import type { RichTextNode } from "@uniformdev/richtext"; import type { Component } from "vue"; import CustomHeading from "./CustomHeading.vue"; const resolveRichTextRenderer = (node: RichTextNode): Component | null => { if (node.type === "heading") { return CustomHeading; } // Return null/undefined to use the default renderer return null; }; </script> <template> <UniformRichText parameter-id="body" as="div" :resolveRichTextRenderer="resolveRichTextRenderer" /> </template>

Each custom renderer component receives a node prop with the RichTextNode data and a children slot/prop for nested nodes.


Slots define where child components can be placed within a parent component. A page component typically has slots like content, header, and footer.

UniformSlot is globally registered by the Nuxt module:

<!-- components/Page.vue --> <template> <div> <header> <UniformSlot name="header" /> </header> <main> <UniformSlot name="content" /> </main> <footer> <UniformSlot name="footer" /> </footer> </div> </template>

When name is omitted, UniformSlot renders all slots combined:

<template> <UniformSlot /> </template>
PropTypeDefaultDescription
namestringundefinedThe slot name to render. If omitted, all slots are rendered
resolveRendererResolveRendererinheritedOverride the component resolver for this slot's children

UniformSlot supports a default scoped slot for wrapping individual slot items:

<UniformSlot name="content"> <template #default="{ child, component }"> <div class="my-4 border-b pb-4"> <component :is="child" /> </div> </template> </UniformSlot>

The scoped slot receives:

PropertyTypeDescription
childVNodeThe rendered child component
componentComponentInstanceThe raw component instance data

UniformSlot supports an emptyPlaceholder slot shown when the slot has no items:

<UniformSlot name="sidebar"> <template #emptyPlaceholder> <p class="text-gray-400 italic">Drag components here</p> </template> </UniformSlot>

Use flattenValues from @uniformdev/canvas to work with asset parameters:

<script lang="ts" setup> import type { ComponentInstance } from "@uniformdev/canvas"; import { flattenValues } from "@uniformdev/canvas"; interface Props { component: ComponentInstance; } const props = defineProps<Props>(); // Multiple assets: returns an array const images = flattenValues(props.component.parameters?.images); // Single asset: returns a single object const hero = flattenValues(props.component.parameters?.featuredImage, { toSingle: true, }); </script> <template> <div> <img v-if="hero" :src="hero.url" :width="hero.width" :height="hero.height" :alt="hero.fields?.alt?.value ?? ''" /> <div class="grid grid-cols-3 gap-4"> <img v-for="(img, i) in images" :key="i" :src="img?.url" :width="img?.width" :height="img?.height" /> </div> </div> </template>

warning

Always use flattenValues from @uniformdev/canvas for asset parameters. Do not create custom utility functions for extracting asset data.


For more complex page layouts where you need to wrap UniformComposition with navigation, metadata, or other elements, create a composition wrapper:

<!-- components/PageComposition.vue --> <script lang="ts" setup> import type { RootComponentInstance } from "@uniformdev/canvas"; import { resolveRenderer } from "./canvasComponents"; interface Props { composition: RootComponentInstance; } const props = defineProps<Props>(); // Access composition-level parameters const { metaTitle } = props.composition?.parameters || {}; const title = metaTitle?.value as string; </script> <template> <div class="page"> <Head> <Title>{{ title }}</Title> </Head> <Navigation /> <UniformComposition :data="composition" :resolveRenderer="resolveRenderer" /> <Footer /> </div> </template>

Then use it in your catch-all route:

<!-- pages/[...path].vue --> <script lang="ts" setup> const route = useRoute(); const path = Array.isArray(route.params.path) ? `/${route.params.path.join("/")}` : `/${route.params.path}`; const { composition, error } = await useUniformComposition({ projectMapNodePath: path, }); </script> <template> <PageComposition v-if="composition" :composition="composition" /> </template>

Personalization and A/B testing are configured in the Uniform dashboard and evaluated automatically by the SDK. The UniformSlot component handles personalization and test containers transparently -- when it encounters a personalization or test component in the composition tree, it renders the appropriate Personalize or Test component from @uniformdev/context-vue.

  1. The personalization manifest is downloaded locally (see Manifest management) and passed to the Nuxt module configuration.
  2. The UniformContextProvider (set up automatically by the Nuxt module) creates a Context instance from the manifest.
  3. When UniformSlot encounters a personalization container, it evaluates the visitor's scores against the variant criteria and renders the winning variant(s).
  4. Score updates happen through enrichment tracking (Track component) and route-based signals.

The outputType in your nuxt.config.ts controls how variants are rendered:

  • standard (default) -- Evaluates personalization during SSR or on the client. Only the winning variant HTML is included in the response. Best for most deployments.

  • edge -- All variant HTML is rendered with metadata tags. An edge worker (CDN-level) selects and shows the winning variant. Best for fully cached/static sites with edge personalization.

// nuxt.config.ts export default defineNuxtConfig({ uniform: { // ... outputType: "standard", // or "edge" }, });

Components with enrichment tags automatically track visitor behavior. The behaviorTracking prop on UniformComposition controls when tracking occurs:

<UniformComposition :data="composition" :resolveRenderer="resolveRenderer" behaviorTracking="onView" />
ValueDescription
"onView" (default)Tracks when the component enters the viewport (uses IntersectionObserver). Renders a wrapping <div>
"onLoad"Tracks immediately when the component mounts, regardless of viewport. No wrapping element

The Nuxt module automatically sets up the UniformContextProvider for your app. Several composables are available for client-side interactivity.

Access the Uniform Context instance directly. Available through the Nuxt app instance:

<script lang="ts" setup> const { context } = useNuxtApp().$useUniformContext(); const forgetMe = async () => { await context.forget(true); }; </script> <template> <button @click="forgetMe">Forget me</button> </template>

The context object is the @uniformdev/context Context instance, providing access to:

Method / PropertyDescription
context.scoresCurrent visitor score values
context.quirksCurrent visitor quirk values
context.update(options)Update visitor data (quirks, enrichments, URL)
context.forget(purge)Clear all visitor data. Pass true to also clear cookies
context.personalize(options)Manually evaluate personalization

Provides reactive access to the current visitor's quirk values. The component re-renders whenever quirks change:

<script lang="ts" setup> import { useQuirks } from "@uniformdev/context-vue"; const quirks = useQuirks(); </script> <template> <div>Current country: {{ quirks?.country ?? "Unknown" }}</div> </template>

Provides reactive access to the current visitor's score values:

<script lang="ts" setup> import { useScores } from "@uniformdev/context-vue"; const scores = useScores(); </script> <template> <div>Tech interest score: {{ scores?.tech ?? 0 }}</div> </template>

Update visitor quirks to trigger personalization changes:

<script lang="ts" setup> const { context } = useNuxtApp().$useUniformContext(); const setCountry = async (country: string) => { await context.update({ quirks: { country }, }); }; </script> <template> <div> <button @click="setCountry('Canada')">I'm from Canada</button> <button @click="setCountry('USA')">I'm from USA</button> </div> </template>

The Uniform Project Map defines the site's URL structure and maps paths to compositions. You can query project map nodes to build navigation or resolve routes programmatically.

Create a composable to fetch project map nodes for navigation:

// composables/useUniformProjectMapNodes.ts import { ProjectMapClient } from "@uniformdev/project-map"; import { CANVAS_DRAFT_STATE, CANVAS_PUBLISHED_STATE, } from "@uniformdev/canvas"; export function useUniformProjectMapNodes() { const nuxtApp = useNuxtApp(); const isPreview = nuxtApp.$preview as boolean; const uniformConfig = useRuntimeConfig().public.$uniform; return useAsyncData( `project-map-nodes-${isPreview}`, async () => { const projectMapClient = new ProjectMapClient({ apiKey: uniformConfig.readOnlyApiKey, apiHost: uniformConfig.apiHost, projectId: uniformConfig.projectId, bypassCache: true, }); const state = process.env.NODE_ENV === "development" || isPreview ? CANVAS_DRAFT_STATE : CANVAS_PUBLISHED_STATE; const response = await projectMapClient.getNodes({ state, depth: 1, }); return (response.nodes ?? []) .filter( (node) => node.path && node.type === "composition" && node.compositionId ) .map((node) => ({ title: node.name, url: node.path, })); } ); }

Use it in a navigation component:

<!-- components/Navigation.vue --> <script lang="ts" setup> export interface NavLink { title: string; url: string; } interface Props { navLinks: NavLink[]; } defineProps<Props>(); </script> <template> <nav class="navigation"> <NuxtLink v-for="{ url, title } in navLinks" :key="url" :href="url" > {{ title }} </NuxtLink> </nav> </template>

warning

Nuxt requires downloading the manifest locally. Unlike the Next.js App Router SDK which fetches the manifest at runtime, the Nuxt SDK expects the manifest to be imported as a JSON file and passed to the module configuration.

Add these scripts to your package.json:

{ "scripts": { "dev": "run-s download:manifest nuxt:dev", "nuxt:dev": "nuxt dev", "build": "run-s download:manifest nuxt:build", "nuxt:build": "nuxt build", "download:manifest": "uniform context manifest download --output ./lib/uniform/contextManifest.json", "uniform:push": "uniform sync push", "uniform:pull": "uniform sync pull", "uniform:publish": "uniform context manifest publish" } }
ScriptDescription
download:manifestDownloads the personalization manifest to a local JSON file
uniform:pushPushes local content definitions to your Uniform project
uniform:pullPulls content definitions from your Uniform project
uniform:publishPublishes the personalization manifest (required after changing signals, enrichments, or quirks)

note

The dev and build scripts use npm-run-all (run-s) to download the manifest before starting the dev server or building. Install it as a dev dependency: npm install -D npm-run-all.

The manifest file is imported in nuxt.config.ts:

import manifest from "./lib/uniform/contextManifest.json"; import type { ManifestV2 } from "@uniformdev/context"; export default defineNuxtConfig({ uniform: { manifest: manifest as ManifestV2, // ... }, });

Create uniform.config.ts at your project root to configure the Uniform CLI for content sync:

// uniform.config.ts import { uniformConfig } from "@uniformdev/cli/config"; module.exports = uniformConfig({ preset: "all", overrides: { serializationConfig: { directory: "./content" }, }, disableEntities: ["webhook"], });

This configuration:

  • Uses the all preset to sync all entity types
  • Serializes content definitions to the ./content directory
  • Disables webhook syncing (optional)

The SDK provides built-in support for Uniform's visual/contextual editing. When a composition is opened in the Uniform Canvas editor, the SDK automatically:

  1. Listens for composition update messages from the editor
  2. Re-renders the component tree with the updated data
  3. Enables inline editing for UniformText and UniformRichText components
  4. Annotates components with editing attributes for the Canvas UI

This composable is integrated into UniformComposition automatically. For custom setups, you can use it directly:

<script lang="ts" setup> import { useUniformContextualEditing } from "@uniformdev/canvas-vue"; import { shallowRef } from "vue"; const initialData = shallowRef(compositionData); const { composition, isContextualEditing } = useUniformContextualEditing({ initialCompositionValue: initialData, enhance: (message) => message.composition, }); </script>
Return ValueTypeDescription
compositionComputedRef<RootComponentInstance>The current composition (updates during editing)
isContextualEditingComputedRef<boolean>Whether the app is currently in contextual editing mode

If you need to enhance the composition data during contextual editing (e.g., to run data enhancers), pass a custom contextualEditingEnhancer to UniformComposition:

<UniformComposition :data="composition" :resolveRenderer="resolveRenderer" :contextualEditingEnhancer="enhance" />

The enhancer function receives the update message and should return the enhanced composition:

const enhance = async (message: { composition: RootComponentInstance; hash: string; }) => { // Run custom enhancers on the composition return message.composition; };

The Nuxt module. Register it in nuxt.config.ts under modules.

Automatically provides:

  • UniformComposition, UniformPlayground, UniformSlot, UniformText, UniformRichText as global components
  • useUniformComposition composable
  • UniformContextProvider wrapping the app
  • Runtime config under useRuntimeConfig().public.$uniform

Component and utility exports for Canvas rendering:

ExportDescription
UniformCompositionMain composition renderer component
UniformPlaygroundPlayground composition renderer for visual editing
UniformComponentInternal component renderer (for advanced usage)
UniformSlotRenders child components from a named slot
UniformTextRenders text parameters with inline editing
UniformRichTextRenders rich text parameters with formatting
DefaultNotImplementedComponentFallback component for unknown types
useUniformContextualEditingComposable for custom contextual editing setup
useUniformCurrentCompositionAccess the current composition data from any descendant
useUniformCurrentComponentAccess the current component data from any descendant
convertComponentToPropsConverts a ComponentInstance to flattened props
getParameterAttributesGets editing attributes for a parameter
ResolveRendererType for the component resolver function
ComponentPropsType for component props (flattened)

Context and personalization component and composable exports:

ExportDescription
UniformContextProviderProvides Context to the component tree
PersonalizeRenders personalized content variations
PersonalizeEdgeEdge-mode personalization renderer
TestRenders A/B test variations
TrackTracks enrichment behavior on viewport visibility
TrackSlotTracks enrichment behavior on component load
useUniformContextComposable: access Context instance
useQuirksComposable: reactive quirk values
useScoresComposable: reactive score values
useIsPersonalizedComposable: whether inside a personalized variant
provideUniformContextManually provide Context (for custom setups)
onRouteChangeTrigger a route change event on the Context

Core Canvas types and utilities:

ExportDescription
flattenValuesExtracts values from asset parameters
ComponentInstanceType for a component in the composition tree
RootComponentInstanceType for the root composition component
ComponentParameterType for raw parameter data
AssetParamValueType for asset parameter values
CANVAS_DRAFT_STATEConstant for draft content state
CANVAS_PUBLISHED_STATEConstant for published content state
ExportDescription
ProjectMapClientClient for querying project map nodes

type ResolveRenderer = ( componentInstance: ComponentInstance ) => Component<any>;
type ComponentProps<TProps = unknown> = TProps & { component: ComponentInstance; };
type UniformCompositionProps = { /** The composition data */ data?: RootComponentInstance; /** Function to resolve Vue components for Canvas component types */ resolveRenderer?: ResolveRenderer; /** When to track enrichment behavior: "onView" | "onLoad" */ behaviorTracking?: "onLoad" | "onView"; /** Custom enhancer for contextual editing updates */ contextualEditingEnhancer?: ( message: Pick<UpdateCompositionMessage, "composition" | "hash"> ) => RootComponentInstance | Promise<RootComponentInstance>; };
type UniformContextProps = { /** The configured Uniform Context instance */ context: Context; /** Output type: "standard" (SSR/CSR evaluated) or "edge" (all variants output) */ outputType?: "standard" | "edge"; /** Whether to track route changes automatically */ trackRouteOnRender?: boolean; };
type UniformSlotProps = { /** Name of the slot to render */ name?: string; /** Override the resolve renderer for this slot's children */ resolveRenderer?: ResolveRenderer; };
type UniformTextProps = { /** The ID of the parameter */ parameterId: string; /** HTML element to render (default: "span") */ as?: string; /** Enable multi-line mode with white-space: pre-wrap */ isMultiline?: boolean; /** Placeholder text in the Canvas editor */ placeholder?: string | ((parameter: { id: string }) => string | undefined); };
type UniformRichTextProps = { /** The ID of the rich text parameter */ parameterId: string; /** Custom resolver for rich text node renderers */ resolveRichTextRenderer?: RenderRichTextComponentResolver; /** Wrapper HTML element */ as?: keyof HTMLElementTagNameMap; /** Placeholder text in the Canvas editor */ placeholder?: string | ((parameter: { id: string }) => string | undefined); };

  1. Install packages:
npm install @uniformdev/uniform-nuxt@latest @uniformdev/canvas@latest @uniformdev/canvas-vue@latest @uniformdev/context@latest @uniformdev/context-vue@latest @uniformdev/project-map@latest npm install -D @uniformdev/cli@latest npm-run-all
  1. Add environment variables to .env:
UNIFORM_API_KEY=your-api-key UNIFORM_PROJECT_ID=your-project-id
  1. Download the personalization manifest:
npx uniform context manifest download --output ./lib/uniform/contextManifest.json
  1. Configure nuxt.config.ts:
import manifest from "./lib/uniform/contextManifest.json"; import type { ManifestV2 } from "@uniformdev/context"; export default defineNuxtConfig({ modules: ["@uniformdev/uniform-nuxt"], uniform: { projectId: process.env.UNIFORM_PROJECT_ID, readOnlyApiKey: process.env.UNIFORM_API_KEY, manifest: manifest as ManifestV2, defaultConsent: true, outputType: "standard", playgroundPath: "/playground", }, });
  1. Create the component resolver (components/canvasComponents.ts):
import type { ComponentInstance } from "@uniformdev/canvas"; import { DefaultNotImplementedComponent, type ResolveRenderer, } from "@uniformdev/canvas-vue"; const componentMap: Record<string, any> = {}; export const resolveRenderer: ResolveRenderer = (component) => { return ( componentMap[component.type] ?? DefaultNotImplementedComponent ); };
  1. Create the catch-all route (pages/[...path].vue):
<script lang="ts" setup> import { resolveRenderer } from "~/components/canvasComponents"; const route = useRoute(); const path = Array.isArray(route.params.path) ? `/${route.params.path.join("/")}` : `/${route.params.path}`; const { composition, error } = await useUniformComposition({ projectMapNodePath: path, }); if (error?.value) { console.error("Error fetching composition", error.value); } </script> <template> <UniformComposition v-if="composition" :data="composition" :resolveRenderer="resolveRenderer" /> </template>
  1. Create the playground route (pages/playground.vue):
<script lang="ts" setup> import { resolveRenderer } from "~/components/canvasComponents"; </script> <template> <UniformPlayground :resolveRenderer="resolveRenderer" /> </template>
  1. Create CORS middleware (server/middleware/cors.ts):
export default defineEventHandler((event) => { setResponseHeaders(event, { "Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Headers": "*", "Access-Control-Expose-Headers": "*", }); if (getMethod(event) === "OPTIONS") { event.res.statusCode = 204; event.res.statusMessage = "No Content."; return "OK"; } });
  1. Add scripts to package.json:
{ "scripts": { "dev": "run-s download:manifest nuxt:dev", "nuxt:dev": "nuxt dev", "build": "run-s download:manifest nuxt:build", "nuxt:build": "nuxt build", "download:manifest": "uniform context manifest download --output ./lib/uniform/contextManifest.json", "uniform:push": "uniform sync push", "uniform:pull": "uniform sync pull", "uniform:publish": "uniform context manifest publish" } }
<!-- components/Article.vue --> <script lang="ts" setup> import type { ComponentInstance } from "@uniformdev/canvas"; import type { UniformRichTextNodeProps } from "@uniformdev/canvas-vue"; interface Props { title: string; subtitle?: string; body?: UniformRichTextNodeProps["node"]; component: ComponentInstance; } defineProps<Props>(); </script> <template> <article class="max-w-4xl mx-auto"> <UniformText parameterId="title" as="h1" class="text-3xl font-bold mb-2" placeholder="Article title" /> <UniformText parameterId="subtitle" as="p" class="text-lg text-gray-600 mb-8" placeholder="Article subtitle" /> <div class="grid grid-cols-3 gap-8"> <div class="col-span-2"> <UniformRichText parameter-id="body" as="div" class="prose" placeholder="Write your article content here" /> </div> <aside> <UniformSlot name="sidebar" /> </aside> </div> <section class="mt-12"> <h2 class="text-2xl font-semibold mb-4">Related</h2> <UniformSlot name="relatedContent"> <template #default="{ child }"> <div class="mb-4"> <component :is="child" /> </div> </template> </UniformSlot> </section> </article> </template>

Register it in your component resolver:

import Article from "./Article.vue"; const componentMap = { article: Article, // ...other components };
<!-- components/PersonalizedGreeting.vue --> <script lang="ts" setup> import { useQuirks, useScores } from "@uniformdev/context-vue"; const quirks = useQuirks(); const scores = useScores(); const topInterest = computed(() => { if (!scores.value) return null; const entries = Object.entries(scores.value); if (entries.length === 0) return null; return entries.sort(([, a], [, b]) => b - a)[0]?.[0]; }); </script> <template> <div class="p-4 bg-blue-50 rounded-lg"> <p v-if="quirks?.country"> Welcome from {{ quirks.country }}! </p> <p v-if="topInterest"> We see you're interested in {{ topInterest }}. </p> </div> </template>
<!-- components/ForgetMe.vue --> <script lang="ts" setup> const { context } = useNuxtApp().$useUniformContext(); const forgetMe = async () => { await context.forget(true); }; </script> <template> <button @click="forgetMe">Forget me</button> </template>
<!-- pages/[...path].vue --> <script lang="ts" setup> import { resolveRenderer } from "~/components/canvasComponents"; const route = useRoute(); const path = Array.isArray(route.params.path) ? `/${route.params.path.join("/")}` : `/${route.params.path}`; const { composition } = await useUniformComposition({ projectMapNodePath: path, }); // Extract metadata from composition parameters const metaTitle = composition?.parameters?.metaTitle?.value as string; const metaDescription = composition?.parameters?.metaDescription?.value as string; useHead({ title: metaTitle, meta: [ { name: "description", content: metaDescription }, ], }); </script> <template> <UniformComposition v-if="composition" :data="composition" :resolveRenderer="resolveRenderer" /> </template>

  1. Use TypeScript -- The SDK is TypeScript-first. Define Props interfaces for all components with proper types for each parameter.

  2. Use DefaultNotImplementedComponent as fallback -- Always return DefaultNotImplementedComponent from your resolveRenderer for unknown component types. It provides helpful development-time feedback about missing implementations.

  3. Download manifest before build -- Use run-s download:manifest nuxt:build to ensure the manifest is always up-to-date. Never commit a stale manifest.

  4. Run uniform:publish after changes -- Always run uniform context manifest publish after modifying signals, enrichments, or quirks. The manifest must be re-downloaded after publishing.

  5. Use flattenValues for assets -- Always use flattenValues from @uniformdev/canvas for asset parameters rather than manually traversing the parameter structure.

  6. Access the component prop for metadata -- When you need raw parameter data, component IDs, or slot information, use the component prop rather than trying to reconstruct the data from flattened props.

  7. Keep the CORS middleware in place -- The CORS server middleware is required for visual editing in the Uniform Canvas editor. Do not remove it even if your production site doesn't need CORS.

  8. Use useAsyncData for data fetching -- When fetching additional data in your components (like project map nodes for navigation), use Nuxt's useAsyncData to benefit from SSR data transfer and caching.

  9. Prefer standard output type -- Unless you have a specific edge personalization setup, use outputType: "standard" for simpler and more predictable personalization behavior.

  10. Keep resolveRenderer lightweight -- The resolver function is called for every component in the tree. Keep it as a simple lookup map rather than running complex logic.