---
schema_version: "1"
id: 70350ffe822f6b8401e104b586fdd7c01f6f1e52ddd0b0931668434bfd790a2e
title: Integrate Page Designer with PWA Kit
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/commerce-cloud/pwa-kit-managed-runtime/guides/page-designer.md
canonical_url: https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/page-designer.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 31019380429ac6cab9da2960809f65c055b30072cc6bce26ff527b62991af179
products:
  - Composable Storefront
  - Salesforce APIs
---
# Integrate Page Designer with PWA Kit

With Page Designer, you can create reusable page types and component types for your [Progressive Web App (PWA) Kit](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/getting-started.html) 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](https://github.com/SalesforceCommerceCloud/pwa-kit/blob/develop/packages/template-retail-react-app/README.md)
  - A [phased headless rollout](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/phased-headless-rollouts.html) ([Storefront Reference Architecture - SFRA](https://developer.salesforce.com/docs/commerce/sfra/guide/sfra-overview.html) and PWA Kit version 2.7.0 or later with usage of the Retail React app template)
- Create or update a SLAS public or private client. Follow the steps in [Create a SLAS Client](https://developer.salesforce.com/docs/commerce/commerce-api/guide/authorization-for-shopper-apis.html#create-a-slas-client) or use the [registerClient API endpoint](https://developer.salesforce.com/docs/commerce/commerce-api/references/auth-admin?meta=registerClient).

  - In the **Scopes** field, include this scope: `sfcc.shopper-experience`.

    A SLAS private client offers several advantages over a public client. See [Use a SLAS Private Client](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/use-a-slas-private-client.html).

- [Get a SLAS admin token](https://developer.salesforce.com/docs/commerce/commerce-api/guide/authorization-for-shopper-apis.html#get-a-slas-admin-token).
- **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.
- Create your Page Designer pages in the Page Designer visual editor. See [Create a Page Using the Page Designer Visual Editor](https://help.salesforce.com/s/articleView?id=cc.b2c_create_page.htm&language=en_US&type=5).

## Considerations

- **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.

**Page Metadata Example**:

```json
{
  "name": "Home Page",
  "description": "Main landing page with hero carousel and featured products",
  "arch_type": "headless",
  "route": "/",
  "region_definitions": [
    {
      "id": "headerbanner",
      "name": "Header Banner Region",
      "max_components": 3
    },
    {
      "id": "main",
      "name": "Main Content Region",
      "max_components": 5
    }
  ]
}
```

**Key Fields**:

- `arch_type: "headless"` - Enables React-only rendering without ISML (required for headless pages).
  - Set to `"headless"` for PWA Kit React rendering.
  - Set to `"controller"` or omit for traditional ISML rendering.
  - Supports **hybrid storefronts** with both ISML and headless pages, enabling incremental migration.
- `route` - **Critical**: URL path where the page is accessible. Page Designer uses this field to render your storefront in edit mode.
  - Static routes: `"/"` for homepage, `"/about"` for about page
  - Dynamic routes with parameters: `"/product/:productId"`, `"/category/:categoryId"`
  - **Important**: Make sure route parameters (`:productId`, `:categoryId`) match the attribute IDs defined in the page's aspect type
  - Page Designer loads your PWA Kit storefront at this route when editing the page.
- `region_definitions` - Defines the content areas for this page.

**How Page Designer Uses Routes**:

When you edit a headless page in the Page Designer visual editor, Page Designer:

1. Reads the `route` field from the page metadata.
2. Loads your PWA Kit storefront URL (configured as the connected MRT environment in Business Manager).
3. Goes to the route (for example, `/`, `/product/123`, `/category/mens`).
4. Renders your React page in an iframe for live editing.
5. Uses `arch_type: "headless"` to identify which components are headless.

This means:

- **No special page-viewer route needed** - Pages render at their actual routes.
- **Non-breaking change** - ISML pages without `arch_type: "headless"` continue to work.
- **Hybrid storefronts supported** - Mix ISML and headless pages during migration.
- **Accurate preview** - See exactly what customers see on your PWA Kit site.

**Routes in Page Types**:

Different page types require different route configurations:

| Page Type                           | Route Example             | Description                                                                       |
| ----------------------------------- | ------------------------- | --------------------------------------------------------------------------------- |
| Homepage                            | `"/"`                     | Static route for the main landing page                                            |
| Content Page                        | `"/about"`, `"/contact"`  | Static routes for informational pages                                             |
| Product Detail Page (PDP)           | `"/product/:productId"`   | Dynamic route where `:productId` matches the attribute ID in the PDP aspect type  |
| Category/Product Listing Page (PLP) | `"/category/:categoryId"` | Dynamic route where `:categoryId` matches the attribute ID in the PLP aspect type |
| Search Results Page                 | `"/search"`               | Static route for search results                                                   |

**Dynamic Route Parameters**:

For pages with dynamic routes (PDP, PLP), the route parameter name must match the attribute ID defined in the page's aspect type metadata:

```json
{
  "name": "Product Detail Page",
  "arch_type": "headless",
  "route": "/product/:productId",
  "attribute_definitions": [
    {
      "id": "productId",
      "name": "Product ID",
      "type": "product",
      "required": true
    }
  ]
}
```

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).

**Component Metadata Example (Leaf Component)**:

```json
{
  "name": "Content Card",
  "description": "Flexible card component with optional image, title, description, and CTA",
  "group": "odyssey_base",
  "arch_type": "headless",
  "region_definitions": [],
  "attribute_definition_groups": [
    {
      "id": "contentCard",
      "name": "Content Card",
      "attribute_definitions": [
        {
          "id": "title",
          "name": "Title",
          "type": "string",
          "required": false
        },
        {
          "id": "imageUrl",
          "name": "Image URL",
          "type": "image",
          "required": false
        },
        {
          "id": "buttonText",
          "name": "Button Text",
          "type": "string",
          "required": false
        }
      ]
    }
  ]
}
```

**Component Metadata Example (Layout Component)**:

```json
{
  "name": "Grid",
  "description": "A flexible grid layout component for organizing content in columns",
  "group": "odyssey_base",
  "arch_type": "headless",
  "region_definitions": [
    {
      "id": "main",
      "name": "Main"
    }
  ],
  "attribute_definition_groups": [
    {
      "id": "grid",
      "name": "Grid",
      "attribute_definitions": [
        {
          "id": "columns",
          "name": "Columns",
          "type": "enum",
          "required": false,
          "values": ["1", "2", "3", "4", "5", "6"],
          "default_value": "1"
        }
      ]
    }
  ]
}
```

**Key Fields**:

- `arch_type: "headless"` - Required for React-only components (no ISML).
- `group` - Organizes components in Page Designer.
- `region_definitions` - Empty array for leaf components, populated for layout components.
- `attribute_definitions` - Props that will be passed to your React component.

### Component Lifecycle and Data Flow

Understanding how components are discovered, loaded, and rendered helps you debug issues and optimize performance:

1. **Page Load**: The `<Page>` component receives page data from the Shopper Experience API via `usePage()` hook.
2. **Region Rendering**: The `<Page>` component maps over its regions and renders each using the `<Region>` component.
3. **Component Discovery**: For each component in a region, the `<Component>` wrapper:
   - Looks up the React component in the registry using the component's `typeId`.
   - Lazy-loads the component if needed (triggers React Suspense).
   - Extracts component data from the Page Designer configuration.
4. **Component Rendering**: The resolved React component receives:
   - Props matching the `attribute_definitions` from metadata (title, image, etc.)
   - `regions`: Nested regions (for layout components only)
   - `page`: Reference to the full page object
   - `regionId`: ID of the parent region
   - `designMetadata`: Design mode metadata (for Page Designer editor integration)

### Component Registry

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**:

```jsx
import {registry} from '@salesforce/commerce-sdk-react'

export function initializeRegistry() {
  // Register components with lazy imports
  registry.registerImporter('pwa.contentCard', () => import('./components/content-card'))

  registry.registerImporter('pwa.grid', () => import('./components/grid'))

  // Register component with data loader and fallback
  registry.registerImporter('pwa.productCarousel', () => import('./components/product-carousel'), {
    loader: 'loader',
    fallback: 'fallback'
  })
}
```

**How It Works**:

1. Call `initializeRegistry()` once during app startup.
2. When a page renders, the registry checks if the component is loaded.
3. If not loaded, it calls the importer function (triggers React Suspense).
4. 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:

```jsx
import React from 'react'
import PropTypes from 'prop-types'
import {SimpleGrid} from '@salesforce/retail-react-app/app/components/shared/ui'
import {Region, regionPropType} from '@salesforce/commerce-sdk-react/components'

/**
 * This layout component displays its children in a 2 row x 1 column grid on mobile
 * and a 1 row x 2 column grid on desktop.
 */
export const MobileGrid2r1c = ({regions, component}) => {
  return (
    <SimpleGrid columns={{base: 1, sm: 2}} gridGap={4}>
      {regions.map((region) => (
        <Region key={region.id} regionId={region.id} component={component} />
      ))}
    </SimpleGrid>
  )
}

MobileGrid2r1c.propTypes = {
  regions: PropTypes.arrayOf(regionPropType).isRequired,
  component: PropTypes.object.isRequired
}

export default MobileGrid2r1c
```

### Component Metadata

Store the metadata in your cartridge at:
`cartridge/experience/components/{group}/{componentId}.json`

```json
{
  "name": "Mobile Grid 2x1",
  "description": "2 row x 1 column grid on mobile, 1 row x 2 column on desktop",
  "group": "odyssey_base",
  "arch_type": "headless",
  "region_definitions": [
    {
      "id": "region1",
      "name": "First Region"
    },
    {
      "id": "region2",
      "name": "Second Region"
    }
  ],
  "attribute_definition_groups": []
}
```

### 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:

```jsx
import React from 'react'
import PropTypes from 'prop-types'
import {Box, Image} from '@salesforce/retail-react-app/app/components/shared/ui'

/**
 * Simple ImageTile component that displays a responsive image.
 * This component can be placed inside any Layout component.
 */
export const ImageTile = ({image}) => {
  return (
    <Box className="image-tile">
      <figure>
        <picture>
          <source srcSet={image?.src?.tablet} media="(min-width: 48em)" />
          <source srcSet={image?.src?.desktop} media="(min-width: 64em)" />
          <Image src={image?.src?.mobile || image?.url} alt={image?.alt} title={image?.alt} />
        </picture>
      </figure>
    </Box>
  )
}

ImageTile.propTypes = {
  image: PropTypes.shape({
    url: PropTypes.string,
    alt: PropTypes.string,
    src: PropTypes.shape({
      mobile: PropTypes.string,
      tablet: PropTypes.string,
      desktop: PropTypes.string
    })
  })
}

export default ImageTile
```

### Component Metadata

Store the metadata in your cartridge at:
`cartridge/experience/components/{group}/{componentId}.json`

```json
{
  "name": "Image Tile",
  "description": "Simple image display component with responsive sources",
  "group": "odyssey_base",
  "arch_type": "headless",
  "region_definitions": [],
  "attribute_definition_groups": [
    {
      "id": "imageTile",
      "name": "Image Tile",
      "attribute_definitions": [
        {
          "id": "image",
          "name": "Image",
          "type": "image",
          "required": true,
          "description": "The image to display"
        }
      ]
    }
  ]
}
```

### Key Points for Leaf Components

- **arch_type is "headless"**: No ISML component needed.
- **region_definitions is empty array**: Leaf components don't have nested regions.
- **Attributes match props**: Each `attribute_definition` becomes a prop passed to your React component.
- **Define PropTypes**: Always define PropTypes to match your metadata attributes.
- **Supported attribute types**: string, text, markup, integer, boolean, product, category, file, page, image, url, enum, custom

## Migrating from PWA Kit with ISML to Headless

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:

```bash
npm install @salesforce/storefront-next-runtime
```

Update your `package.json`:

```json
{
  "dependencies": {
    "@salesforce/storefront-next-runtime": "^VERSION",
    "@salesforce/commerce-sdk-react": "^VERSION"
  }
}
```

#### Step 2: Update Component Metadata to Headless

For **each existing component** in your cartridge, update the JSON metadata file to include `arch_type: "headless"`:

**Before** (`cartridge/experience/components/commerce_assets/photoTile.json`):

```json
{
    "name": "Photo Tile",
    "description": "Image display component",
    "group": "commerce_assets",
    "attribute_definition_groups": [...]
}
```

**After**:

```json
{
    "name": "Photo Tile",
    "description": "Image display component",
    "group": "commerce_assets",
    "arch_type": "headless",
    "region_definitions": [],
    "attribute_definition_groups": [...]
}
```

**Required changes for each component**:

- Add `"arch_type": "headless"`.
- Add `"region_definitions": []` for leaf components, or populate with regions for layout components.

#### Step 3: Update Page Metadata with Routes

For **each existing page** in your cartridge, update the JSON metadata to include `arch_type` and `route`:

**Before** (`cartridge/experience/pages/homePage.json`):

```json
{
    "name": "Home Page",
    "description": "Main landing page",
    "region_definitions": [...]
}
```

**After**:

```json
{
    "name": "Home Page",
    "description": "Main landing page",
    "arch_type": "headless",
    "route": "/",
    "region_definitions": [...]
}
```

**Route examples**:

- Homepage: `"route": "/"`
- Static pages: `"route": "/about"`, `"route": "/contact"`
- Product pages: `"route": "/product/:productId"` (`:productId` matches the attribute ID in your aspect type)
- Category pages: `"route": "/category/:categoryId"` (`:categoryId` matches the attribute ID in your aspect type)

#### Step 4: Update Aspect Type Metadata

For **each aspect type** (PDP, PLP, Search), add `arch_type`:

**Before** (`cartridge/experience/aspects/pdp.json`):

```json
{
    "name": "Product detail page",
    "description": "A product detail page",
    "attribute_definitions": [...],
    "supported_object_types": ["product"]
}
```

**After**:

```json
{
    "name": "Product detail page",
    "description": "A product detail page",
    "arch_type": "headless",
    "attribute_definitions": [...],
    "supported_object_types": ["product"]
}
```

#### Step 5: Migrate from Manual Mapping to Registry

Replace the old manual component mapping with the new registry approach.

**Remove** the old mapping pattern from your page-viewer:

```jsx
// OLD - Remove this
const PAGEDESIGNER_TO_COMPONENT = {
    'commerce_assets.photoTile': ImageTile,
    'commerce_layouts.carousel': Carousel,
    // ...
}

<Page page={page} components={PAGEDESIGNER_TO_COMPONENT} />
```

**Create** a registry file at `app/page-designer/registry.js`:

```jsx
import {registry} from '@salesforce/commerce-sdk-react'

export function initializeRegistry() {
  // Commerce Assets
  registry.registerImporter('commerce_assets.imageTile', () => import('./assets/image-tile'))
  registry.registerImporter('commerce_assets.imageAndText', () =>
    import('./assets/image-with-text')
  )
  registry.registerImporter('commerce_assets.productTile', () => import('./assets/product-tile'))

  // Commerce Layouts
  registry.registerImporter('commerce_layouts.carousel', () => import('./layouts/carousel'))
  registry.registerImporter('commerce_layouts.mobileGrid1r1c', () =>
    import('./layouts/mobileGrid1r1c')
  )
  registry.registerImporter('commerce_layouts.mobileGrid2r1c', () =>
    import('./layouts/mobileGrid2r1c')
  )
  registry.registerImporter('commerce_layouts.mobileGrid2r2c', () =>
    import('./layouts/mobileGrid2r2c')
  )
  registry.registerImporter('commerce_layouts.mobileGrid2r3c', () =>
    import('./layouts/mobileGrid2r3c')
  )
  registry.registerImporter('commerce_layouts.mobileGrid3r1c', () =>
    import('./layouts/mobileGrid3r1c')
  )
  registry.registerImporter('commerce_layouts.mobileGrid3r2c', () =>
    import('./layouts/mobileGrid3r2c')
  )
}
```

**Update** your page-viewer to use the registry:

```jsx
// app/pages/page-viewer/index.jsx
import React from 'react'
import {useParams} from 'react-router-dom'
import {Box} from '@salesforce/retail-react-app/app/components/shared/ui'
import {usePage} from '@salesforce/commerce-sdk-react'
import {Page} from '@salesforce/commerce-sdk-react/components'
import {HTTPError, HTTPNotFound} from '@salesforce/pwa-kit-react-sdk/ssr/universal/errors'

const PageViewer = () => {
  const {pageId} = useParams()
  const {data: page, error} = usePage({parameters: {pageId}})

  if (error) {
    let ErrorClass = error.response?.status === 404 ? HTTPNotFound : HTTPError
    throw new ErrorClass(error.response?.statusText)
  }

  return (
    <Box layerStyle={'page'}>
      {/* No components prop needed - registry handles it */}
      <Page page={page} />
    </Box>
  )
}

export default PageViewer
```

#### Step 6: Set Up PageDesignerProvider in App Component

Update your main `_app/index.jsx` to initialize the registry and wrap with PageDesignerProvider:

```jsx
// app/components/_app/index.jsx
import {PageDesignerProvider} from '@salesforce/commerce-sdk-react/components'
import {useUsid} from '@salesforce/commerce-sdk-react'
import {initializeRegistry} from '@salesforce/retail-react-app/app/page-designer/registry'

// Initialize registry synchronously at module load time so components are available during SSR
initializeRegistry()

const App = (props) => {
  const {children} = props
  const {usid} = useUsid()

  // Detect Page Designer mode from URL
  const pageDesignerMode = useMemo(() => {
    const queryParams = location?.search || ''
    if (queryParams.includes('mode=EDIT')) return 'EDIT'
    if (queryParams.includes('mode=PREVIEW')) return 'PREVIEW'
    return undefined
  }, [])

  return (
    <Box className="sf-app">
      {/* Other providers... */}
      <PageDesignerProvider
        clientId="pwa-kit-client"
        targetOrigin="*"
        usid={usid}
        mode={pageDesignerMode}
      >
        {children}
      </PageDesignerProvider>
    </Box>
  )
}
```

**Key setup points**:

1. **Initialize registry at module level** (outside the component) so components are available during SSR.
2. **Pass `usid`** from `useUsid()` hook to the provider for session tracking.
3. **Detect mode from URL** - Page Designer passes `mode=EDIT` or `mode=PREVIEW` as query parameters.
4. **Always wrap with PageDesignerProvider** - it handles both design mode and normal rendering.

#### Step 7: Create PageDesignerInit Component

Create `app/components/page-designer-init/index.jsx` to handle design mode behaviors:

```jsx
import React, {useEffect} from 'react'
import {Prompt} from 'react-router-dom'
import {usePageDesignerMode} from '@salesforce/commerce-sdk-react/components'
import {useGlobalAnchorBlock} from '@salesforce/retail-react-app/app/hooks/use-global-anchor-block'

export function PageDesignerInit() {
  const {isDesignMode} = usePageDesignerMode()

  useGlobalAnchorBlock(isDesignMode)

  useEffect(() => {
    if (isDesignMode) {
      void import('@salesforce/storefront-next-runtime/design/styles.css')
    }
  }, [isDesignMode])

  return <Prompt when={isDesignMode} message={() => false} />
}

export default PageDesignerInit
```

#### Step 8: Create useGlobalAnchorBlock Hook

Create `app/hooks/use-global-anchor-block.js` to prevent link navigation in design mode:

```jsx
import {useEffect} from 'react'

export function useGlobalAnchorBlock(enabled = true) {
  useEffect(() => {
    if (typeof window === 'undefined' || !enabled) return

    function preventAnchorClicks(event) {
      const anchor = event.target.closest('a')
      // Allow links with data-pd-allow-link attribute
      if (anchor && !anchor.hasAttribute('data-pd-allow-link')) {
        event.preventDefault()
      }
    }

    document.addEventListener('click', preventAnchorClicks)
    return () => document.removeEventListener('click', preventAnchorClicks)
  }, [enabled])
}
```

#### Step 9: Add PageDesignerInit to App

In `app/components/_app/index.jsx`, render `PageDesignerInit` inside the provider:

```jsx
import {PageDesignerInit} from '@salesforce/retail-react-app/app/components/page-designer-init'

// Inside your App component's return:
;<PageDesignerProvider
  clientId="pwa-kit-client"
  targetOrigin="*"
  usid={usid}
  mode={pageDesignerMode}
>
  <PageDesignerInit />
  {children}
</PageDesignerProvider>
```

#### Step 10: Deploy and Test

1. Deploy your updated cartridge with headless metadata to your SFCC instance.
2. Restart your PWA Kit development server:
   ```bash
   npm start
   ```
3. Verify in Business Manager:
   - Go to **Merchant Tools > Content > Components**.
   - Your components appear with no ISML requirement.
4. Test page rendering:
   - Go to your pages (homepage, PDP, PLP).
   - Components render using the registry.
5. 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**:

```javascript
// OLD - Custom script in Page Designer (no longer executed)
// This JavaScript will be IGNORED for headless components

// NEW - Implement in your React component
export const MyComponent = ({title, showDiscount}) => {
  // All logic in React code
  const displayPrice = showDiscount ? applyDiscount(price) : price
  return <div>{displayPrice}</div>
}
```

**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_definition` `id` 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:

```typescript
interface ComponentDesignMetadata {
  id: string // Component instance ID
  name?: string // Display name
  isFragment: boolean // Is this a fragment?
  isVisible: boolean // Is component visible?
  isLocalized: boolean // Is component localized?
}
```

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:

```jsx
import {usePageDesignerMode} from '@salesforce/commerce-sdk-react/components'

function MyComponent() {
  const {isDesignMode, isPreviewMode} = usePageDesignerMode()

  return (
    <div>
      {isDesignMode && <span>Editing mode</span>}
      {/* ... */}
    </div>
  )
}
```

Or use the utility functions for checking outside of React components:

```jsx
import {isDesignModeActive, isPreviewModeActive} from '@salesforce/commerce-sdk-react/components'

if (isDesignModeActive()) {
  // In design mode
}
```

### 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:**

```jsx
// For page-level regions
<Region page={page} regionId="main" fallbackElement={<Loading />} />

// For nested regions in layout components
<Region component={component} regionId="left" errorElement={<Error />} />
```

### Component Registry API

```javascript
import {registry} from '@salesforce/commerce-sdk-react'

// Register with lazy loading
registry.registerImporter('typeId', () => import('./component'))

// Register with fallback for loading state
registry.registerImporter(
  'typeId',
  () => import('./component'),
  () => import('./skeleton')
)

// Get a component
const Component = registry.getComponent('typeId')

// Preload a component
await 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:

**Before (Old API):**

```jsx
// page-viewer.jsx
import {Page} from '@salesforce/commerce-sdk-react/components'
import ImageTile from './components/image-tile'
import Banner from './components/banner'
import TwoColumn from './components/two-column'

const components = {
  'commerce_assets.imageTile': ImageTile,
  'commerce_assets.banner': Banner,
  'commerce_layouts.twoColumn': TwoColumn
}

function PageViewer({pageData}) {
  return <Page page={pageData} components={components} />
}

// two-column.jsx
import {Region} from '@salesforce/commerce-sdk-react/components'

function TwoColumn({regions}) {
  return (
    <div>
      <Region region={regions.left} />
      <Region region={regions.right} />
    </div>
  )
}
```

**After (New API):**

```jsx
// registry.js
import {registry} from '@salesforce/commerce-sdk-react'

export function initializeRegistry() {
  registry.registerImporter('commerce_assets.imageTile', () => import('./assets/image-tile'))
  registry.registerImporter('commerce_assets.banner', () => import('./assets/banner'))
  registry.registerImporter('commerce_layouts.twoColumn', () => import('./layouts/two-column'))
}

// _app/index.jsx
import {initializeRegistry} from './page-designer/registry'
initializeRegistry() // At module level, not in useEffect

// page-viewer.jsx
import {Page} from '@salesforce/commerce-sdk-react/components'

function PageViewer({pageData}) {
  return <Page page={pageData} />
}

// two-column.jsx
import {Region} from '@salesforce/commerce-sdk-react/components'

function TwoColumn({component}) {
  return (
    <div>
      <Region component={component} regionId="left" />
      <Region component={component} regionId="right" />
    </div>
  )
}
```

## Configure Page Designer

Follow these steps to set up Page Designer in a new PWA Kit project.

### 1. Install Dependencies

```bash
npm install @salesforce/storefront-next-runtime
```

### 2. Create Component Registry

Create `app/page-designer/registry.js` to register your components with lazy loading:

```jsx
import {registry} from '@salesforce/commerce-sdk-react'

export function initializeRegistry() {
  // Register layout components
  registry.registerImporter('commerce_layouts.carousel', () => import('./layouts/carousel'))
  registry.registerImporter('commerce_layouts.mobileGrid2r1c', () =>
    import('./layouts/mobileGrid2r1c')
  )

  // Register asset components
  registry.registerImporter('commerce_assets.photoTile', () => import('./assets/image-tile'))
  registry.registerImporter('commerce_assets.imageAndText', () =>
    import('./assets/image-with-text')
  )

  // Add all your Page Designer components here
}
```

### 3. Set Up App Integration

Update `app/components/_app/index.jsx` to initialize Page Designer:

```jsx
import {PageDesignerProvider} from '@salesforce/commerce-sdk-react/components'
import {useUsid} from '@salesforce/commerce-sdk-react'
import {initializeRegistry} from '@salesforce/retail-react-app/app/page-designer/registry'
import {PageDesignerInit} from '@salesforce/retail-react-app/app/components/page-designer-init'

// Initialize registry synchronously at module load time so components are available during SSR
initializeRegistry()

const App = (props) => {
  const {children} = props
  const {usid} = useUsid()

  // Detect Page Designer mode from URL
  const pageDesignerMode = useMemo(() => {
    const queryParams = location?.search || ''
    if (queryParams.includes('mode=EDIT')) return 'EDIT'
    if (queryParams.includes('mode=PREVIEW')) return 'PREVIEW'
    return undefined
  }, [])

  return (
    <Box className="sf-app">
      <PageDesignerProvider
        clientId="pwa-kit-client"
        targetOrigin="*"
        usid={usid}
        mode={pageDesignerMode}
      >
        <PageDesignerInit />
        {children}
      </PageDesignerProvider>
    </Box>
  )
}
```

### 4. Create PageDesignerInit Component

Create `app/components/page-designer-init/index.jsx`:

```jsx
import React, {useEffect} from 'react'
import {Prompt} from 'react-router-dom'
import {usePageDesignerMode} from '@salesforce/commerce-sdk-react/components'
import {useGlobalAnchorBlock} from '@salesforce/retail-react-app/app/hooks/use-global-anchor-block'

export function PageDesignerInit() {
  const {isDesignMode} = usePageDesignerMode()

  // Block anchor navigation when in design mode
  useGlobalAnchorBlock(isDesignMode)

  // Load Page Designer styles only in design mode
  useEffect(() => {
    if (isDesignMode) {
      void import('@salesforce/storefront-next-runtime/design/styles.css')
    }
  }, [isDesignMode])

  // Block React Router navigation in design mode
  return <Prompt when={isDesignMode} message={() => false} />
}

export default PageDesignerInit
```

### 5. Create useGlobalAnchorBlock Hook

Create `app/hooks/use-global-anchor-block.js`:

```jsx
import {useEffect} from 'react'

export function useGlobalAnchorBlock(enabled = true) {
  useEffect(() => {
    if (typeof window === 'undefined' || !enabled) return

    function preventAnchorClicks(event) {
      const anchor = event.target.closest('a')
      // Allow links with data-pd-allow-link attribute
      if (anchor && !anchor.hasAttribute('data-pd-allow-link')) {
        event.preventDefault()
      }
    }

    document.addEventListener('click', preventAnchorClicks)
    return () => document.removeEventListener('click', preventAnchorClicks)
  }, [enabled])
}
```

### 6. How It Works

With this setup:

1. **Your existing routes work automatically** - No special page-viewer route needed
2. **Page Designer loads your actual routes** - When you edit a page with `"route": "/"`, Page Designer loads your homepage at `/`
3. **Live editing** - Changes in Page Designer's visual editor appear instantly in your PWA Kit storefront
4. **Hybrid support** - Mix headless and ISML pages during migration

**Example**: If you create a homepage with:

```json
{
    "name": "Home Page",
    "arch_type": "headless",
    "route": "/",
    "region_definitions": [...]
}
```

Page Designer:

- Loads your PWA Kit site's homepage (`/`).
- Renders it in an iframe for editing.
- Applies changes in real-time.

## See Also

- _GitHub:_ [Page Designer Repo for PWA Kit](https://github.com/SalesforceCommerceCloud/pwa-kit/tree/develop/packages/template-retail-react-app/app/page-designer)
- _Salesforce Help:_ [Page Designer Pages](https://help.salesforce.com/s/articleView?id=cc.b2c_creating_pd_pages.htm&type=5)
