Reference Live preview
Custom Live Preview implementation
No matter what front-end framework you are using, you can build your own hook using the same underlying tooling that Strife provides.
First, install the @strifeapp/strife base package:
npm install @strifeapp/strifeThis packages provides you with the following functions:
| Path | Description |
|---|---|
subscribe | Subscribes to the the Strife studio's window.postMessage events and calls the provided callback function. |
The subscribe function takes the following arg:
| Path | Description |
|---|---|
callback | A callback function that is called with data every time a change is made to the document. |
With this function, you can build your own hook using your front-end framework of choice:
import { subscribe } from '@strifeapp/strife'
const onChange = (data) => { ... }
const unsubscribe = subscribe(onChange);
// To unsubscribe, call `unsubscribe();`
// To build your own hook, subscribe to Live Preview events using the`subscribe` function
// It handles everything from:
// 1. Listening to `window.postMessage` events
// 2. Calling the `onChange` callback with the result
// Your hook should also:
// 1. Handle the results of the `onChange` callback to update the UI
// 2. Unsubscribe from the `window.postMessage` events when it unmountsCallback data shape
The data argument passed to your callback is a plain object representing the current document state, keyed by each editor's propertyName. For a template defined as:
const Homes: Template = {
displayName: 'Home',
collection: 'Homes',
editors: [
{ label: 'Heading', editor: { name: 'str-input', type: 'text', propertyName: 'heading' } },
{ label: 'Description', editor: { name: 'str-textarea', type: 'text', propertyName: 'description' } },
],
};the callback receives:
subscribe((data) => {
// data.heading: string — current value of the Heading editor
// data.description: string — current value of the Description editor
});Every editor's propertyName becomes a top-level key on data. Nested types (e.g. objects from str-image-group or arrays from str-multi-select) follow the same shape as they appear in the stored document.
Hover highlighting and stegaClean
While a document is open in the editor's live preview, moving the pointer over your page outlines the element under it on the page, and a click on it opens the matching field in the editor: the tab it sits on, the chapter it belongs to, an image's editing card. To make that work for a site that renders the state itself, the editor hides each text field's path inside the text value as zero-width characters (the same technique as Vercel's Content Link and Sanity's Visual Editing). They carry no glyph and no width, HTML escaping leaves them alone, and a published site never receives them: only the editor's preview sends them.
They are still characters, so a comparison against a preview value can fail:
import { subscribe, stegaClean } from '@strifeapp/strife'
subscribe((data) => {
data.heading === 'Welcome'; // false in the preview: the value starts with the hidden path
stegaClean(data.heading) === 'Welcome'; // true
const clean = stegaClean(data); // a deep copy with every hidden path removed
});Use stegaClean on any preview value you compare, match a route against, or measure (length). Values a site is likely to compare are never encoded to begin with: slugs, dates, numbers, option codes of pickers and radio groups, labels, keywords and SEO fields. Only text, textarea and rich text values carry a path, plus an image's alt text (its URL is never touched).
Rendering needs no change: print the values as they come, and the pointer resolves the field from the text under it, or from an image's alt attribute. A click keeps its effect on your page — nothing is prevented. If your components already carry data-field attributes (the SDK's own web components do), those win over the hidden path.
| Path | Description |
|---|---|
stegaClean | Removes the hidden field paths from a string, or recursively from an array or plain object. |
stegaDecode | Returns the field path hidden in a string, or null. Useful for building your own hover affordance. |
Initialization timing
Strife Studio performs a handshake with the preview iframe at page load. For subscribe to receive events, the SDK must be present in the page before that handshake completes — meaning before your framework has finished hydrating client components.
Load the SDK eagerly at the top of your application, via either of these patterns:
npm install + import in your entry-point (recommended for build-tool projects):
// src/main.js (or main.ts, index.js, wherever your app boots)
import "@strifeapp/strife";CDN tag in your HTML <head> (simplest when you don't have a build step):
<script type="module" src="https://unpkg.com/@strifeapp/strife"></script>Components can then import subscribe as normal — the ESM module is cached and resolves to the already-initialized singleton.