Build Content Blocks for Site-Wide Regions in PWA Kit
Build content blocks that merchants add to site-wide regions, such as the header and the mega menu. These site-wide content blocks appear on every page containing the region. With content blocks in site-wide regions, merchants maintain brand consistency and save time when updating promotional messages or navigation elements that appear site-wide. For example, merchants can add an announcement banner to a header that appears on every page in the storefront.
Let’s walk though the process of building an announcement banner content block that can be added to the storefront header as an example. The announcement banner will appear on all page headers in the storefront. A Page Designer component (instance id header) exposes an announcement region above the storefront’s header. This task involves two React components, their registry entries, and two B2C descriptors.
- A
useComponentfetch of the site-wide component in your app shell, rendered inline through<Region>. - An
EmbeddedSubtreeProviderwrapper (new instorefront-next-runtime@1.2.0) that tells the design runtime this subtree is a site-wide region, not a page. - Two new components: a
Headerlayout that hosts the region and anAnnouncementBannercontent block, in addition to their registry and type-map entries. - Two new cartridge descriptors, one of which uses the site-wide-region
"embedded": trueflag and an explicitcomponent_id.
- PWA Kit v3.21 or later.
- Page Designer integration with PWA Kit. See Integrate Page Designer with PWA Kit.
@salesforce/commerce-sdk-reactwith theuseComponenthook. ImportuseComponentfrom the package root. ImportRegion/Page/registry/PageDesignerProviderfrom the/page-designersubpath.@salesforce/storefront-next-runtime@1.1.0or later—this is the version that exportsEmbeddedSubtreeProviderfrom@salesforce/storefront-next-runtime/design/react/core. Site-Wide Regions don’t work on version0.4.2.- Node 24 or later for tooling and local development.
- A SLAS client with the
sfcc.shopper-experiencescope—useComponentcalls the Shopper ExperiencegetComponentendpoint.
The Site-Wide Regions for Content Blocks feature requires PWA Kit v3.21 or later, which includes new fields that this feature uses. If you built your storefront with v3.21 or later of the template, your storefront contains the new fields. Otherwise, add the new fields manually.
The new fields are:
embedded: This field indicates whether the header component can contain a content block.component_id: This field identifies the header and is required for calling thegetComponentB2C Commerce API (SCAPI) call to get the embedded content block in a page that’s not in Page Designer—a page that’s not a product detail or listing, about us, or home page. The get Component API endpoint is:GET /experience/shopper-experience/v1/organizations/{organizationId}/components/{componentId}. See Shopper Experience endpoints.
The header component holds no chrome of its own—your storefront’s existing Header or AboveHeader remain the visual header. This component’s only job is to expose the announcement region so content managers have a place to drop blocks. It is a thin <Region> passthrough.
In this example:
- The header component renders a single named region.
<Region component={component} regionId="announcement" />renders whatever content blocks a merchandiser placed in theannouncementregion of this component instance. componentis required. When the V2 pipeline renders this component (via<Region>/<Component>), it injects the component’s own data as thecomponentprop. You pass it to the nested<Region>.- Export the header component from the layouts barrel so that the component map can import it (Step 4).
This is the actual content block a merchandiser drops into the header’s announcement region. It is a leaf component: no regions, just editable attributes.
In this example:
- The prop names are the descriptor’s attribute ids.
message,linkUrl,linkText,colorScheme,height,alignmentmap 1:1 to theattribute_definitionsyou author in Step 5. Keep them in sync. if (!message) return null. A banner with no message renders nothing—a required attribute, defensively enforced in the component too.- Token-based color/height/alignment, normalized.
normalize()clamps any unexpected value back to a safe default (md/center/primary) so a bad authored value can’t break layout or contrast. - Absolute vs. relative links.
isAbsoluteURL(frompage-designer/utils) picks between the ChakraLink(externalhref) and the app routerLink(internalto), so internal links stay client-side. - The
fallbacknamed export is the Suspense placeholder. The V2 registry renders a module’sfallbackexport while the component’s code-split chunk loads. ExportingAnnouncementBannerFallback as fallbackwires that up; the skeleton mirrors the default banner height to avoid layout shift.
Add a content barrel so the component map can import from a single path:
In your full storefront shell (StorefrontApp), fetch the header component with useComponent and render its announcement region above the storefront header, wrapped in EmbeddedSubtreeProvider.
Inside StorefrontApp, fetch the site-wide component alongside the other top-level data hooks:
Then render the region above <AboveHeader />, gated on the component existing:
In this example:
useComponent({parameters: {componentId: 'header'}})fetches the site-wide component. ThecomponentIdis a fixed, well-known ID ('header'). It matches the descriptor’scomponent_idin Step 5. Unlike Content Block Editor, nothing supplies this ID per-request. The storefront always asks for the same component.useComponentis already Page-Designer-mode aware. It readsmodeorpdTokenfrom the provider config (the Content Block Editor plumbing you already have) and switches torawResponse: truein design or preview mode. You don’t add any mode handling here.- Gate on
embeddedHeader. Until the component resolves—or if no header content is authored—render nothing. There is no banner or layout shift beyond the component’s own fallback. EmbeddedSubtreeProvider embeddedmarks this subtree as a site-wide region, which is the new runtime piece. It tells the design runtime that the wrapped<Region>is a site-wide region living in your layout (not a Page Designer page), so Content Block Editor can target and edit it in place. Theembeddedboolean prop turns that behavior on.- The
embeddedboolean prop renders the sameannouncementregion as theHeaderlayout component (Step 1). The app-shell path (<Region component={embeddedHeader} regionId="announcement" />) is what shows on the live storefront. TheHeaderlayout component is the registry-resolved rendering used when the component is reached through the V2 pipeline (e.g. in Content Block Editor). Both render the identical region. - Not on checkout. The header region renders only in the non-checkout branch, matching where the storefront normally shows its header.
Add the two new components to the eager type map and the lazy importer registry. Site-wide regions add Header and AnnouncementBanner.
Export Header from the layouts barrel so the map import above resolves:
Add the two lazy importers to the registry:
Each typeId is {group}.{componentId}, which must equal {descriptor-folder}.{descriptor-filename}. For site-wide regions that means:
| typeId | Map Key | Registry Importer Key | Descriptor Path |
|---|---|---|---|
commerce_layouts.header | Header | commerce_layouts.header | commerce_layouts/header.json |
commerce_assets.announcementBanner | AnnouncementBanner | commerce_assets.announcementBanner | commerce_assets/announcementBanner.json |
All three columns must match byte-for-byte, or the component silently fails to resolve.
As with Content Block Editor, the React side only teaches the storefront how to render. The B2C Commerce side needs descriptors for both new components. The header descriptor is the one that uses the site-wide-region fields.
In this example:
"embedded": true. This field marks the component as hosting a site-wide region—a fixed component that lives in your storefront chrome, not one dropped into a Page Designer page. It is the flag Content Block Editor and the Content Blocks editor use to treat it as a site-wide region."component_id": "header". This field fixes the instance ID so the storefront can fetch it by a known ID—exactly thecomponentIdyour app shell passes touseComponent({parameters: {componentId: 'header'}})in Step 3.region_definitionsdeclares theannouncementregion. The regionid(announcement) is what both the app-shell<Region regionId="announcement">and theHeaderlayout component render. It’s where the merchandiser drops the banner.- No editable attributes on the header.
attribute_definitionsis empty—the header is a structural container. The editable content lives in the banner it hosts.
embedded and component_id are site-wide-region fields. A standard Content Block Editor component (leaf or layout, like the carousel) doesn’t use these. If you’re adding a normal editable component, follow the Content Block Editor guide’s descriptor shape instead.
The banner is a normal leaf content block—no embedded flag. Its attribute_definitions are the props the React component reads (Step 2).
The six attribute ids (message, linkUrl, linkText, colorScheme, height, alignment) are exactly the props the AnnouncementBanner component destructures. The enum values and default_values match the component’s normalize() allow-lists and fallbacks—keep the two definitions in lockstep.
Deploy the cartridge to B2C Commerce using the CLI. See Cartridges. Then add it to your site’s cartridge path in Administration > Sites > Manage Sites > {site} > Settings. After the descriptors are live, author the content in the Content Block editor in Merchant Tools > Content > Content Blocks. Create a block of the announcement-banner type, set its properties, and add it as a site-wide region on the header. To learn more, see the steps in Add a Content Block in a Site-Wide Region for Storefront Next. The Page Designer steps in Business Manager are identical for any supported storefront type.
Next, reload any storefront page—the banner renders above the header. Open it in the focused visual canvas to edit it in Content Block Editor.
If you test the components in isolation, this mock mirrors the shape useComponent returns for the header, including a nested announcement banner.
mockEmbeddedHeader has id: 'header' and typeId: 'commerce_layouts.header', and its single announcement region holds one commerce_assets.announcementBanner component whose data is the banner attributes. This is the exact shape <Region component={embeddedHeader} regionId="announcement"> expects—use it to render the subtree without a live getComponent call.