Skip to content

Static Renderer (SSR Compatible)

The CraftStaticRenderer component and renderCraftNodesToVNodes function provide SSR-compatible rendering of CraftNode content without requiring the Pinia editor store.

Static Renderer Demo

Below you can see a working demo of the Static Renderer.
It renders CraftNode content without any editor state or Pinia dependency, making it suitable for SSR and static site generation.


Show Code
vue
<template>
  <div class="content">
    <CraftStaticRenderer :nodes="demoContent" :resolverMap="resolverMap" />
  </div>
</template>
<script lang="ts" setup>
import { CraftStaticRenderer } from "@versa-stack/v-craft";
import { resolverMap } from "./resolvermap";
import { demoContent } from "./demo-content";
</script>

When to Use

Use the static renderer when:

  • Building static sites with Nuxt, Astro, or similar frameworks
  • Server-side rendering page content
  • Displaying view-only content without editor functionality
  • Pre-rendering pages at build time

CraftStaticRenderer Component

The simplest way to render CraftNode arrays in SSR environments.

Basic Usage

vue
<template>
  <CraftStaticRenderer
    :nodes="pageContent"
    :resolver-map="resolverMap"
  />
</template>

<script setup>
import { CraftStaticRenderer } from '@versa-stack/v-craft'

const resolverMap = {
  MyButton: {
    componentName: 'MyButton',
    defaultProps: { variant: 'primary' }
  },
  MyText: {
    componentName: 'MyText',
    defaultProps: {}
  }
}

const pageContent = [
  {
    uuid: '1',
    componentName: 'MyText',
    props: { content: 'Hello World' },
    slots: {}
  }
]
</script>

Props

PropTypeRequiredDescription
nodesCraftNode[]YesArray of CraftNode objects to render
resolverMapCraftNodeResolverMapYesComponent resolver configuration
nodeDataMapRecord<string, CraftNodeDatasource>NoData sources keyed by node UUID
eventsContextRecord<string, any>NoContext object available to event handlers

Nuxt Example

vue
<template>
  <div class="page-content">
    <CraftStaticRenderer
      v-if="content?.length"
      :nodes="content"
      :resolver-map="resolverMap"
    />
  </div>
</template>

<script setup>
import { CraftStaticRenderer } from '@versa-stack/v-craft'

const { $vcraftConfig } = useNuxtApp()
const resolverMap = $vcraftConfig?.resolverMap || {}

const { data: content } = await useFetch('/api/page-content')
</script>

renderCraftNodesToVNodes Function

For programmatic rendering or custom render functions.

Basic Usage

ts
import { renderCraftNodesToVNodes } from '@versa-stack/v-craft'
import { h } from 'vue'

const vnodes = renderCraftNodesToVNodes(nodes, {
  resolverMap,
  componentRegistry: { MyButton, MyText }
})

Options

ts
interface RenderOptions<T extends object> {
  resolverMap: CraftNodeResolverMap<T>
  componentRegistry?: Record<string, any>
}
OptionTypeRequiredDescription
resolverMapCraftNodeResolverMapYesComponent resolver configuration
componentRegistryRecord<string, Component>NoMap of component names to actual components. If not provided, component names are used as-is (requires global registration)

Custom Render Function Example

vue
<script setup>
import { renderCraftNodesToVNodes } from '@versa-stack/v-craft'
import { h, computed } from 'vue'
import MyButton from './MyButton.vue'
import MyText from './MyText.vue'

const props = defineProps<{
  nodes: CraftNode[]
}>()

const resolverMap = {
  MyButton: { componentName: 'MyButton' },
  MyText: { componentName: 'MyText' }
}

const renderedContent = computed(() => 
  renderCraftNodesToVNodes(props.nodes, {
    resolverMap,
    componentRegistry: { MyButton, MyText }
  })
)
</script>

<template>
  <component :is="() => renderedContent" />
</template>

Comparison with CraftFrame

FeatureCraftFrameCraftStaticRenderer
SSR Compatible
Requires Pinia
Edit Mode
View-Only Mode
Drag & Drop
Event Handlers
Data Sources
Visibility Control

Migration from CraftFrame

If you're currently using CraftFrame with viewOnly: true and experiencing SSR issues:

Before (SSR Error)

vue
<template>
  <CraftFrame :view-only="true" :resolver-map="resolverMap" />
</template>

<script setup>
import { CraftFrame, useEditor } from '@versa-stack/v-craft'

const editor = useEditor()
editor.setNodes(content)
</script>

After (SSR Compatible)

vue
<template>
  <CraftStaticRenderer :nodes="content" :resolver-map="resolverMap" />
</template>

<script setup>
import { CraftStaticRenderer } from '@versa-stack/v-craft'
</script>

Nested Content

The static renderer handles nested content automatically:

ts
const nodes = [
  {
    uuid: '1',
    componentName: 'Container',
    props: { padding: '20px' },
    slots: {
      default: [
        {
          uuid: '2',
          componentName: 'MyText',
          props: { content: 'Nested content' },
          slots: {}
        }
      ]
    }
  }
]

Content is passed as default slot content to parent components.

Event Handlers

Event handlers work the same as in CraftFrame. Define events on nodes and provide context:

vue
<template>
  <CraftStaticRenderer
    :nodes="nodes"
    :resolver-map="resolverMap"
    :events-context="eventsContext"
  />
</template>

<script setup>
const eventsContext = {
  showAlert: (message) => alert(message),
  navigate: (path) => router.push(path),
  trackEvent: (name) => analytics.track(name),
}

const nodes = [
  {
    uuid: '1',
    componentName: 'MyButton',
    props: { label: 'Click me' },
    events: {
      click: 'ctx.showAlert("Button clicked!")'
    },
    slots: {}
  }
]
</script>

Data Binding

For information about binding dynamic data to nodes using nodeDataMap, see the Data Wrappers documentation.

Limitations

The static renderer is designed for view-only SSR scenarios:

  • No editor state: Cannot select, drag, or modify nodes
  • No node refs: Does not register DOM element references

For editing capabilities, use CraftFrame on the client side.