From 393b623aef2921d70383381207528884902c5a0a Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 5 Mar 2021 13:41:52 +0100 Subject: [PATCH 1/4] 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; From 173ed924b7f13f334dc40c84e0e127e51e803745 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 5 Mar 2021 16:02:23 +0100 Subject: [PATCH 2/4] Include max-age=0 Signed-off-by: Dominik Henneke --- plugins/app-backend/src/service/router.test.ts | 2 +- plugins/app-backend/src/service/router.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts index 1a6fee01a0..cabe4c59b9 100644 --- a/plugins/app-backend/src/service/router.test.ts +++ b/plugins/app-backend/src/service/router.test.ts @@ -73,7 +73,7 @@ describe('createRouter', () => { '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'); + expect(response.header['cache-control']).toBe('no-store, max-age=0'); }, ); diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index ba407d9ef8..fec300bace 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -103,7 +103,7 @@ export async function createRouter( // 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'); + res.setHeader('Cache-Control', 'no-store, max-age=0'); } }, }), @@ -114,7 +114,7 @@ export async function createRouter( // 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', + 'cache-control': 'no-store, max-age=0', }, }); }); From e6fa667639c171597577573352a63ee8d2bf02a1 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Mon, 8 Mar 2021 11:37:35 +0100 Subject: [PATCH 3/4] Update type and changeset Signed-off-by: Dominik Henneke --- .changeset/good-humans-draw.md | 4 ++-- plugins/app-backend/src/service/router.ts | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.changeset/good-humans-draw.md b/.changeset/good-humans-draw.md index ff9bacaa49..3e97fa15b6 100644 --- a/.changeset/good-humans-draw.md +++ b/.changeset/good-humans-draw.md @@ -2,5 +2,5 @@ '@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. +Add a `Cache-Control: no-store, max-age=0` header to the `index.html` response to instruct the browser to not cache the pages. +This tells the browser to not serve a cached `index.html` that might link to static assets from a previous deployment that are not available anymore. diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index fec300bace..d140c73180 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -23,6 +23,9 @@ import { resolve as resolvePath } from 'path'; import { Logger } from 'winston'; import { injectConfig, readConfigs } from '../lib/config'; +// express uses mime v1 while we only have types for mime v2 +type Mime = { lookup(arg0: string): string }; + export interface RouterOptions { config: Config; logger: Logger; @@ -101,8 +104,10 @@ export async function createRouter( // 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') { + if ( + ((express.static.mime as unknown) as Mime).lookup(path) === + 'text/html' + ) { res.setHeader('Cache-Control', 'no-store, max-age=0'); } }, From bded5a3237b0ce8bc676cfad51367c8aec2bc038 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Tue, 9 Mar 2021 09:55:15 +0100 Subject: [PATCH 4/4] Update comments Signed-off-by: Dominik Henneke --- plugins/app-backend/src/service/router.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index d140c73180..8fe8355bc3 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -102,8 +102,7 @@ export async function createRouter( 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. + // link to static assets from recently deployed versions. if ( ((express.static.mime as unknown) as Mime).lookup(path) === 'text/html' @@ -117,8 +116,7 @@ export async function createRouter( 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. + // link to static assets from recently deployed versions. 'cache-control': 'no-store, max-age=0', }, });