Build a Znode 10 Page Builder widget for preview and storefront

Design a small technical-resources widget around one saved contract, then verify editing, rendering, links, and existing content in both applications.

Znode 10GuideIntermediateGCG engineering guide
In this guide
Editor fields Saved configuration Shared view model Storefront render
One saved content contract should produce consistent results in preview and the published storefront.

A widget has two audiences

A marketer adds a technical-resources panel in Page Builder. It looks correct in preview, so they publish it. On the storefront, the heading falls back to a default and one link disappears. The component works in isolation; the contract between the editor and renderer does not.

Build the widget around the saved content it owns. For this resource panel, that is a heading, one link label, and one local path. A single resource action keeps the first implementation useful and bounded, with a clear path for adding a list later through a deliberate saved-contract change.

Implement the shared widget

Create ResourcePanelConfig.tsx under your theme configuration's widgets/ui-widgets/resource-panel folder. The file includes the props model, URL normalizer, React renderer, editor fields, and defaults. It exposes one resource link, which keeps the saved contract small and makes its full editorial lifecycle easy to inspect.

The current Znode guide uses @puckeditor/core. Install the documented 0.21.1 package as part of the complete Puck upgrade, including existing page migration. Older SDK branches using @measured/puck need that coordinated package migration before adopting this import. Keep the ResourcePanel saved key stable across the transition.

widgets/ui-widgets/resource-panel/ResourcePanelConfig.tsx tsx

import type { ComponentConfig } from "@puckeditor/core";
export type ResourcePanelProps = {
heading: string;
actionLabel: string;
actionPath: string;
};
export function localPath(value: string): string | null {
if (!value.startsWith("/") || value.startsWith("//") ||
/[\\\s]/.test(value)) return null;
const base = new URL("https://store.example.test");
const target = new URL(value, base);
return target.origin === base.origin
? target.pathname + target.search + target.hash : null;
}
export function ResourcePanel(props: ResourcePanelProps) {
const path = localPath(props.actionPath);
return (
<section className="gcg-resource-panel">
<h2>{props.heading}</h2>
{path && props.actionLabel.trim() &&
<a href={path}>{props.actionLabel}</a>}
</section>
);
}
export const ResourcePanelConfig: ComponentConfig<ResourcePanelProps> = {
label: "Technical resource panel",
fields: {
heading: { type: "text", label: "Heading" },
actionLabel: { type: "text", label: "Link label" },
actionPath: { type: "text", label: "Local link path" }
},
defaultProps: {
heading: "Technical resources",
actionLabel: "View documents",
actionPath: "/resources"
},
render: (props) => <ResourcePanel {...props} />
};

Add defaults without overwriting editorial work

Defaults make a newly inserted widget useful. They should preserve existing saved content. The example supplies a default heading and resource action when a widget is inserted. Its renderer uses the actual saved values, including a deliberately cleared label.

When extending an existing widget, preserve its saved key and supported properties unless you are explicitly migrating configuration. Changing a registration name can strand existing content. Adding a required field without a fallback can break pages created before the release. Test an older serialized configuration alongside a newly created widget, and document the supported migration if the contract must change.

Wire both applications deliberately

Locate the target SDK's theme configuration and the editor's registration entry. Then trace how the storefront resolves the same saved component. Do not assume importing the component into one application makes it available in the other. Shared packages can still have different translation providers, data loaders, or build boundaries.

Keep browser-only behavior out of the saved content adapter. If the widget later loads account-specific documents, authorize that request on the server and avoid putting protected document data in a publicly cached page. Static technical resources make a useful first widget because their publication lifecycle can be understood without introducing account permissions at the same time.

Register ResourcePanelConfig under a stable ResourcePanel key in your theme's config/override-component-list.ts, alongside the theme's existing entries. The surrounding root-config.ts and theme loader retain their current configuration. That registration follows the path documented by Znode; both deployed applications need the same configuration and component package.

config/resource-component-entry.ts typescript

import { ResourcePanelConfig } from "../widgets/ui-widgets/resource-panel/ResourcePanelConfig";
export const resourceComponentEntry = {
ResourcePanel: ResourcePanelConfig
};
// Spread resourceComponentEntry into the existing addOrOverrideComponents map.
// Preserve all other registrations in that theme.

Walk through the complete editorial journey

Create a page with the widget, change every field, save, leave the editor, and reopen it. Verify the exact values survived. Preview the page, publish through the target environment's supported workflow, and inspect the public storefront in a separate session. Check headings, the resource action, missing labels, and links.

Also test a cleared action label, an older saved configuration, an invalid path, long labels, and a narrow viewport. Navigate every link with a keyboard. If the widget contains interaction, verify focus and reduced-motion behavior where relevant. Include this editorial journey in the paired Page Builder and Webstore release checks.

Keep the first release easy to maintain

Package the saved schema, renderer, defaults, and tests together. Record the SDK and theme versions with the release so the next upgrade has an identifiable comparison point. A screenshot is useful evidence of appearance, but preserve the configuration fixture too; it explains what produced that appearance.

Add richer features only after the basic lifecycle is reliable. A useful widget is more than a visually convincing preview. It is content that an editor can understand, save, revisit, and publish with predictable results across the applications that consume it.

References and further reading

Bring your next engineering question.

Build a useful Znode storefront

Explore how GCG can help

Independent guidance from GCG. Znode is a trademark of its owner. Examples use fictional data and are not official platform documentation. Suggest a correction.