When running JSS application created from starter template provided by Sitecore, you may notice that your application is making a request to /sitecore/api/dictionary
every time you load a page.
This is obviously not the most optimal solution, taking into account that Sitecore dictionaries are rather static and those client-side requests are done even when your app works in headless or integrated mode with Server Side Rendering (for details see later section).
If you analyze JSS starter template code, in i18n.js
(i18n is a translation module used in JSS) you will find suggestion to cache the dictionary to improve your application performance, but if you are new to i18n, it may be difficult to understand what to do.
TLDR; to cache dictionaries fetched from Sitecore in JSS app’s local storage:
- Add
i18next-chained-backend
andi18next-localstorage-backend
to yourpackage.json
- Make following changes in your
i18n.js
file (check highlighted lines, the rest should be unchanged):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import i18n from 'i18next'; import backend from 'i18next-chained-backend'; import localStorageBackend from 'i18next-localstorage-backend'; import fetchBackend from 'i18next-fetch-backend'; import { initReactI18next } from 'react-i18next'; import config from './temp/config'; export default function i18nInit(language, dictionary) { return new Promise((resolve, reject) => { ... if (dictionary) { ... } else { // eslint-disable-next-line const dictionaryServicePath = `${config.sitecoreApiHost}/sitecore/api/jss/dictionary/${config.jssAppName}/{{lng}}?sc_apikey=${config.sitecoreApiKey}`; options.backend = { backends: [ localStorageBackend, // primary fetchBackend, // fallback ], backendOptions: [ { prefix: 'i18next_my-app_res_', // cache prefix use your app name here expirationTime: 5 * 60 * 1000, // cache for 5 minutes }, { loadPath: dictionaryServicePath, parse: (data) => { const parsedData = JSON.parse(data); if (parsedData.phrases) { return parsedData.phrases; } return parsedData; }, }, ], }; i18n .use(backend) .use(initReactI18next) .init(options, (error) => { if (error) reject(error); resolve(); }); } }); } |
During initial load of JSS application in headless or integrated mode you shall still see two requests in browser’s network tab, one to get the document and one to /sitecore/api/jss/dictionary
:
But if you reload the page, you will see only one request to layout service and your dictionary cached for 5 minutes in local storage (each language will have it’s own store under different key):
Sitecore Dictionaries in JSS Headless Proxy
In the code of JSS starter template and node headless SSR proxy there are four places where application interacts with dictionaries:
- Fetch dictionary from Sitecore, add to node cache (by default for 5 minutes) and pass to SSR process with viewBag in https://github.com/Sitecore/jss/blob/master/samples/node-headless-ssr-proxy/config.js
- Load dictionary from viewBag and initialize i18n for SSR in https://github.com/Sitecore/jss/blob/master/samples/react/server/server.js
- Initialize i18n for CSR (or hydrate in SSR) in https://github.com/Sitecore/jss/blob/master/samples/react/src/index.js
- Resolve i18n with dictionary passed in parameter or fetched from Sitecore API in https://github.com/Sitecore/jss/blob/master/samples/react/src/i18n.js
Visualization of page rendering flow with JSS SSR proxy (caching in local storage described in this article marked in green):
If you checked the diagram, you may be wondering why there is a client-side call to fetch the dictionaries even if it’s already stored in node cache and used for SSR.
When React rendered your components server-side, it has to do it again to add JavaScript events by calling ReactDom.Hydrate (which is possible client-side only). To optimize this process and avoid calls to Sitecore, JSS adds results from layout service and GraphQL queries to __JSS_STATE__ element in <script> section of HTML returned from node server, so it’s available client-side.
Because there could be a lot of entries in Sitecore dictionary and this data is the same for every route, it’s actually better to fetch it again from Sitecore and cache the data client-side, than to include it in __JSS_STATE__ and return big chunk of static data for every request.