Developing a dynamic page extension

Dynamic pages let you create pages that load different data based on the URL. Unlike the static pages available in the Site builder area in the Studio, dynamic pages have a dynamic URL. For example, a product details page has a dynamic URL because it contains the product ID and displays the content specific to the matching product ID.

Dynamic page handler

The dynamic-page-handler function in the packages/backend/index.ts file implements the dynamic page handler extension, which handles requests for all the dynamic pages. If an incoming request URL path is resolved, a PossibleDynamicPageResults response is returned. Otherwise, it returns null.

The API hub resolves the incoming requests following these rules:

  • If the path in the request matches a page folder URL, serve the related page folder.
  • If the dynamic page handler resolves the path in the request, serve the related dynamic page.
  • If the path in the request doesn't match either a page folder or a dynamic page, return the 404 - Page not found error.

Develop a dynamic page

To develop a dynamic page, follow these steps:

  1. Create the dynamic page schema.
  2. Upload the dynamic page schema to the Studio
  3. Implement the dynamic page handler.

Create a dynamic page schema

A dynamic page schema specifies the information about a dynamic page and its configuration. The schema determines the configuration options displayed in the Dynamic pages area of the Studio.

A dynamic page schema requires the following fields:

  • dynamicPageType- String - Required. It must comply with the format COMPANY_OR_PROJECT_NAME/DYNAMIC_PAGE_IDENTIFIER.

  • name - String - Required. It specifies how the dynamic page is referenced in the Studio and should be understandable for Studio users. The category and icon are specific schema fields for visualization in the Studio.

  • category - String - Optional. It specifies the clustering of the dynamic page in the Studio.

  • icon - String - Optional. It specifies the name of the icon associated with the dynamic page in Studio.

  • dataSourceType - String - Required. It specifies the name of the main data source for the dynamic page. Every dynamic page of the specified dynamicPageType will contain a data source of this type, which provides the data that the page needs.

  • isMultiple - Boolean - Required. If true, the dynamic page supports creating dynamic page rules. Otherwise, false.

  • pageMatchingPayloadSchema - Array of objects - Optional. It contains a list of schema fields that are displayed in the Studio to set dynamic page rule criteria with the dynamic page rule builder.

    The pageMatchingPayloadSchema only supports the following schema field types: boolean, enum, numeric, string, and dynamic-filter.

    All other field types are displayed as string fields on the Studio.

In the following example, we create a product/product-blog-page dynamic page that uses the product/product-blog data source, supports dynamic page rules, and defines the blog.id and blog.type fields for setting the dynamic page rule criteria in the Studio.

Example dynamic page schema for a product blog with static fieldsjson
{
"dynamicPageType": "product/product-blog-page",
"name": "Product blog",
"category": "Blog",
"icon": "stars",
"dataSourceType": "product/product-blog",
"isMultiple": true,
"pageMatchingPayloadSchema": [
{
"label": "Blog ID",
"field": "blog.id",
"type": "string"
},
{
"label": "Blog type",
"field": "blog.type",
"type": "enum",
"values": [
{
"value": "comparison",
"name": "comparison"
},
{
"value": "trends",
"name": "trends"
}
]
}
]
}

In this example, the blog.type field values might require frequent updates. In such case, you can instead use the dynamic-filter field type and create an action extension that returns the schema fields dynamically.

Example dynamic page schema for a product blog with dynamic filterjson
{
"dynamicPageType": "product/product-blog-page",
"name": "Product blog",
"category": "",
"icon": "stars",
"dataSourceType": "product/product-blog",
"isMultiple": true,
"pageMatchingPayloadSchema": [
{
"label": "Blog dynamic filter fields",
"field": "dynamicFields",
"type": "dynamic-filter",
"dynamicFilterEndpoint": "/action/product-blog/dynamic-fields"
}
]
}

You can store the schema JSON file where you prefer because it is only required in the Studio and not in your Frontend project code.

Upload the dynamic page schema to the Studio

To upload a dynamic page JSON schema, follow these steps:

  1. From the Studio home page or from the left menu, go to Developer > Dynamic pages.

  2. Click Upload schema to browse and upload a JSON file you created or click Create schema: the schema editor opens.

  3. From the schema editor, click Publish to make the dynamic page available in the Dynamic pages area. Otherwise, click Save as draft.

Implement the dynamic page logic

The dynamic page handler extension is a function in the packages/backend/index.ts that handles the logic for dynamic pages. This gives you the flexibility to implement the routing logic that best suits your needs.

For example, in the following code, we use a regular expression (/\/blog\/(.+)\/(\d+)/gm) to match paths starting with /blog containing a slug and blog ID. If the URL matches the expression pattern, the dynamic-page-handler returns the corresponding blog content in a DynamicPageSuccessResult, which contains:

  • dynamicPageType: used to identify the required dynamic page layout.
  • dataSourcePayload: relayed to the page, it contains data from a data source to be displayed on the page in Frontend components.
  • pageMatchingPayload: payload for the dynamic page rule criteria in the Studio.

Executing API calls costs time and affects the performance of your website. Therefore, you should make as few calls as possible and execute them in parallel.

Dynamic page handler for the product blog dynamic pagetypescript
export default {
'dynamic-page-handler': async (
request: Request
): Promise<DynamicPageSuccessResult | null> => {
// Example implementation
const matchesProductBlogPage =
request.query.path.match(/\/blog\/(.+)\/(\d+)/gm);
if (matchesProductBlogPage) {
const [_, _blog, slug, blogID] = matchesProductBlogPage[0].split('/');
// Example implementation
const data = await getBlogContent(blogId);
return {
dynamicPageType: 'product/product-blog',
dataSourcePayload: response.data,
pageMatchingPayload: response.data,
};
}
// Implement the logic of another dynamic page.
return null;
},
// ...
};

Dynamic page rule

For your dynamic pages, you can also set dynamic page rules to display a specific page version if the rule's criteria are met. For the same dynamic page, you can create both a default page version and page versions depending on dynamic page rules.

For example, for a product details page, you can create a default page version with a generic layout and a page version with a special offer layout that appears if the selected product is under 10 Euros. For further information, see Page rule criteria builder.

If, for the same dynamic page, you set different dynamic page rules with the related page versions and an item matches the criteria of multiple rules, it is not possible to determine which page version displays on your website.

The pageMatchingPayload structure must conform with the fields in the pageMatchingPayloadSchema for the Studio to display the configurable options correctly.

Redirect from a dynamic page

Apart from the DynamicPageSuccessResult, you can also return a DynamicPageRedirectResult for scenarios where the current page has been moved to a new URL or the page doesn't exist anymore.

In the following example, we update the product/product-blog dynamic page handler to redirect to the new page if the blog slug has changed.

Example implementation of a redirect responsetypescript
export default {
'dynamic-page-handler': async (
request: Request
): Promise<DynamicPageSuccessResult | DynamicPageRedirectResult | null> => {
// example implementation
const matchesProductBlogPage =
request.query.path.match(/\/blog\/(.+)\/(\d+)/gm);
if (matchesProductBlogPage) {
const [_, _blog, slug, blogID] = matchesProductBlogPage[0].split('/');
// example implementation
const data = await getBlogContent(blogId);
if (data.slug !== slug) {
// if the slug has changed, redirect to the new URL
return {
redirectLocation: getProductBlogURL(blogId),
statusCode: 301,
};
} else {
return {
dynamicPageType: 'product/product-blog',
dataSourcePayload: response.data,
pageMatchingPayload: response.data,
};
}
}
// implement other dynamic pages logic
return null;
},
};