Docs Content Negotiation
CloudFront distribution with HTTP content negotiation for the Hummingbird
public documentation site (hummingbird-project.io). When a client sends
Accept: text/markdown, a CloudFront Function rewrites the request URI to the
pre-built .md equivalent so GitLab Pages serves Hugo’s markdown output
instead of HTML.
Overview
Hugo generates .md files alongside .html for every page. GitLab Pages is a
static file server — it cannot inspect Accept headers and select a response
type. This stack interposes a CloudFront distribution between DNS and GitLab
Pages, adding the header-based routing that GitLab Pages cannot provide.
The stack is deployed by the infrastructure repository. After each deploy,
Route53 A and AAAA alias records for hummingbird-project.io are updated to
point at the CloudFront distribution.
Architecture
flowchart LR
client["Client\n(browser / agent)"]
dns["Route53\nhummingbird-project.io"]
cf["CloudFront\nDistribution"]
fn["CloudFront Function\n(viewer-request)"]
pages["GitLab Pages\nredhat.gitlab.io"]
client -->|"HTTPS"| dns
dns -->|"A/AAAA alias"| cf
cf --> fn
fn -->|"rewrite URI if\nAccept: text/markdown"| cf
cf -->|"forwards viewer Host header"| pages
Content Negotiation Logic
The cloudfront-js-2.0 viewer-request function runs at every edge location
before the cache is consulted:
- If
Acceptdoes not includetext/markdown→ pass through unchanged (HTML response served as before). - Rewrite only known HTML-like patterns:
/docs/(trailing slash) →/docs/index.md/docs/page.html→/docs/page.md/docs/page(no extension) →/docs/page/index.md
- Everything else (
.css,.js,.png,.md,.txt, etc.) → pass through unchanged.
Because the rewrite happens before the cache key is computed (viewer-request
event), HTML and markdown responses land in separate cache entries. No custom
cache policy is needed — the standard CachingOptimized managed policy is
used.
Origin
The origin is redhat.gitlab.io (the GitLab Pages default domain). GitLab
Pages routes requests based on the Host header. The AllViewer origin
request policy forwards the viewer’s Host: hummingbird-project.io header
to the origin, so the correct project is served regardless of what DNS
hummingbird-project.io resolves to.
Components
| Resource | Type | Description |
|---|---|---|
ContentNegotiationFunction |
CloudFront Function | viewer-request; rewrites URI for Accept: text/markdown |
DocsCertificate |
ACM Certificate | TLS for the custom domain; DNS-validated via Route53 |
DocsDistribution |
CloudFront Distribution | CDN proxy in front of GitLab Pages |
Parameters
| Parameter | Description |
|---|---|
ResourcePrefix |
Prefix for resource names (e.g., arr-hummingbird-prod-docs-cdn) |
DocsDomainName |
Custom domain for the documentation site (e.g., hummingbird-project.io) |
HostedZoneId |
Route53 hosted zone ID — used for ACM DNS validation CNAME auto-creation |
Outputs
| Output | Description |
|---|---|
DistributionDomainName |
CloudFront domain name |
DistributionId |
CloudFront distribution ID |
Deployment
Deployed by the infrastructure repository. Stack name and region are
configured in samconfig.toml.j2 in the infrastructure repo.
On the first deploy, CloudFormation requests an ACM certificate and
auto-creates the DNS validation CNAME in Route53 (via HostedZoneId). Once
validated (~2–5 minutes), the distribution is created and Route53 alias records
are updated to point at it.
Verification
# Content negotiation: should return text/markdown
curl -H "Accept: text/markdown" https://hummingbird-project.io/docs/ -sI
# Normal browsing: should return text/html (unchanged)
curl https://hummingbird-project.io/docs/ -sI
# Static assets are excluded from rewriting
curl -H "Accept: text/markdown" https://hummingbird-project.io/favicon.ico -sI
# Alternate domains still redirect to primary (unchanged)
curl https://project-hummingbird.io/ -sI # expect 308
curl https://hummingbird-project.org/ -sI # expect 308
# DNS resolves to CloudFront, not the GitLab Pages IP
dig hummingbird-project.io A +short
Scope
Only the primary domain (hummingbird-project.io) is proxied through
CloudFront. The alternate domains (project-hummingbird.io,
hummingbird-project.org) continue pointing directly to GitLab Pages, which
issues 308 redirects to the primary domain.