Files
Waters ChurchandClaude Fable 5.1 78cd0cc550 Plan Your Visit landing page for visit.waterschurch.org
Next.js 16 static export + PHP 8 API for cPanel/Apache hosting.

- Location-aware campus finder (IP estimate, optional precise location, ZIP search)
- Three-step Plan Your Visit form with separate email/SMS consent
- PHP API: validation, anti-spam, rate limiting, SQLite lead store,
  Mailchimp and Sinch Engage adapters, idempotent retries via cron
- Attribution capture (utm_*, gclid/gbraid/wbraid, fbclid, ttclid, twclid) and
  consent-aware analytics hooks
- Vitest and PHP test suites, cPanel deployment script and docs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 13:22:26 -04:00

87 lines
4.3 KiB
Plaintext

# ============================================================================
# visit.waterschurch.org — Apache configuration for the subdomain document root
# Copied to out/.htaccess by `npm run build` (scripts/postbuild.mjs).
# ============================================================================
Options -Indexes -MultiViews
DirectoryIndex index.html
ErrorDocument 404 /404.html
# cPanel's default 403 page does not exist in this document root; show our branded page instead (status stays 403).
ErrorDocument 403 /404.html
# --- Private configuration directory for the PHP API -----------------------
# Uncomment and set to the absolute path of the folder holding your .env file
# (OUTSIDE the document root). If omitted, the API looks for
# <parent of document root>/visit-private, e.g. /home/USER/visit-private.
# SetEnv VISIT_PRIVATE_DIR /home/USER/visit-private
<IfModule mod_rewrite.c>
RewriteEngine On
# Force HTTPS and the canonical non-www host
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} ^www\.visit\.waterschurch\.org$ [NC]
RewriteRule ^(.*)$ https://visit.waterschurch.org/$1 [R=301,L]
# Never serve dot-files (except the ACME challenge folder used for SSL issuance)
RewriteCond %{REQUEST_URI} !^/\.well-known/
RewriteRule (^|/)\.(?!well-known) - [F,L]
# Next.js static export: serve /path as /path.html when the file exists
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/$1.html -f
RewriteRule ^(.+?)/?$ /$1.html [L]
# Strip trailing slash on non-directories
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)/$ /$1 [R=301,L]
</IfModule>
# --- Security headers -------------------------------------------------------
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(self), camera=(), microphone=(), payment=()"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" "expr=%{HTTPS} == 'on'"
# CSP is report-only until Waters Church confirms its tag/pixel list. See docs/DEPLOY-CPANEL.md.
Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com https://connect.facebook.net https://analytics.tiktok.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://www.google-analytics.com https://analytics.google.com https://www.facebook.com https://analytics.tiktok.com https://challenges.cloudflare.com; frame-src https://challenges.cloudflare.com; base-uri 'self'; form-action 'self' sms:; frame-ancestors 'self'"
# Immutable hashed assets
<FilesMatch "\.(js|css|woff2|avif|webp|jpg|jpeg|png|svg|ico)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# HTML and text files: always revalidate
<FilesMatch "\.(html|xml|txt|json)$">
Header set Cache-Control "public, max-age=0, must-revalidate"
</FilesMatch>
</IfModule>
# --- Compression -----------------------------------------------------------
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml font/woff2
</IfModule>
<IfModule mod_brotli.c>
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml
</IfModule>
# --- MIME types some cPanel builds miss -------------------------------------
AddType image/avif .avif
AddType image/webp .webp
AddType font/woff2 .woff2
# --- Expires fallback (when mod_headers is unavailable) ---------------------
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/avif "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>