From 393b623aef2921d70383381207528884902c5a0a Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 5 Mar 2021 13:41:52 +0100 Subject: [PATCH] Add a `Cache-Control: no-store` header to the `index.html` response to instruct the browser to not cache the pages Signed-off-by: Dominik Henneke --- .changeset/good-humans-draw.md | 6 ++++ .../app-backend/src/service/router.test.ts | 18 +++++++++- plugins/app-backend/src/service/router.ts | 33 +++++++++++++++---- 3 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 .changeset/good-humans-draw.md diff --git a/.changeset/good-humans-draw.md b/.changeset/good-humans-draw.md new file mode 100644 index 0000000000..ff9bacaa49 --- /dev/null +++ b/.changeset/good-humans-draw.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-app-backend': patch +--- + +Add a `Cache-Control: no-store` header to the `index.html` response to instruct the browser to not cache the pages. +This is a workaround for a missing `staticFallbackHandler` since an old `index.html` might link to static assets from a previous deployment. diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts index 35dd9287a6..1a6fee01a0 100644 --- a/plugins/app-backend/src/service/router.test.ts +++ b/plugins/app-backend/src/service/router.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { resolve as resolvePath } from 'path'; import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import express from 'express'; import Router from 'express-promise-router'; +import { resolve as resolvePath } from 'path'; import request from 'supertest'; import { createRouter } from './router'; @@ -68,6 +68,22 @@ describe('createRouter', () => { expect(response.status).toBe(200); expect(response.text.trim()).toBe('this is index.html'); }); + + it.each(['/index.html', '/other.html', '/missing.html'])( + 'returns %s with no-store Cache-Control header', + async file => { + const response = await request(app).get(file); + expect(response.header['cache-control']).toBe('no-store'); + }, + ); + + it.each(['/static/main.txt'])( + 'returns %s with default Cache-Control header', + async file => { + const response = await request(app).get(file); + expect(response.header['cache-control']).toBe('public, max-age=0'); + }, + ); }); describe('createRouter with static fallback handler', () => { diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index 4379fcccb9..ba407d9ef8 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { resolve as resolvePath } from 'path'; -import express from 'express'; -import Router from 'express-promise-router'; -import { Logger } from 'winston'; import { notFoundHandler, resolvePackagePath } from '@backstage/backend-common'; import { Config } from '@backstage/config'; -import { injectConfig, readConfigs } from '../lib/config'; +import express from 'express'; +import Router from 'express-promise-router'; import fs from 'fs-extra'; +import { resolve as resolvePath } from 'path'; +import { Logger } from 'winston'; +import { injectConfig, readConfigs } from '../lib/config'; export interface RouterOptions { config: Config; @@ -95,9 +95,28 @@ export async function createRouter( staticRouter.use(notFoundHandler()); router.use('/static', staticRouter); - router.use(express.static(appDistDir)); + router.use( + express.static(appDistDir, { + setHeaders: (res, path) => { + // The Cache-Control header instructs the browser to not cache html files since it might + // link to static assets from recently deployed versions. This is a workaround when no + // staticFallbackHandler is configured. + // use `as any` since express uses mime v1 while we only have types for mime v2 + if ((express.static.mime as any).lookup(path) === 'text/html') { + res.setHeader('Cache-Control', 'no-store'); + } + }, + }), + ); router.get('/*', (_req, res) => { - res.sendFile(resolvePath(appDistDir, 'index.html')); + res.sendFile(resolvePath(appDistDir, 'index.html'), { + headers: { + // The Cache-Control header instructs the browser to not cache the index.html since it might + // link to static assets from recently deployed versions. This is a workaround when no + // staticFallbackHandler is configured. + 'cache-control': 'no-store', + }, + }); }); return router;