The bundles carried all thirteen translations at once and a page fetched them a third time as raw source; each page now loads only its own language. Alongside that: JavaScript is minified, the eight stylesheets are served as one file, the QR scanner is fetched after the app is up instead of on every visit, Inter ships once rather than five copies of the same file, and Font Awesome is subset to the 82 icons this app draws instead of all 2468. 1.85 MB across 43 requests becomes under 700 KB across 33. On mobile the page starts drawing in 1.6 s instead of 6.3 s and is usable in 4.5 s instead of 11 s. Pages also carry their text in the HTML now. Everything was drawn by JavaScript into an empty div, so crawlers saw correct metadata around nothing, and twelve of the thirteen language pages had never been shown to anyone. The documentation is published under /docs/ with a new FAQ, and unknown addresses return a real 404. Separately: the localized shells were served with the year-long immutable cache header meant for static assets, which pinned anyone who opened /de/ or /ru/ to that build. The header is fixed and the service worker refreshes what it cached. Claude-Session: https://claude.ai/code/session_014KjzTXxrhzYoDDWChYQ4u2
215 lines
7.0 KiB
ApacheConf
215 lines
7.0 KiB
ApacheConf
# SecureBit.chat - Apache Configuration
|
|
# Comprehensive caching configuration for forced updates
|
|
|
|
# ============================================
|
|
# MIME TYPES - MUST BE FIRST (before other rules)
|
|
# ============================================
|
|
# Critical: Set MIME types BEFORE any other rules to ensure correct Content-Type headers
|
|
<IfModule mod_mime.c>
|
|
# JavaScript modules - explicit order matters
|
|
AddType application/javascript .jsx
|
|
AddType application/javascript .mjs
|
|
AddType application/javascript .js
|
|
AddType application/json .json
|
|
|
|
# Fonts
|
|
AddType font/woff .woff
|
|
AddType font/woff2 .woff2
|
|
AddType application/font-woff .woff
|
|
AddType application/font-woff2 .woff2
|
|
|
|
# Service Worker
|
|
AddType application/manifest+json .webmanifest
|
|
</IfModule>
|
|
|
|
# Force Content-Type headers (override any server defaults)
|
|
<IfModule mod_headers.c>
|
|
# All JavaScript files including JSX - CRITICAL for ES modules
|
|
<FilesMatch "\.(js|mjs|jsx)$">
|
|
Header always set Content-Type "application/javascript; charset=utf-8"
|
|
</FilesMatch>
|
|
</IfModule>
|
|
|
|
# Enable mod_rewrite
|
|
<IfModule mod_rewrite.c>
|
|
RewriteEngine On
|
|
RewriteBase /
|
|
</IfModule>
|
|
|
|
# ============================================
|
|
# CRITICAL FILES - NO CACHING
|
|
# ============================================
|
|
|
|
# meta.json - versioning file (never cache)
|
|
<FilesMatch "meta\.json$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
|
Header set Pragma "no-cache"
|
|
Header set Expires "0"
|
|
Header set X-Content-Type-Options "nosniff"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# HTML files - always fresh
|
|
<FilesMatch "\.(html|htm)$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
|
Header set Pragma "no-cache"
|
|
Header set Expires "0"
|
|
# Remove ETag for validation
|
|
Header unset ETag
|
|
FileETag None
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# Service Worker - no cache
|
|
<FilesMatch "sw\.js$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
|
Header set Pragma "no-cache"
|
|
Header set Expires "0"
|
|
Header set Service-Worker-Allowed "/"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# manifest.json - no cache
|
|
<FilesMatch "manifest\.json$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
|
Header set Pragma "no-cache"
|
|
Header set Expires "0"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# ============================================
|
|
# STATIC RESOURCES - AGGRESSIVE CACHING
|
|
# ============================================
|
|
|
|
# JavaScript files in dist/ - no cache (for updates)
|
|
<FilesMatch "^dist/.*\.(js|mjs)$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "no-cache, no-store, must-revalidate, max-age=0"
|
|
Header set Pragma "no-cache"
|
|
Header set Expires "0"
|
|
Header set X-Content-Type-Options "nosniff"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# JavaScript files with hashes in other locations - long cache
|
|
<FilesMatch "\.(js|mjs)$">
|
|
<IfModule mod_headers.c>
|
|
# Files with hashes in name - cache for one year
|
|
Header set Cache-Control "public, max-age=31536000, immutable"
|
|
Header set X-Content-Type-Options "nosniff"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# The dictionary is imported by a bare path from a plain browser module, so nothing
|
|
# busts its cache. It must revalidate or translations freeze at whatever a visitor
|
|
# first loaded.
|
|
<FilesMatch "^(index|generated)\.js$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "no-cache, must-revalidate"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# CSS files - long cache
|
|
<FilesMatch "\.css$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "public, max-age=31536000, immutable"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# Images - long cache
|
|
<FilesMatch "\.(jpg|jpeg|png|gif|webp|svg|ico)$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "public, max-age=31536000, immutable"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# Fonts - long cache
|
|
<FilesMatch "\.(woff|woff2|ttf|otf|eot)$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "public, max-age=31536000, immutable"
|
|
Header set Access-Control-Allow-Origin "*"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# Audio/Video - long cache
|
|
<FilesMatch "\.(mp3|mp4|webm|ogg)$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "public, max-age=31536000, immutable"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# Crawler-facing files - short cache so search engines re-read them
|
|
<FilesMatch "^(robots\.txt|sitemap\.xml)$">
|
|
<IfModule mod_headers.c>
|
|
Header set Cache-Control "public, max-age=3600"
|
|
</IfModule>
|
|
</FilesMatch>
|
|
|
|
# ============================================
|
|
# SECURITY
|
|
# ============================================
|
|
|
|
# XSS Protection
|
|
<IfModule mod_headers.c>
|
|
Header set X-XSS-Protection "1; mode=block"
|
|
Header set X-Content-Type-Options "nosniff"
|
|
Header set Referrer-Policy "strict-origin-when-cross-origin"
|
|
Header set X-Frame-Options "DENY"
|
|
# Force HTTPS (2 years + preload) to close the first-visit SSL-strip window.
|
|
Header set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
|
|
# Restrict powerful features; camera + microphone kept for QR scanning and calls.
|
|
Header set Permissions-Policy "camera=(self), microphone=(self), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
|
|
</IfModule>
|
|
|
|
# Content Security Policy (frame-ancestors and report-uri only work in HTTP headers, not meta tags)
|
|
<IfModule mod_headers.c>
|
|
Header set Content-Security-Policy "frame-ancestors 'none'; report-uri /csp-report; report-to csp-endpoint;"
|
|
</IfModule>
|
|
|
|
# ============================================
|
|
# GZIP COMPRESSION
|
|
# ============================================
|
|
|
|
<IfModule mod_deflate.c>
|
|
# Compress text files
|
|
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json application/xml
|
|
|
|
# Compress fonts
|
|
AddOutputFilterByType DEFLATE font/woff font/woff2 application/font-woff application/font-woff2
|
|
</IfModule>
|
|
|
|
|
|
# ============================================
|
|
# CLOUDFLARE RULES
|
|
# ============================================
|
|
|
|
# Cloudflare can cache static files, but should not cache:
|
|
# - meta.json
|
|
# - index.html
|
|
# - sw.js
|
|
# - manifest.json
|
|
|
|
# These rules are applied at Cloudflare Page Rules level
|
|
# (see CLOUDFLARE_SETUP.md documentation)
|
|
|
|
# ============================================
|
|
# NOT-FOUND HANDLING
|
|
# ============================================
|
|
|
|
# There is no client-side routing, so an address that is not a file and not a
|
|
# directory is a mistake rather than a route. Rewriting those to index.html answered
|
|
# every typo with 200 OK and turned an infinite URL space into indexable pages.
|
|
ErrorDocument 404 /404.html
|
|
|
|
# ============================================
|
|
# LOGGING (optional)
|
|
# ============================================
|
|
|
|
# Uncomment for debugging
|
|
# LogLevel rewrite:trace3
|
|
|