Cache Data in Sitecore JSS Javascript Layer

In Sitecore JSS you have two options to cache data in your front-end app (besides caching entire page). You can either cache client site: using react context, react state, or for example with apollo client in-memory cache for connected GraphQL query, results are cached until you reload entire page (therefore it can be used to cache static data for sites using react router for internal links). You can also use browser’s session storage/local storage/cookies where your data is persisted, even if you reload entire page (you can check example how to use local storage for caching Sitecore dictionaries in my previous blog).

Another option to cache data in Javascript layer is to keep it in Express.js server used for headless SSR and then pass it to the JSS application, so it can be used in components.

By caching data client’s site your application still needs to call Sitecore CD at least once for every new visit (if data wasn’t stored in browser’s local storage/cookies during previous visit).

By caching data in Javascript server’s site you can reduce number of calls to Sitecore CD and for example make a request to Sitecore once per 10 minutes, independently from number of visits.

Cache Data in Javascript SSR Proxy

To cache the data in server side Javscript, we can divide our work into following tasks:

  1. Expose data in Sitecore CD server.
  2. Load and cache the data from Sitecore CD server in express.js server.
  3. Pass the data from express.js server to our JSS application.
  4. Use cached data in JSS application.

Let’s solve it one by one:

1. Get data out of Sitecore

Basic option to get data from Sitecore in JSS is to use layout service – endpoint exposed by Sitecore CD server which returns renderings data as JSON which then can be very easily used in JSS components.

Obviously in our case we cannot use layout service, JSS is calling layout service for every page reload/route change and the reason why we want to cache the data in Javascript layer is to actually reduce JSON payload returned from layout service.

So we need to remove data source item from the component which shall use cache and to create new endpoint which returns data from Sitecore. It would be great if we can return data in exactly same structure as layout service, so we don’t have to do too many changes in our front-end components when switching to cache.

Fortunately it’s quite easy to mimic Layout Service API in our custom MVC controller.

We can implement base class with generic methods:

Sample specific implementation can look like this:

In our case we want to return data source item of the widget, which has some tabs defined as child items. We need to register this controller with following class:

and config patch:

Now we should have working API under http(s)://{sitecore-host}/api/sample/widget. Mind that you need to pass same ?sc_apikey={your api key} query string parameter as for layout service, cause we used exactly the same MVC attributes.

2. Load and Cache Data in Express.js Server

To load and cache data in Express.js server layer we can reuse the code which cache Sitecore dictionaries in SSR proxy provided by Sitecore in config.js file and adding it to viewBag object. We just need to modify createViewBag function:

We only modified marked lines, where we fetch dictionaries and our custom API, then if both requests are done we add two entries to viewBag object. Keep in mind that viewBag is stored in cache under site and language specific key. To control the cache timeout we can change stdTTL in dictionaryCache declaration.

3. Pass Data from Express.js to JSS Application

Now we have our data cached in Express.js layer, but we want to use it somewhere in JSS application, right? To do it we need to check how server calls code from JSS app bundle.

In index.js file of SSR proxy in line:
server.use('*', scProxy(config.serverBundle.renderView, config, config.serverBundle.parseRouteUrl));
server executes renderView function from JSS app bundle for every request which is not handled by previous middleware (for example static assets). We can find this function in server/server.js inside our bundle code (assuming it was created with jss create command) and viewBag is one of the parameter of the function.

RenderView is responsible for following tasks:

  1. Initialize i18n with Sitecore dictionary loaded from viewBag, so it can be used for translation inside React components.
  2. Call server side rendering of JSS app for current state using renderToStringWithData and store results in a string (state contains i.a. layout service results executed for current path).
  3. Place the string from previous point inside <html> element with title and meta data inside <head> added by react-helmet’s Helmet.renderStatic()
  4. Serialize current state of layout service (eventually together with connected GraphQL query results) and add it to HTML inside __JSS_STATE__ variable, so when the app is rendered again client’s side, it doesn’t need to query layout service or call connected GraphQL queries again, cause all the data is already inside __JSS_STATE__ variable.

Finally HTML document generated in RenderView is returned to the client.

To get the data from viewBag and pass it to the React components, we can add our logic inside RenderView, before rendering to string. We move the data to sitecore.context, so it will be available in React components which uses withSitecoreContext() and stored later in __JSS_STATE__.
Modifications in server/server.js file (only marked lines):

4. Use Cached Data in JSS App

We can finally use our cached data in sample component:

At first glance it may look complicated but actual change comparing to standard component is very simple, we only replaced props.fields.{field} with props.sitecoreContext.widget.{field}. Rest of the code is to support JSS connected and integrated modes (to fetch the data if it wasn’t passed from SSR) which on production code you would move to HOC or custom context.