Public Site
Preview Listener
Overview
Vextro's admin panel includes a live preview sidebar (VextroLivePreview) that embeds your public site in an iframe and broadcasts field values to it over postMessage as editors type. The vextro/preview subpath provides the frontend listener that your public site uses to receive and apply those updates in real time.
The preview system has two sides:
- Admin side --
VextroLivePreview(built into the admin panel; no extra configuration needed) - Frontend side --
initVextroPreviewListener(add to your public Astro page or layout)
Quick Start
Add the listener to your preview page or layout:
---
// src/pages/posts/[slug].astro
---
<html>
<head>...</head>
<body>
<h1 data-vextro-field="title">Default Title</h1>
<p data-vextro-field="description">Default description</p>
<img data-vextro-field="coverImageUrl" data-vextro-attr="src" />
<script>
import 'vextro/preview';
</script>
</body>
</html> When an editor types in the admin panel with preview open, the listener updates the DOM automatically.
initVextroPreviewListener
The underlying function that vextro/preview calls automatically on module load. Consumer pages should rely on auto-init and not call it directly.
Any import from vextro/preview -- named or side-effect -- triggers module auto-initialization. Calling initVextroPreviewListener() after that registers a second listener on window. Use the side-effect import and let auto-init handle registration:
// Correct: auto-init runs when the module loads
import 'vextro/preview'; The function is exported for advanced contexts where ES module side-effects are suppressed (SSR, test runners). In normal browser usage, rely on auto-init.
The listener attaches a message event handler to window. On each vextro-preview-update message it:
- Finds all elements with a
data-vextro-fieldattribute matching a key in the incoming data - Updates
textContentby default, or updates the attribute named bydata-vextro-attrif present - Dispatches a custom
vextro:preview-dataevent for advanced handling
DOM Attributes
data-vextro-field
Marks an element as a preview target. Set the value to the field name that should update it.
<!-- Updates textContent with the "title" field value -->
<h1 data-vextro-field="title">Placeholder</h1>
<!-- Updates textContent with the "excerpt" field value -->
<p data-vextro-field="excerpt">...</p> data-vextro-attr
When present alongside data-vextro-field, the listener updates the named attribute instead of textContent.
<!-- Updates the src attribute with the "heroImageUrl" field value -->
<img data-vextro-field="heroImageUrl" data-vextro-attr="src" alt="" />
<!-- Updates the href attribute with the "ctaUrl" field value -->
<a data-vextro-field="ctaUrl" data-vextro-attr="href">Read more</a> Custom Event
For fields that require more complex handling (rich text, arrays, nested structures), listen to the vextro:preview-data event on window. This fires for every incoming preview message regardless of data-vextro-field bindings.
import { renderTipTapToHtml } from 'vextro/content';
import type { VextroPreviewEventDetail } from 'vextro/preview';
window.addEventListener('vextro:preview-data', (e: CustomEvent<VextroPreviewEventDetail>) => {
const { data, collectionSlug, documentId } = e.detail;
// Manually update a rich text block with new content
if (data.body) {
document.querySelector('#body').innerHTML = renderTipTapToHtml(data.body);
}
}); Event detail
type VextroPreviewEventDetail = {
data: Record<string, unknown>; // All field values from the admin form
collectionSlug?: string; // Collection the preview document belongs to
documentId?: string; // ID of the document being previewed
}; Admin Setup
The preview sidebar is built into the admin panel. To add a preview button to a collection's document editor, add preview to the collection's actions config:
// convex/collections/posts.ts
import { defineVextroCollection } from 'vextro';
export const posts = defineVextroCollection({
slug: 'posts',
// ...
preview: {
url: 'https://yoursite.com/posts/{slug}',
},
}); The {slug} placeholder (or any field name in curly braces) is replaced with the current document's field value before the iframe loads. The {_id} placeholder resolves to the document ID.
Message Protocol
The admin panel sends messages in this shape. You do not need to handle this directly when using initVextroPreviewListener.
type VextroPreviewMessage = {
type: 'vextro-preview-update';
data: Record<string, unknown>; // Field values; _collectionSlug and _id are nested here
}; Security
The listener accepts messages from any origin because preview may be served from a different domain than the admin panel. The vextro:preview-data custom event detail does not expose the message origin. If your security model requires origin validation, add a native window.addEventListener('message', handler) listener and check event.origin there -- the native MessageEvent always carries the sender's origin.
initVextroPreviewListener only updates the DOM -- it never writes data back to Convex or the admin panel. It is safe to include in production builds; the listener is a no-op when no admin preview message is received.