Live Preview

To make your site editable with Hydra you load hydra.js in your frontend and call initBridge(). This sets up a two-way communication channel that handles authentication, page navigation, and live content updates.


Setting Up the Bridge

Call initBridge() with an onEditChange callback to receive live content updates as the user edits. Your frontend re-renders in real time. Outside edit mode, fetch content from the API as normal.

import { initBridge } from './hydra.js';

let bridge;
if (window.name.startsWith('hydra')) {
    bridge = initBridge({
        onEditChange: (formData) => renderPage(formData),
    });
} else {
    renderPage(await fetchContent(window.location.pathname));
}

The formData passed to onEditChange has the same structure as the Plone REST API response, so the same rendering code works for both live editing and normal page display.

A Simple Page Renderer

Iterate blocks_layout.items and render each block by type. Add data-block-uid so Hydra knows which block the user clicked.

<!DOCTYPE html>
<html>
<head>
    <script type="module">
    import { initBridge } from './hydra.js';

    if (window.name.startsWith('hydra')) {
        initBridge({
            onEditChange: (formData) => renderPage(formData),
        });
    } else {
        renderPage(await fetchContent(window.location.pathname));
    }

    function renderPage(data) {
        document.getElementById('content').innerHTML =
            data.blocks_layout.items.map(id => {
                const block = data.blocks[id];
                return `<div data-block-uid="${id}">
                    ${renderBlock(block)}
                </div>`;
            }).join('');
    }

    function renderBlock(block) {
        switch (block['@type']) {
            case 'slate':
                return renderSlate(block.value);
            case 'image':
                return `<img src="${block.url}/@@images/image" />`;
            default:
                return `<pre>${JSON.stringify(block, null, 2)}</pre>`;
        }
    }
    </script>
</head>
<body>
    <div id="content"></div>
</body>
</html>

Allowed Blocks and Page Regions

When initialising the bridge, you can configure rules for what blocks can be added to the page and where. Pages can have multiple blocks fields for different regions (e.g., header, content, footer), each with its own allowed block types and limits. These show as separate sections in the sidebar when no block is selected:

bridge = initBridge({
    page: {
        schema: {
            properties: {
                blocks_layout: {
                    title: 'Content',
                    allowedBlocks: ['slate', 'image', 'hero', 'columns'],
                },
                header_blocks: {
                    title: 'Header',
                    allowedBlocks: ['slate', 'image'],
                    maxLength: 3,
                },
                footer_blocks: {
                    title: 'Footer',
                    allowedBlocks: ['slate', 'link'],
                },
            },
        },
    },
});