# Static site on shared hosting / cPanel (Apache 2.4). Copy to the docroot as .htaccess.
# Tested 2026-10 on Apache 2.4 (macOS httpd): every rule below returned the expected status.
# Edit the dev-file list; nothing else needs project values.
Options -Indexes
DirectoryIndex index.html
ErrorDocument 404 /404.html

<IfModule mod_rewrite.c>
  RewriteEngine On
  # HTTPS (also behind a proxy that sets X-Forwarded-Proto). AutoSSL validates over plain http:
  # redirecting /.well-known/ breaks certificate renewal.
  RewriteCond %{REQUEST_URI} !^/\.well-known/
  RewriteCond %{HTTPS} !=on
  RewriteCond %{HTTP:X-Forwarded-Proto} !=https
  RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
  # /index.html -> / (one canonical URL)
  RewriteCond %{THE_REQUEST} \s/index\.html[\s?]
  RewriteRule ^index\.html$ / [L,R=301]
  # Dotfiles and dot-dirs (.git, .env, .DS_Store, .htaccess) are never served, except .well-known
  RewriteRule (^|/)\.(?!well-known/) - [R=404,L]
  # Dev-only files that may get uploaded by accident: edit this list
  RewriteRule ^(README\.md|PLACEHOLDERS\.md|vercel\.json|netlify\.toml|package(-lock)?\.json)$ - [R=404,L]
</IfModule>
# Belt and braces if mod_rewrite is off. Note <FilesMatch "^\."> would NOT cover .git/HEAD (a directory).
RedirectMatch 404 /\.(?!well-known)

<IfModule mod_headers.c>
  Header always set X-Content-Type-Options "nosniff"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
  # HSTS only over https, and without includeSubDomains on a subdomain you don't own the apex of
  Header always set Strict-Transport-Security "max-age=31536000" "expr=%{HTTPS} == 'on'"

  # HTML always revalidates. css/js/woff2 cached for a year: version them in the HTML (styles.css?v=...)
  # and change the value on every deploy, or returning visitors keep the old files.
  <FilesMatch "\.html$">
    Header set Cache-Control "no-cache"
  </FilesMatch>
  <FilesMatch "\.(css|js|woff2)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
  <FilesMatch "\.(webp|avif|png|jpe?g|svg|ico|pdf|webmanifest)$">
    Header set Cache-Control "public, max-age=2592000"
  </FilesMatch>
</IfModule>

<IfModule mod_mime.c>
  AddType application/manifest+json .webmanifest
  AddType font/woff2 .woff2
  AddType image/webp .webp
  AddType image/avif .avif
</IfModule>

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml application/xml application/javascript text/javascript application/json application/manifest+json image/svg+xml
</IfModule>
