Skip to content

Deployment verification – CSP, CORS, and snapshot viewer

This guide walks through verifying a live deployment of the HealthArchive frontend + backend with a focus on:

  • Security headers and CSP on the frontend.
  • CORS between the frontend and backend.
  • The snapshot viewer iframe loading raw HTML from the backend.

Shared-VPS ownership note:

  • This guide is canonical for HealthArchive frontend verification steps.
  • Shared host topology, ingress ownership, and other cross-project VPS facts are canonical in /home/jer/repos/vps/platform-ops.
  • Boundary reference: /home/jer/repos/vps/platform-ops/docs/standards/PLAT-009-shared-vps-documentation-boundary.md

Current deployment model (important context)

  • Single backend API: https://api.healtharchive.ca
  • Canonical frontend domain: https://healtharchive.ca
  • Frontend alias: https://www.healtharchive.ca (redirects to apex)
  • Replay domain: https://replay.healtharchive.ca
  • Target frontend runtime: VPS-hosted Next.js container behind host Caddy
  • Strict backend CORS allowlist (current choice):
  • https://healtharchive.ca
  • https://www.healtharchive.ca
  • https://replay.healtharchive.ca

1. Confirm frontend runtime environment

For the deployed frontend runtime, set:

NEXT_PUBLIC_API_BASE_URL=https://api.healtharchive.ca
NEXT_PUBLIC_REPLAY_BASE_URL=https://replay.healtharchive.ca

Optional diagnostics envs should normally remain disabled in production:

NEXT_PUBLIC_SHOW_API_HEALTH_BANNER=true
NEXT_PUBLIC_LOG_API_HEALTH_FAILURE=true
NEXT_PUBLIC_SHOW_API_BASE_HINT=true

2. Verify frontend security headers & CSP (report-only)

Current posture note:

  • Production currently sends Content-Security-Policy-Report-Only, not an enforcing Content-Security-Policy header.
  • No dedicated CSP report collector is configured yet, so header inspection and browser console warnings are the practical verification signals today.

Do these checks on the production frontend:

  • https://healtharchive.ca/archive

In Chrome or Firefox DevTools:

  1. Open the page.
  2. Open DevTools → Network and reload.
  3. Click the main document request (usually the first row with path /archive).
  4. In Headers → Response headers, confirm you see:
  5. Referrer-Policy: strict-origin-when-cross-origin
  6. X-Content-Type-Options: nosniff
  7. X-Frame-Options: SAMEORIGIN
  8. Permissions-Policy: geolocation=(), microphone=(), camera=()
  9. Content-Security-Policy-Report-Only: ... with a value containing:
    • connect-src 'self' https://api.healtharchive.ca;
    • frame-src 'self' https://api.healtharchive.ca https://replay.healtharchive.ca;
  10. No enforcing Content-Security-Policy header on the same response.

  11. Check the Console tab for Content-Security-Policy-Report-Only warnings. Some warnings are expected while the CSP is report-only and being tuned. Because there is no separate report collector yet, these warnings are part of the current tuning loop.


3. Verify the UI is using the live API (and CORS is correct)

3.1 Browser checks

  1. Open https://healtharchive.ca/archive.
  2. Confirm the page does not show the offline fallback notice (for example, “Live API unavailable”).
  3. (Optional, if previews are enabled) Confirm the “Browse archived sites” cards show preview images. If they do not:
  4. confirm HEALTHARCHIVE_REPLAY_PREVIEW_DIR is set on the backend host, and
  5. confirm /api/sources includes entryPreviewUrl values.
  6. In DevTools → Network, look for requests to https://api.healtharchive.ca like:
  7. /api/health
  8. /api/sources
  9. /api/search?...
  10. Click one of those requests and confirm in Response headers:
  11. Access-Control-Allow-Origin matches the frontend origin
  12. Vary: Origin is present

Some API calls may happen server-side in Next.js. To verify CORS directly from your terminal:

curl -i \
  -H "Origin: https://healtharchive.ca" \
  https://api.healtharchive.ca/api/health | grep -iE "HTTP/|access-control|vary"

You should see HTTP/2 200, Access-Control-Allow-Origin: https://healtharchive.ca, and Vary: Origin.


4. Snapshot viewer + replay verification

This verifies that the snapshot viewer iframe can load replay content from replay.healtharchive.ca (preferred), and that raw HTML from the API remains available as a fallback/debug path.

  1. Pick a known snapshot ID that exists in the backend (at least one should exist in production).
  2. Open (metadata + embedded viewer):

  3. https://healtharchive.ca/snapshot/<id>

  4. Confirm:

  5. The page loads snapshot metadata (title, capture date, source, URL).
  6. If replay is configured, the page displays “Archived content · served from https://replay.healtharchive.ca/... via the replay service.”
  7. The embedded iframe renders the archived page (CSS/JS/images should load where captured).
  8. The page still offers a “Raw HTML” link to https://api.healtharchive.ca/api/snapshots/raw/<id>.

  9. Open the full-screen browse view:

  10. https://healtharchive.ca/browse/<id>

Confirm it loads the same replay content with a persistent HealthArchive banner/controls above the iframe.

  1. (Optional, if a source has multiple editions) Verify edition switching:
  2. In /browse/<id> (or /snapshot/<id>), look for an “Edition” / “Switch edition” dropdown.
  3. Click a few links inside the replay iframe to navigate to a deeper page.
  4. Switch the dropdown to another job/edition and confirm:

    • the viewer attempts to keep you on the same original URL in the selected edition, and
    • if that page wasn’t captured in the selected edition, it falls back to the edition’s entry page with a short notice.
  5. Verify “Browse archived sites” search shortcuts:

  6. On https://healtharchive.ca/archive, click Search on one of the “Browse archived sites” cards.
  7. Confirm:

    • the Source filter updates to that source (not “All sources”), and
    • the page scrolls to the Search card without the fixed header covering its title.
  8. (Optional) Verify the direct replay banner (non-framed replay):

  9. From a snapshot page, click Replay ↗ to open the replay service in a new tab.
  10. Confirm the replay banner:
    • shows a compact snapshot summary,
    • includes links like View diff / Raw HTML / Metadata JSON, and
    • ← HealthArchive.ca returns to the specific /snapshot/<id> page (not the homepage).

5. Optional: allow additional preview origins

If you later introduce a separate staging frontend origin:

  1. Add that origin to HEALTHARCHIVE_CORS_ORIGINS on the backend.
  2. Ensure connect-src and frame-src continue to allow the API and replay hosts.
  3. Redeploy backend + frontend and repeat the checks above.