# Apache

Serve prebuilt Markdown and HTML from the same canonical URL in Apache using rewrite + proxy fallback for strict `Accept` handling.

Apache can do pragmatic negotiation in config, but strict q-value behavior
(`q=0`, wildcard precedence, tie-breaking) is easier in a tiny app layer.
This recipe gives both patterns.

## Option A: static-first (pragmatic, not strict)

This is the minimal setup if your build emits both `index.html` and
`index.md`. It is **not RFC-9110-correct** — Apache rewrite conditions
can't express q-value precedence, specificity tiebreaks, or
`text/html;q=0` rejections. It also doesn't emit `Link: rel="alternate"`
to advertise the Markdown sibling via [RFC 8288](/reference).
Use Option B if you want spec-correct behavior for `q=0`, mixed
wildcards, or 406 on unsatisfiable `Accept`.

```apache
# .htaccess or vhost context
RewriteEngine On

# Skip real files/dirs and obvious assets.
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteCond %{REQUEST_URI} \.(?:css|js|mjs|map|png|jpe?g|webp|gif|svg|avif|ico|woff2?|ttf|otf|eot|xml|txt|json|pdf|mp4|webm|mp3|wav|ogg|zip)$ [NC]
RewriteRule ^ - [L]

# Ask for markdown? try .md first. The `;q=0` guard avoids serving
# Markdown to a client that explicitly rejected it via `text/markdown;q=0`.
# Two rules cover both common build layouts: sibling files (/about.md)
# and directory indexes (/about/index.md).
RewriteCond %{HTTP:Accept} "text/markdown" [NC]
RewriteCond %{HTTP:Accept} !"text/markdown\s*;\s*q\s*=\s*0" [NC]
RewriteCond %{DOCUMENT_ROOT}/$1.md -f
RewriteRule ^(.+?)/?$ /$1.md [L]

RewriteCond %{HTTP:Accept} "text/markdown" [NC]
RewriteCond %{HTTP:Accept} !"text/markdown\s*;\s*q\s*=\s*0" [NC]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}/index.md -f
RewriteRule ^(.+?)/?$ /$1/index.md [L]

# Default to HTML — same pair of layouts.
RewriteCond %{DOCUMENT_ROOT}/$1.html -f
RewriteRule ^(.+?)/?$ /$1.html [L]

RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}/index.html -f
RewriteRule ^(.+?)/?$ /$1/index.html [L]

# Root helpers.
RewriteRule ^$ /index.html [L]

# Ensure type + vary.
<FilesMatch "\\.md$">
  ForceType text/markdown
  Header set Content-Type "text/markdown; charset=utf-8"
</FilesMatch>
Header merge Vary Accept
```

Known limitations of Option A: no `406` when the client rejects both
representations, no `Link: rel="alternate"` advertising on HTML,
substring matching can misfire on exotic `Accept` headers.

## Option B: strict negotiation (recommended)

Keep Apache serving static files, but proxy only content routes to a tiny app
that parses `Accept` correctly and picks `text/html` vs `text/markdown`.

```apache
RewriteEngine On

# Leave assets and API routes alone.
RewriteCond %{REQUEST_URI} ^/(?:api/|_next/|assets/) [NC,OR]
RewriteCond %{REQUEST_URI} \.(?:css|js|png|jpe?g|webp|gif|svg|ico|woff2?|map|txt|xml|json)$ [NC]
RewriteRule ^ - [L]

# Proxy content requests to app server for strict negotiation.
RewriteRule ^ http://127.0.0.1:3000%{REQUEST_URI} [P,L]

ProxyPassReverse / http://127.0.0.1:3000/
Header merge Vary Accept
```

The app server should return:

- `200 text/markdown` when markdown is preferred + available
- `200 text/html` otherwise
- `406 Not Acceptable` when client rejects both
- `Link: </path/index.md>; rel="alternate"; type="text/markdown"` on HTML when sibling exists

## Verify

```bash
curl -sI https://your-site.com/about/
# Content-Type: text/html; charset=utf-8
# Vary: Accept

curl -sI -H "Accept: text/markdown" https://your-site.com/about/
# Content-Type: text/markdown; charset=utf-8
# Vary: Accept

curl -sI -H "Accept: application/pdf" https://your-site.com/about/
# HTTP/2 406
```