Page Caching

Caching for Page Designer pages is similar to other pages, with some nuances.

Controller Pagecache Lifecycle

The rendering or serialization of a page starts with a top-level request from the web adapter that invokes a custom controller, such as Page-Show, on the application server. For example, the Page.js controller from the SFRA Page Designer reference implementation uses the following snippet to check if the page is visible. Only pages with no visibility rules are pagecached.

server.get('Show', cache.applyDefaultCache, consentTracking.consent, function (_req_, res, next) {
   ...
   var page = PageMgr.getPage(req.querystring.cid); 
   
   if (page != null && page.isVisible()) {
      if (page.hasVisibilityRules()) {
         res.cachePeriod = 0; // pages with visibility rules should not be cached
         res.cachePeriodUnit = 'hours'; 
      }

      res.page(page.ID, {}); // invokes PageMgr.renderPage()
   } else {
      ...
   }
    
   next();
}, pageMetaData.computedPageMetaData);

Deciding if you want to apply pagecaching in your controller, and how to do so, is effectively a case by case decision. Donโ€™t create pagecache conditions in your code that are subject to the request context, thus not pagecacheable in a global manner.

Page Rendering Using Nested Remote Includes to Handle Visibility Rules

Parallel to the first pagecache lifecycle constructed around the controller, thereโ€™s a second pagecache lifecycle constructed around the actual contents of the page that is initiated by two nested remote includes.

When PageMgr.renderPage() renders or PageMgr.serializePage() serializes a page:

  • The first-level remote include is the system controller __SYSTEM__Page-Include. This remote include determines a visibility fingerprint of the page and its components, which are visible for the current request context (based on schedules, customer groups, or other visibility settings). Due to this context evaluation, this remote include is never going to be pagecached - or in other words, any page rendering or serialization only hits this remote include one time. It then passes the visibility fingerprint to the second-level remote include.
  • The second-level remote include is the system controller __SYSTEM__Page-Render (rendering) or __SYSTEM__Page-Serialize (serialization), which is pagecachable based on the attached visibility fingerprint. This remote include invokes the custom scripts of the render function to render the page, or the serialize function to serialize the page. The response created is configured with pagecache settings through the previously mentioned scripts that are invoked for the page and all its processed visible components.
Each variation of the page showing a different set of visible components results in a separate pagecache entry because the visibility fingerprint calculation yields a different result.
Note: The more a page varies (in terms of different components shown for different request contexts) the more pagecache fragmentation for this page occurs.

If you check caching for the first-level remote include, __SYSTEM__Page-Include, all invocations appear uncached. The second-level remote include, __SYSTEM__Page-Render or __SYSTEM__Page-Serialize, maintains caching statistics for Page Designer pages but those caching statistics track caching for all Page Designer pages. If you have several Page Designer pages, such as a home page and a Product Detail page, you can't track caching separately per page.

Page and Component Pagecache Lifecycle

The pagecache lifecycle of the page contents is induced through a separate request. This isnโ€™t tied to the first pagecache lifecycle of the top-level request or controller (for example, Page-Show). If you configure pagecaching for the page itself, or any component within that page, the setting is applied for the response of the __SYSTEM__Page-Render and __SYSTEM__Page-Serialize request. Thus this setting affects the pagecaching for the whole page, and anything within it, but doesnโ€™t affect anything outside of it like the controller that initiated the rendering.

If different pagecache settings are supplied for different components of the page, the response takes on the shortest of the pagecache settings. For example, if you have a page where:

  • Component A has a 1-minute relative pagecaching.
  • Component B has a 1-hour relative pagecaching.
  • Component C has a 1-day relative pagecaching.

The final calculated relative pagecaching is the minimum of these three values (1 minute).

Letโ€™s look at an excerpt of the component type implementation that corresponds to component B to see how it instruments the response for a 1-hour pagecaching. Assuming this component presents personalized information (for example, based on a combination of pricebook, promotion, sorting rule, and A/B test segments), then you must account for that by instrumenting the response with the corresponding vary-by. For more information, refer to the B2C Commerce Script documentation in the Commerce Cloud Infocenter.

module.exports.render = function(context) {
   // do your business logic
   ...
    
   // instruct 1 hour personalized relative pagecache
   var expiry = new Date();
   expiry.setHours(expiry.getHours() + 1);
   response.setExpires(expiry);
   response.setVaryBy('price_promotion');

   // create markup
   return ...;
}

If you use dw.util.Template.render() to produce your markup, as SFRA does, then you must not use <iscache> in your template. Use response.setExpires(), as shown above, because the dw.util.Template class is suited only for string or markup generation and not response modification.

If you donโ€™t supply any pagecache setting for your page and none of its components (for example, neither the page type implementation itself nor component A, B, or C related component type implementations configure a pagecache setting) then the page isnโ€™t going to be pagecached at all. As soon as at least one of these building blocks within a page supplies a pagecache setting, pagecaching happens.

Because the visibility fingerprint is also factored in to the pagecache key of a page rendering or serialization (in shape of a query parameter vf of the cachable __SYSTEM__Page-Render and __SYSTEM__Page-Serialize), thereโ€™s no need for the page or component type implementation to care about page or component visibility rules, that take for example customer groups into consideration, as this is already automatically handled for you.

Best Practices

If your page or component requires a minimum pagecache setting, supply it in your page or component type implementation. Component types require extra care as this pagecache setting is affecting the pagecache of the entire page and not just the component alone.

If your page or component doesnโ€™t strictly need any specific pagecache setting - donโ€™t supply it in your page or component type implementation because it relies on the pagecache instrumentation supplied by the other components that reside on the page.
Note: This is subject to change as your merchant adjusts which components are plugged into a page over time

For a page type that doesnโ€™t supply a pagecache setting, carefully think about this decision as this means that a pageโ€™s components determine if the page is pagecached or not.

If the content of a component canโ€™t be affected by the pagecache settings of the page, other components, or must not be pagecached at all, you must move such content into a remote include to separate it from the rest of the page. The remotely included controller (and its respective template) can then use a pagecache setting as it fits its business purpose. For example, a product tile component that renders the productโ€™s inventory would likely want to include this inventory state in a remote include manner so that the current inventory can always be shown while the remainder of this component can still be pagecached.