Hedgerules lets you set custom HTTP response headers on any path, using a CloudFront Key Value Store and a viewer-response CloudFront Function.

How it works#

Headers are defined in a _hedge_headers.json file in your Hugo build output. This file is generated by a Hugo template that merges headers from two sources:

  1. Path headers from params.HedgerulesPathHeaders in hugo.toml (root /, specific paths, or extension wildcards)
  2. Per-page headers from HedgerulesHeaders in page frontmatter

Hedgerules reads this file and uploads each entry to a CloudFront KVS.

Header matching#

The viewer-response function checks multiple KVS patterns for each request, in order of specificity (least to most):

  1. Root / — global defaults applied to every response
  2. Parent directories — each parent path with trailing / (e.g., /docs/ for /docs/headers/)
  3. Extension wildcard — matches by file extension (e.g., *.xml)
  4. Exact path — the full request path (e.g., /docs/headers/)

More-specific matches override less-specific ones. For example, a header set on /docs/headers/ overrides the same header name from /.

Path headers#

Define headers by path pattern in hugo.toml:

[params.HedgerulesPathHeaders]
  [params.HedgerulesPathHeaders."/"]
    X-Content-Type-Options = "nosniff"
    X-Frame-Options = "DENY"
    Referrer-Policy = "strict-origin-when-cross-origin"
  [params.HedgerulesPathHeaders."/favicon.svg"]
    X-Hedgerules-Icon-About = "The icon is made of the characters 'h≡', in JetBrains Mono, size 56, color '#14532D'"
  [params.HedgerulesPathHeaders."*.xml"]
    Content-Type = "application/xml; charset=utf-8"
    X-Content-Type-Options = "nosniff"

The root / key defines global defaults applied to every response. Other keys match specific paths or extension wildcards.

Per-page headers#

Define headers in page frontmatter:

---
title: "My Page"
HedgerulesHeaders:
  Cache-Control: "public, max-age=86400"
  X-Custom-Header: "some-value"
---

These are stored in the KVS under the page’s exact path (e.g., /docs/my-page/) and override root headers with the same name.

Applying the same header to many pages#

If you want a header on every page in a section (not just root defaults), use Hugo’s cascade in the section’s _index.md:

---
title: "Blog"
cascade:
  HedgerulesHeaders:
    X-Section: "blog"
---

Hugo’s template then emits a separate _hedge_headers.json entry for each page in the section. Let Hugo enumerate the pages — the CloudFront Function stays simple.

KVS constraints#

CloudFront KVS has size limits:

  • Maximum key size: 512 bytes
  • Maximum key + value size: 1 KB (1024 bytes)
  • Maximum total data size: 5 MB

Hedgerules validates these constraints before uploading and reports errors if any entry exceeds them.