Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Injecting nonRoute data into Sitecore JSS context using customizeContext
When the app runs in disconnected mode, and Sitecore is not present, we need to give
the app copies of the Sitecore APIs it depends on (layout service, dictionary service, content service)
to talk to so that the app can run using the locally defined disconnected data.
This is accomplished by spinning up a small Express server that mocks the APIs, and then
telling the dev server to proxy requests to the API paths to this express instance.
/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');
const { createDefaultDisconnectedServer } = require('@sitecore-jss/sitecore-jss-dev-tools');
const { remapFieldsArrayToFieldsObject } = require('../node_modules/@sitecore-jss/sitecore-jss-dev-tools/dist/disconnected-server/layout-service');
const config = require('../package.json').config;
const touchToReloadFilePath = 'src/temp/config.js';
// Retrieves the top most node in the nonRoutes array.
const retrieveContentNode = (currentManifest) => {
// `rootItemName` from content.sitecore.js
const contentItem = currentManifest.items.nonRoutes.find(nonRoute => === 'Content');
return contentItem;
const findContentItemById = (currentNode, id) => {
let foundNode;
// check to see if the current node is a match
const is_match = === id;
// if it is as match, set found_node to the current node
if (is_match) {
foundNode = currentNode;
} else if (currentNode.children) {
// otherwise, check the children of this node if they exist
currentNode.children.some(child => {
foundNode = findContentItemById(child, id);
return foundNode != null;
return foundNode;
const convertContentItemToLayoutServiceFormat = (contentItem) => {
let item;
if (contentItem) {
item = JSON.parse(JSON.stringify(contentItem));
if (item.fields) {
item.fields = remapFieldsArrayToFieldsObject(item.fields);
return item;
const proxyOptions = {
appRoot: path.join(__dirname, '..'),
appName: config.appName,
watchPaths: ['./data'],
language: config.language,
port: process.env.PROXY_PORT || 3042,
onManifestUpdated: (manifest) => {
// if we can resolve the config file, we can alter it to force reloading the app automatically
// instead of waiting for a manual reload. We must materially alter the _contents_ of the file to trigger
// an actual reload, so we append "// reloadnow" to the file each time. This will not cause a problem,
// since every build regenerates the config file from scratch and it's ignored from source control.
if (fs.existsSync(touchToReloadFilePath)) {
const currentFileContents = fs.readFileSync(touchToReloadFilePath, 'utf8');
const newFileContents = `${currentFileContents}\n// reloadnow`;
fs.writeFileSync(touchToReloadFilePath, newFileContents, 'utf8');
console.log('Manifest data updated. Reloading the browser.');
} else {
console.log('Manifest data updated. Refresh the browser to see latest content!');
customizeContext: (context, route, currentManifest, request, response) => {
// Retrieves the top level content node for nonRoutes
const contentNode = retrieveContentNode(currentManifest);
// If it does not exist, something is wrong.
if (!contentNode) {
console.error('Unable to locate content node, check manifest.');
// Find all of the items we want to inject into our context
const additionalContext = {
site_settings: findContentItemById(contentNode, 'site-settings')
// Convert all of the found items to layout service structure
Object.keys(additionalContext).forEach(key => {
additionalContext[key] = convertContentItemToLayoutServiceFormat(additionalContext[key])
// Return the existing context with our injected properties added
return { ...additionalContext, ...context };
// Need to customize something that the proxy options don't support?
// createDefaultDisconnectedServer() is a boilerplate that you can copy from
// and customize the middleware registrations within as you see fit.
// See
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment