With Page Designer, you can create reusable page types and component types for your Progressive Web App (PWA) Kit site. Use the no-code Page Designer visual editor in Business Manager to design, schedule, and publish Page Designer pages for your site. When integrated with a PWA Kit site, Page Designer enables the rendering of dynamic, responsive pages that use React components.
This guide explains how to configure PWA Kit so that your site can show Page Designer pages.
Before running the commands in this topic, replace any placeholders with actual values. Placeholders have this format: $PLACEHOLDER.
Prerequisites
To integrate Page Designer with a PWA Kit site:
Build your site with either of these implementations:
Composable Storefront (PWA Kit version 2.7.0 or later) for your whole site with usage of the Retail React app template
Single MRT Environment: Each Business Manager site can only have one MRT environment configured. Multiple MRT environments pointing to the same Business Manager site (n:n assignment) isn’t supported.
Required for all metadata types: Add arch_type: "headless" to all Page Designer metadata files—pages, components, and aspect types (PDP, PLP, Search)—for headless rendering to work correctly. Without this field, Business Manager expects ISML components.
No ISML required: With headless Page Designer (arch_type: "headless"), you only need React components, not ISML templates. This change is a major advantage, simplifying development and freeing you from maintaining parallel ISML and React implementations.
No custom JavaScript in metadata: Pages and components marked as arch_type: "headless" don’t run custom JavaScript. Only the page and component metadata (JSON) is evaluated—custom scripts are ignored. You implement all storefront logic in your PWA Kit React code—an intentional architectural change to separate concerns between the storefront (PWA Kit) and server (B2C Commerce).
You define your components as pure React components with metadata in JSON format, specifying the architecture type, attributes, and regions.
For pages, you define metadata including the route path where the page is accessible.
Make sure that component metadata matches your React component’s props structure.
Architecture Overview
Understanding the Page Designer architecture helps you build better components and integrate them effectively into your PWA Kit site.
Understanding Page Designer Components
Page Designer uses a hierarchical architecture with three main concepts: pages, regions, and components.
Pages
A Page is the top-level container representing a complete web page. It contains:
Page metadata including name, description, and route
One or more Regions that organize content
Architecture type set to "headless" for React-only rendering
A route path that defines where the page is accessible
In PWA Kit, you fetch page data with the usePage() hook from @salesforce/commerce-sdk-react and render it with the <Page> component.
In this example, the route parameter :productId matches the attribute definition id: "productId". When a merchant selects a product in Page Designer, the system uses this attribute to construct the correct URL for preview.
Regions
A Region is a logical content area within a page or component. Regions:
Contain an ordered list of components.
Are nestable within Layout components.
Are identifiable by unique IDs (for example, ‘main’, ‘header’, ‘sidebar’).
Can define maximum component limits and restrictions.
The <Region> component from @salesforce/commerce-sdk-react/components handles rendering regions and their child components.
Components
Components are the building blocks of your page content. There are two types:
Leaf Components (Content Components):
Render actual content (images, text, products, carousels).
Do NOT contain nested regions.
Have "region_definitions": [] in their metadata.
Examples: ContentCard, Hero, ProductCarousel.
Receive their configuration as props directly from Page Designer.
Layout Components (Container Components):
Organize other components with visual layouts (grids, columns, tabs).
Must contain one or more nested Regions to hold child components.
Have populated "region_definitions" in their metadata.
Use the <Region> component to render their nested content.
Examples: Grid, Carousel (when used as a container).
The component registry dynamically loads Page Designer components on-demand using lazy imports. Instead of a simple mapping object, it uses a ComponentRegistry class that enables:
Lazy loading: Components are only loaded when needed, reducing initial bundle size.
Dynamic imports: Each component is registered with an importer function.
Fallback support: Optional loading states during component load.
Data loaders: Optional server/client data fetching functions hoisted to the page.
Registry Initialization Example:
1import{registry}from '@salesforce/commerce-sdk-react'23export function initializeRegistry(){4 // Register components with lazy imports5 registry.registerImporter('pwa.contentCard', ()=> import('./components/content-card'))67 registry.registerImporter('pwa.grid', ()=> import('./components/grid'))89 // Register component with data loader and fallback10 registry.registerImporter('pwa.productCarousel', ()=> import('./components/product-carousel'), {11 loader: 'loader',12 fallback: 'fallback'13})14}
How It Works:
Call initializeRegistry() once during app startup.
When a page renders, the registry checks if the component is loaded.
If not loaded, it calls the importer function (triggers React Suspense).
Subsequent uses of the same component are instant.
Key Benefits:
Only components used on the page are downloaded.
Initial page load is faster.
Components are shared across pages (loaded one time and cached).
Eliminates the manual management of imports in page files.
Building Layout Components
Layout components organize other components using visual layouts. They render nested regions to hold child components.
React Component Structure
Here’s an example of a layout component that shows content in a responsive grid:
1import React from 'react'2import PropTypes from 'prop-types'3import{SimpleGrid}from '@salesforce/retail-react-app/app/components/shared/ui'4import{Region, regionPropType}from '@salesforce/commerce-sdk-react/components'56/**7 * This layout component displays its children in a 2 row x 1 column grid on mobile8 * and a 1 row x 2 column grid on desktop.9 */10export const MobileGrid2r1c = ({regions, component})=>{11 return(12<SimpleGrid columns={{base: 1, sm: 2}} gridGap={4}>13{regions.map((region)=>(14<Region key={region.id} regionId={region.id} component={component} />15))}16</SimpleGrid>17)18}1920MobileGrid2r1c.propTypes = {21 regions: PropTypes.arrayOf(regionPropType).isRequired,22 component: PropTypes.object.isRequired23}2425export default MobileGrid2r1c
Component Metadata
Store the metadata in your cartridge at:
cartridge/experience/components/{group}/{componentId}.json
1{2 "name": "Mobile Grid 2x1",3 "description": "2 row x 1 column grid on mobile, 1 row x 2 column on desktop",4 "group": "odyssey_base",5 "arch_type": "headless",6 "region_definitions": [7{8 "id": "region1",9 "name": "First Region"10},11{12 "id": "region2",13 "name": "Second Region"14}15],16 "attribute_definition_groups": []17}
Key Points for Layout Components
arch_type is “headless”: This tells Business Manager no ISML component is needed.
Receive regions and components prop: Layout components get an array of region objects and the parent component object.
Map over regions: Each nested region is rendered using the <Region> component.
Pass required props: Each <Region> needs:
regionId: The unique identifier for the region
component: The parent component object (the Region finds the region data from component.regions)
key: React key prop for list rendering
Metadata region match: Make sure the region_definitions in your metadata match the regions your component expects.
Building Leaf Components
Leaf components render actual content and do NOT contain nested regions. They’re the “content” pieces that get placed inside layout components.
React Component Structure
Here’s an example of a leaf component that displays an image:
1import React from 'react'2import PropTypes from 'prop-types'3import{Box, Image}from '@salesforce/retail-react-app/app/components/shared/ui'45/**6 * Simple ImageTile component that displays a responsive image.7 * This component can be placed inside any Layout component.8 */9export const ImageTile = ({image})=>{10 return(11<Box className="image-tile">12<figure>13<picture>14<source srcSet={image?.src?.tablet} media="(min-width: 48em)" />15<source srcSet={image?.src?.desktop} media="(min-width: 64em)" />16<Image src={image?.src?.mobile || image?.url} alt={image?.alt} title={image?.alt} />17</picture>18</figure>19</Box>20)21}2223ImageTile.propTypes = {24 image: PropTypes.shape({25 url: PropTypes.string,26 alt: PropTypes.string,27 src: PropTypes.shape({28 mobile: PropTypes.string,29 tablet: PropTypes.string,30 desktop: PropTypes.string31})32})33}3435export default ImageTile
Component Metadata
Store the metadata in your cartridge at:
cartridge/experience/components/{group}/{componentId}.json
The older PWA Kit Page Designer implementation required ISML components. This section describes how to migrate to the new headless metadata approach, which uses React components only.
Understanding the Transformation
The biggest win: You no longer need ISML components. With arch_type: "headless", you only maintain React components.
Aspect
Old PWA Kit Approach (with ISML)
New Approach
Component Definition
ISML + React components required
React components only
Metadata
Component metadata in JSON
Component metadata in JSON with arch_type: "headless"
Maintenance
Two implementations (ISML + React)
Single React implementation
Component Registry and Bundle Impact
Uses a mapping object that you configure manually to register components. All components loaded. No lazy loading.
Uses a dynamic registry to lazy load components. Only the used components are loaded (code splitting). Loading of non-critical components or resources is deferred until they’re needed.
Dependencies
@salesforce/commerce-sdk-react
Requires @salesforce/storefront-next-runtime in PWA Kit v3.17 or later, in addition to @salesforce/commerce-sdk-react.
Region or Component
Basic components
Enhanced Region and Component with design mode
Page Routing
Manual route setup
Automatic via route field in metadata
Server-Side Custom JavaScript
Executed at run time
No server-side JavaScript code. All functionality is implemented in React code.
Preview
Preview in Page Designer shows only ISML pages. Storefront Preview needed for PWA live preview.
Page Designer can preview PWA Kit storefront.
Migration Steps
Step 1: Install Required Dependency
Add the @salesforce/storefront-next-runtime package to your project:
Deploy your updated cartridge with headless metadata to your SFCC instance.
Restart your PWA Kit development server:
1npm start
Verify in Business Manager:
Go to Merchant Tools > Content > Components.
Your components appear with no ISML requirement.
Test page rendering:
Go to your pages (homepage, PDP, PLP).
Components render using the registry.
Test in Page Designer:
Open the Page Designer visual editor.
Edit a page - your PWA Kit site shows in the preview.
Drag components - changes show immediately.
Common Migration Challenges
Challenge: Custom JavaScript no longer runs
Solution: Headless components (arch_type: "headless") do not run custom JavaScript from Page Designer metadata. Migrate all custom logic to your React components. This approach is intentional—it separates storefront logic (PWA Kit React) from content configuration (Business Manager JSON).
Example:
1// OLD - Custom script in Page Designer (no longer executed)2// This JavaScript will be IGNORED for headless components34// NEW - Implement in your React component5export const MyComponent = ({title, showDiscount})=>{6 // All logic in React code7 const displayPrice = showDiscount ? applyDiscount(price) : price8 return<div>{displayPrice}</div>9}
Challenge: Forgetting to set arch_type: "headless"Solution: Always include "arch_type": "headless" in your metadata. Otherwise, Business Manager expects ISML components.
Challenge: Metadata attributes don’t match React props
Solution: Ensure every attribute_definitionid matches a prop name in your React component. Use PropTypes to validate.
Challenge: Component typeId mismatch
Solution: The typeId format is {group}.{componentId}. In your registry, use the same format: 'odyssey_base.myComponent': MyComponent
Challenge: Layout component regions not rendering
Solution: Ensure that your metadata includes region_definitions and your React component maps over the regions prop with <Region> components.
Challenge: Page route not working
Solution: For page metadata, include the route field with the URL path (for example, "route": "/" for homepage).
Challenge: Region not found
Solution: Verify regionId matches the region ID in your page data. For nested regions, pass component not page. Use errorElement prop to handle missing regions gracefully.
Challenge: Visual editing not working
Solution: Ensure PageDesignerProvider wraps your content. Verify targetOrigin matches your Business Manager URL. Check browser console for postMessage errors.
New Features
The updated Page Designer implementation includes several new features for enhanced visual editing support.
Design Metadata
Components now receive designMetadata with information for visual editing:
1interface ComponentDesignMetadata{2 id: string // Component instance ID3 name?: string // Display name4 isFragment: boolean // Is this a fragment?5 isVisible: boolean // Is component visible?6 isLocalized: boolean // Is component localized?7}
Components also receive additional props:
component - The full component data object
regionId - The parent region’s ID
You don’t need to use these props, but they’re available if needed for custom behavior.
Design Mode Detection
Use the usePageDesignerMode hook to conditionally render content based on design mode:
Or use the utility functions for checking outside of React components:
1import{isDesignModeActive, isPreviewModeActive}from '@salesforce/commerce-sdk-react/components'23if(isDesignModeActive()){4 // In design mode5}
Page Component API
The Page component no longer requires a components prop. Components are now resolved via the registry.
Before
After
<Page page={data} components={map} />
<Page page={data} />
Required components prop
Components from registry
Used PageContext internally
No context needed
Updated Region API
The Region component API has changed to support nested regions in layout components:
Before
After
<Region region={regionObj} />
<Region component={comp} regionId="main" />
Received region object directly
Finds region by ID from component
No fallback support
fallbackElement and errorElement props
New Region Props:
1// For page-level regions2<Region page={page} regionId="main" fallbackElement={<Loading />} />34// For nested regions in layout components5<Region component={component} regionId="left" errorElement={<Error />} />
Component Registry API
1import{registry}from '@salesforce/commerce-sdk-react'23// Register with lazy loading4registry.registerImporter('typeId', ()=> import('./component'))56// Register with fallback for loading state7registry.registerImporter(8 'typeId',9()=> import('./component'),10()=> import('./skeleton')11)1213// Get a component14const Component = registry.getComponent('typeId')1516// Preload a component17await registry.preload('typeId')
Component (Internal)
The Component is now internal and uses the registry. You don’t interact with it directly.
Before
After
Used usePageContext() for component map
Uses registry.getComponent()
Wrapped in <div className="component">
No wrapper div
Synchronous rendering
Suspense-based lazy loading
Complete Migration Example
Here’s a consolidated before/after example showing the full transformation: