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:
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 formatCOMPANY_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. Iftrue
, 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.
{"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.
{"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:
From the Studio home page or from the left menu, go to Developer > Dynamic pages.
Click Upload schema to browse and upload a JSON file you created or click Create schema: the schema editor opens.
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.
export default {'dynamic-page-handler': async (request: Request): Promise<DynamicPageSuccessResult | null> => {// Example implementationconst matchesProductBlogPage =request.query.path.match(/\/blog\/(.+)\/(\d+)/gm);if (matchesProductBlogPage) {const [_, _blog, slug, blogID] = matchesProductBlogPage[0].split('/');// Example implementationconst 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.
Add links to dynamic pages
commercetools Frontend doesn't know the URL structure you implement in the dynamic page handler extension. Hence, it is not possible to generate links for dynamic pages automatically. You must handle the linking for dynamic pages either during client-side routing or by generating URLs for linking on the backend.
For example, to use backend-generated URLs for the product/product-blog
dynamic page, implement a function like the following and return the generated URL as a _url
parameter from all the blog-related data sources, and then use the _url
in your Frontend components to link to the blog pages.
const getProductBlogURL = async (blogID: string): Promise<string> => {const blogData = await getBlogData(blogID);return `/blog/${blogData.slug}/${blogData.id}`;};
In case internationalized URLs are required, the convention is to use the property _urls
which holds a hash-map mapping locale strings to URL paths. For example: { _urls: { 'en_US': '/blog/this-is-the-best-product/23', 'de_DE': '/blog/das-ist-das-beste-produkt/23' }
.
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.
export default {'dynamic-page-handler': async (request: Request): Promise<DynamicPageSuccessResult | DynamicPageRedirectResult | null> => {// example implementationconst matchesProductBlogPage =request.query.path.match(/\/blog\/(.+)\/(\d+)/gm);if (matchesProductBlogPage) {const [_, _blog, slug, blogID] = matchesProductBlogPage[0].split('/');// example implementationconst data = await getBlogContent(blogId);if (data.slug !== slug) {// if the slug has changed, redirect to the new URLreturn {redirectLocation: getProductBlogURL(blogId),statusCode: 301,};} else {return {dynamicPageType: 'product/product-blog',dataSourcePayload: response.data,pageMatchingPayload: response.data,};}}// implement other dynamic pages logicreturn null;},};