Mobify DevCenter

Using the App Cache to Maximize Performance


In server-side rendered (SSR) PWAs, it’s often expensive to render a page. The App Cache can be used to store rendered pages to quickly respond to subsequent requests. This code stores and fetches all SSR pages from the App Cache:

// <PROJECT_DIR>/app/ssr.js
import {
} from 'progressive-web-sdk/dist/ssr/server/express'
import {render} from 'progressive-web-sdk/dist/ssr/server/react-rendering'
const app = createApp({
const shouldCacheResponse = (req, res) => res.statusCode === 200 || res.statusCode === 304
const namespace = 'namespace'
app.use((req, res, next) => {
getResponseFromCache({req, res, namespace}).then((entry) => {
if (entry.found) {
return sendCachedResponse(entry)
cacheResponseWhenDone({req, res, shouldCacheResponse, namespace})
app.get('/*', render)

The progressive-web-sdk provides three functions, getResponseFromCache(), sendCachedResponse() and cacheResponseWhenDone() to interact with the App Cache.

The example checks if a response for req exists within the App Cache using getResponseFromCache(). If it does, it’s returned using sendCachedResponse(). If not, cacheResponseWhenDone() stores the response in the cache for use with the next request.

Note that the example uses the App Cache to respond to all requests. You will need to update shouldRespondToRequestFromCache() so that it does not store or fetch any pages that should not be cached. Generally, pages that contain personal information or frequently-changing information should not be cached.

getResponseFromCache() looks for a cached response by checking:

  • The lowercased request path and querystring
  • The device type (mobile, tablet, or desktop)
  • The request class

You can change this behavior using the key argument. Additionally, you can partition the cache with the namespace argument.

getResponseFromCache() returns a Promise which resolves to an entry. entry.found checks that a matching response was found in the cache and sendCachedResponse() sends it back to the client.

If no matching response was found, cacheResponseWhenDone() stores the outgoing response for next time. Responses are stored in the cache for as long as the s-maxage and max-age properties of the Cache-Control header indicate. You can override this behaviour with the expiry argument.


The App Cache should be queried immediately after the App Server receives a request. This way, if a response is found in the cache, it will be returned as quickly as possible.

Testing the App Cache

getResponseFromCache() adds a HTTP response header x-mobify-from-cache with values true or false to show whether a response came from the App Cache.

A common gotcha using the App Cache is that requests may be responded to from the CDN. You can ensure that requests are sent back to the App Server by adding the HTTP request header X-Mobify-Cachebreaker: 1 to your requests. This header only breaks the CDN cache, not the App Cache.

Force using a specific device type

During testing, it might be desired to view App Cache response by a specific device type. To force using a device type, simply append a query parameter mobify_devicetype to the URL, the value can be mobile, tablet or desktop, for example, will return the mobile version of the cache.

Clearing the App Cache

It may be necessary to clear pages stored in the App Cache before they expire.

Early expiry can be implemented using the namespace argument to getResponseFromCache(). Changing the namespace effectively invalidates the entire cache, as lookups will occur against a new partition.

To clear the App Cache on each deploy you can use process.env.DEPLOY_ID in namespace.

More robust implementations may query APIs to check whether the data used to render a page has changed. This information can then be used to set key or namespace.

Implementation considerations

When using the App Cache, consider:

  • What pages should be cached? App Cache responses are shared, so don’t use it to store personalized information. Also, App Cache may not be appropriate for frequently-changing pages.
  • Should error page be cached? By default, cacheResponseWhenDone() stores all responses, including HTTP errors. If you don’t want to store errors, pass the shouldCacheResponse argument to change this behaviour.
  • How long should pages be cached? High cache times mean that more requests will be responded to from the cache. However, they also increase the chance of serving stale content.

Contact for help optimizing your App Cache implementation.

Next steps

Continue through our Server-Side Rendering Performance series, with an article about Testing and Debugging Your Local Backend. Or explore Improving Client-Side Performance.