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:
- Path headers from
params.HedgerulesPathHeadersinhugo.toml(root/, specific paths, or extension wildcards) - Per-page headers from
HedgerulesHeadersin 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):
- Root
/— global defaults applied to every response - Parent directories — each parent path with trailing
/(e.g.,/docs/for/docs/headers/) - Extension wildcard — matches by file extension (e.g.,
*.xml) - 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.