Options -MultiViews -Indexes DirectoryIndex index.html # --------------------------------------------------------------------------- # Routing # --------------------------------------------------------------------------- RewriteEngine On # Serve the prerendered pages produced by prerender.js. # /de/technical is a directory, not a file, so the SPA fallback below would # otherwise swallow it and ship the empty shell to crawlers. RewriteCond %{REQUEST_FILENAME} -d RewriteCond %{REQUEST_FILENAME}/index.html -f RewriteRule ^(.+?)/?$ $1/index.html [L] # SPA fallback, only for paths that are neither a file nor a directory. RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.html [QSA,L] # --------------------------------------------------------------------------- # Security headers # --------------------------------------------------------------------------- # script-src keeps 'unsafe-inline' on purpose: Vite injects an inline # modulepreload polyfill, and index.html carries four inline JSON-LD blocks # that browsers also evaluate against script-src. Removing it silently breaks # both the app and the structured data. # style-src likewise: React sets inline style attributes (--reveal-delay). Header always set Content-Security-Policy "default-src 'self'; base-uri 'self'; font-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://api.sascha-bach.de; object-src 'none'; frame-ancestors 'none'; form-action 'self' mailto:; upgrade-insecure-requests" # No 'preload' yet - that directive is practically irreversible and should # only be added once every subdomain is permanently HTTPS. Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains" Header always set Referrer-Policy "strict-origin-when-cross-origin" Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "DENY" Header always set Cross-Origin-Opener-Policy "same-origin" Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), browsing-topics=()" # Agent discovery documents (ported from the former vercel.json). Header always set Link "; rel=\"alternate\"; type=\"text/markdown\", ; rel=\"alternate\"; type=\"text/markdown\", ; rel=\"alternate\"; type=\"text/markdown\", ; rel=\"agents\"; type=\"application/json\", ; rel=\"agent-card\"; type=\"application/json\", ; rel=\"service-desc\"; type=\"application/json\", ; rel=\"service-desc\"; type=\"application/json\"" # --------------------------------------------------------------------------- # Keep dotfiles unreachable (.env, .git remnants, editor leftovers). # This matches the final filename only, so /.well-known/agent-card.json stays # reachable - its basename does not start with a dot. # --------------------------------------------------------------------------- Require all denied