Skip to main content

Import

import { etag } from 'hono/etag'

Usage

const app = new Hono()

app.use('/etag/*', etag())

app.get('/etag/abc', (c) => {
  return c.text('Hono is hot')
})

Options

The etag middleware accepts an optional ETagOptions object:
weak
boolean
default:false
Define using weak validation. If true, then W/ is added to the prefix of the ETag value.
retainedHeaders
string[]
The headers that you want to retain in the 304 Not Modified response. By default, includes headers required by the HTTP spec for 304 responses.
generateDigest
(body: Uint8Array) => ArrayBuffer | Promise<ArrayBuffer>
A custom digest generation function. By default, it uses SHA-1 algorithm via crypto.subtle.digest. This function is called with the response body as a Uint8Array and should return a hash as an ArrayBuffer or a Promise of one.

Signature

etag(options?: ETagOptions): MiddlewareHandler

Constants

RETAINED_304_HEADERS

const RETAINED_304_HEADERS = [
  'cache-control',
  'content-location',
  'date',
  'etag',
  'expires',
  'vary',
]
Default headers to pass through on 304 responses as specified by HTTP spec.

Examples

Basic usage

const app = new Hono()

app.use('/etag/*', etag())

app.get('/etag/abc', (c) => {
  return c.text('Hono is hot')
})

Weak validation

app.use('/api/*', etag({ weak: true }))

app.get('/api/data', (c) => {
  return c.json({ data: 'example' })
})

// Response: ETag: W/"abc123"

Custom retained headers

app.use('/api/*', etag({
  retainedHeaders: ['etag', 'cache-control', 'date', 'x-custom-header']
}))

Custom digest function

import { createHash } from 'crypto'

app.use('/api/*', etag({
  generateDigest: async (body) => {
    // Use MD5 instead of SHA-1
    const hash = createHash('md5')
    hash.update(body)
    return hash.digest().buffer
  }
}))

Apply to specific routes

const app = new Hono()

app.use('/static/*', etag())

app.get('/static/file', (c) => {
  return c.text('Static content')
})

Behavior

  • Generates ETag header from response body using SHA-1 hash (by default)
  • If response already has an ETag header, uses it instead of generating a new one
  • Compares ETag with If-None-Match header from request
  • Returns 304 Not Modified if ETags match
  • 304 responses only include retained headers (specified by spec or config)
  • Weak validation uses W/ prefix for ETags
  • If digest cannot be generated (e.g., no body), skips ETag generation
  • If crypto.subtle is not available and no custom generateDigest is provided, skips ETag generation

Build docs developers (and LLMs) love