Using the App Cache to Maximize Performance
Introduction
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.jsimport {createApp,createHandler,cacheResponseWhenDone,sendCachedResponse,getResponseFromCache} 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 === 304const namespace = 'namespace'app.use((req, res, next) => {getResponseFromCache({req, res, namespace}).then((entry) => {if (entry.found) {return sendCachedResponse(entry)}cacheResponseWhenDone({req, res, shouldCacheResponse, namespace})next()})})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.
Note
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, www.example.com/test?mobify_devicetype=mobile
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 theshouldCacheResponse
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 support.mobify.com 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.