diff --git a/packages/config/src/options.js b/packages/config/src/options.js index bc5dccaa46..aa2341727d 100644 --- a/packages/config/src/options.js +++ b/packages/config/src/options.js @@ -219,6 +219,21 @@ export function getNuxtConfig (_options) { options.debug = options.dev } + // Validate that etag.hash is a function, if not unset it + if (options.render.etag) { + const { hash } = options.render.etag + if (hash) { + const isFn = typeof hash === 'function' + if (!isFn) { + options.render.etag.hash = undefined + + if (options.dev) { + consola.warn(`render.etag.hash should be a function, received ${typeof hash} instead`) + } + } + } + } + // Apply default hash to CSP option if (options.render.csp) { options.render.csp = defaults({}, options.render.csp, { diff --git a/packages/config/test/options.test.js b/packages/config/test/options.test.js index ab0b1e771a..0f58aceb02 100644 --- a/packages/config/test/options.test.js +++ b/packages/config/test/options.test.js @@ -87,6 +87,16 @@ describe('config: options', () => { expect(store).toEqual(true) }) + test('should unset and warn when etag.hash not a function', () => { + const { render: { etag } } = getNuxtConfig({ render: { etag: { hash: true } } }) + expect(etag).toMatchObject({ hash: undefined }) + expect(consola.warn).not.toHaveBeenCalledWith('render.etag.hash should be a function, received boolean instead') + + const { render: { etag: etagDev } } = getNuxtConfig({ dev: true, render: { etag: { hash: true } } }) + expect(etagDev).toMatchObject({ hash: undefined }) + expect(consola.warn).toHaveBeenCalledWith('render.etag.hash should be a function, received boolean instead') + }) + test('should enable csp', () => { const { render: { csp } } = getNuxtConfig({ render: { csp: { allowedSources: true, test: true } } }) expect(csp).toEqual({ diff --git a/packages/server/src/middleware/nuxt.js b/packages/server/src/middleware/nuxt.js index 824088da84..2657b91c8b 100644 --- a/packages/server/src/middleware/nuxt.js +++ b/packages/server/src/middleware/nuxt.js @@ -38,7 +38,8 @@ export default ({ options, nuxt, renderRoute, resources }) => async function nux // Add ETag header if (!error && options.render.etag) { - const etag = generateETag(html, options.render.etag) + const { hash } = options.render.etag + const etag = hash ? hash(html, options.render.etag) : generateETag(html, options.render.etag) if (fresh(req.headers, { etag })) { res.statusCode = 304 res.end() diff --git a/packages/server/test/middleware/nuxt.test.js b/packages/server/test/middleware/nuxt.test.js index 74d6d0ccf9..7d003b5888 100644 --- a/packages/server/test/middleware/nuxt.test.js +++ b/packages/server/test/middleware/nuxt.test.js @@ -117,6 +117,22 @@ describe('server: nuxtMiddleware', () => { expect(res.setHeader).nthCalledWith(1, 'ETag', 'etag-hash') }) + test('should set etag after rendering through hook', async () => { + const context = createContext() + const hash = jest.fn(() => 'etag-hook') + context.options.render.etag = { hash } + + const result = { html: 'rendered html' } + context.renderRoute.mockReturnValue(result) + const nuxtMiddleware = createNuxtMiddleware(context) + const { req, res, next } = createServerContext() + + await nuxtMiddleware(req, res, next) + + expect(hash).toBeCalledWith('rendered html', expect.any(Object)) + expect(res.setHeader).nthCalledWith(1, 'ETag', 'etag-hook') + }) + test('should return 304 if request is fresh', async () => { const context = createContext() const result = { html: 'rendered html' }