diff --git a/middleware/archived-enterprise-versions-assets.js b/middleware/archived-enterprise-versions-assets.js index e34d5adc80..c1f1253712 100644 --- a/middleware/archived-enterprise-versions-assets.js +++ b/middleware/archived-enterprise-versions-assets.js @@ -3,6 +3,8 @@ const patterns = require('../lib/patterns') const isArchivedVersion = require('../lib/is-archived-version') const got = require('got') +const ONE_DAY = 24 * 60 * 60 // 1 day in seconds + // This module handles requests for the CSS and JS assets for // deprecated GitHub Enterprise versions by routing them to static content in // help-docs-archived-enterprise-versions @@ -26,8 +28,10 @@ module.exports = async (req, res, next) => { res.set('content-length', r.headers['content-length']) res.set('x-is-archived', 'true') res.set('x-robots-tag', 'noindex') - res.send(r.body) + // Allow the browser and Fastly to cache these + res.set('cache-control', `public, max-age=${ONE_DAY}`) + return res.send(r.body) } catch (err) { - next() + return next() } } diff --git a/middleware/archived-enterprise-versions.js b/middleware/archived-enterprise-versions.js index 71b2abeed9..682793d975 100644 --- a/middleware/archived-enterprise-versions.js +++ b/middleware/archived-enterprise-versions.js @@ -46,7 +46,7 @@ module.exports = async (req, res, next) => { res.set('location', staticRedirect[1]) } - res.send(r.body) + return res.send(r.body) } catch (err) { for (const fallbackRedirect of getFallbackRedirects(req, requestedVersion) || []) { try { @@ -54,7 +54,7 @@ module.exports = async (req, res, next) => { return res.redirect(301, fallbackRedirect) } catch (err) { } // noop } - next() + return next() } } diff --git a/middleware/disable-caching-on-safari.js b/middleware/disable-caching-on-safari.js index 45c8df5cf0..4281b37c91 100644 --- a/middleware/disable-caching-on-safari.js +++ b/middleware/disable-caching-on-safari.js @@ -3,5 +3,5 @@ module.exports = (req, res, next) => { if (isSafari) { res.header('Last-Modified', (new Date()).toUTCString()) } - next() + return next() } diff --git a/middleware/index.js b/middleware/index.js index 19559b8d7e..866047c346 100644 --- a/middleware/index.js +++ b/middleware/index.js @@ -37,8 +37,10 @@ module.exports = function (app) { app.use(require('./handle-csrf-errors')) // Must come before regular handle-errors // *** Headers *** + app.set('etag', false) // We will manage our own ETags if desired app.use(require('compression')()) app.use(require('./disable-caching-on-safari')) + app.use(require('./set-fastly-surrogate-key')) // *** Config and context for redirects *** app.use(require('./req-utils')) // Must come before record-redirect and events @@ -66,10 +68,20 @@ module.exports = function (app) { etag: false, immutable: true, lastModified: false, - maxAge: '28 days' + maxAge: '28 days' // Could be infinite given our fingerprinting + })) + app.use('/assets', express.static('assets', { + index: false, + etag: false, + lastModified: false, + maxAge: '1 day' // Relatively short in case we update images + })) + app.use('/public', express.static('data/graphql', { + index: false, + etag: false, + lastModified: false, + maxAge: '7 days' // A bit longer since releases are more sparse })) - app.use('/assets', express.static('assets')) - app.use('/public', express.static('data/graphql')) app.use('/events', instrument('./events')) app.use('/csrf', instrument('./csrf-route')) app.use('/search', instrument('./search')) diff --git a/middleware/render-page.js b/middleware/render-page.js index a81b549e2c..1c887bb3d4 100644 --- a/middleware/render-page.js +++ b/middleware/render-page.js @@ -115,6 +115,7 @@ module.exports = async function renderPage (req, res, next) { const output = await liquid.parseAndRender(layout, context) // First, send the response so the user isn't waiting + // NOTE: Do NOT `return` here as we still need to cache the response afterward! res.send(output) // Finally, save output to cache for the next time around diff --git a/middleware/set-fastly-surrogate-key.js b/middleware/set-fastly-surrogate-key.js new file mode 100644 index 0000000000..5ca1eda829 --- /dev/null +++ b/middleware/set-fastly-surrogate-key.js @@ -0,0 +1,12 @@ +module.exports = (req, res, next) => { + // Fastly provides a Soft Purge feature that allows you to mark content as outdated (stale) instead of permanently + // purging and thereby deleting it from Fastly's caches. Objects invalidated with Soft Purge will be treated as + // outdated (stale) while Fastly fetches a new version from origin. + // + // Use of a surrogate key is required for soft purging + // https://docs.fastly.com/en/guides/soft-purges + // https://docs.fastly.com/en/guides/getting-started-with-surrogate-keys + res.set('surrogate-key', 'all-the-things') + + return next() +} diff --git a/tests/rendering/server.js b/tests/rendering/server.js index c4f59fa6ac..9910e12c32 100644 --- a/tests/rendering/server.js +++ b/tests/rendering/server.js @@ -66,7 +66,7 @@ describe('server', () => { const res = await get('/en') expect(res.headers['cache-control']).toBe('private, no-store') expect(res.headers['surrogate-control']).toBe('private, no-store') - expect(res.headers).not.toHaveProperty('surrogate-key') + expect(res.headers['surrogate-key']).toBe('all-the-things') }) test('does not render duplicate or tags', async () => {