Files
2026-02-10 22:37:29 -05:00

145 lines
8.1 KiB
Markdown

## Context
The production site is built as static output and served via nginx (Docker image). Lighthouse runs (mobile/desktop, light/dark/high-contrast) report repeated misses across Performance, SEO, Best Practices, and (in dark theme) Accessibility.
Notable characteristics from the current system:
- SEO is mostly static-file driven (`site/public/robots.txt`, sitemap output) and must be correct for the deployed domain.
- Global styling is served from `site/public/styles/global.css`, which bypasses typical bundler optimizations (minification, pruning) unless explicitly piped through the build.
- A service worker exists (`site/public/sw.js`) and is involved in caching shell assets; cache behavior must not cause stale critical assets after deploy.
- The site uses environment-driven configuration and has multiple theme modes (light/dark/high-contrast) that must remain visually consistent and accessible.
The goal is a deterministic, repeatable Lighthouse gate that can hit 100s in a clean run environment.
## Goals / Non-Goals
**Goals:**
- Reach 100 Lighthouse scores (Performance, Accessibility, Best Practices, SEO) for the defined set of URLs, form factors, and theme variants.
- Fix SEO correctness issues that are unambiguous (e.g., `robots.txt` sitemap URL must be absolute for the production domain).
- Ensure all user-visible navigational CTAs and modal actions are implemented as crawlable anchors (real `href`s) when they represent links.
- Eliminate dark theme contrast regressions by tightening theme token choices and/or component-level styles.
- Reduce initial render blocking work (fonts + CSS) to improve mobile performance.
- Reduce CSS overhead by ensuring the global stylesheet is minified and sized appropriately for production.
- Make Lighthouse runs deterministic (clean Chrome profile, fixed throttling/UA) and enforce a quality gate in CI or a scripted check.
**Non-Goals:**
- Redesigning the visual aesthetic.
- Building a general-purpose CSS framework migration.
- Perfect scores when Lighthouse is run with extensions or non-deterministic settings.
- Solving third-party performance/caching issues by weakening metrics (the goal is to reduce third-party dependence where it blocks 100s, or scope the gate to first-party pages that can be made deterministic).
## Decisions
### 1) Define the Lighthouse gate upfront
Decision: treat 100s as a contract under a specific, documented run configuration.
- URLs: start with the production home page and any key landing/content pages that are stable and representative.
- Variants: mobile/desktop x light/dark/high-contrast.
- Run environment: headless Chrome in CI (no extensions), consistent throttling, at least N runs per variant with median selection.
Rationale: Lighthouse scores are otherwise too sensitive to environment noise to serve as a reliable build gate.
Alternative considered: manual Lighthouse checks. Rejected because it does not prevent regressions.
### 2) Fix SEO static outputs in `public/`
Decision: keep `robots.txt` and sitemap-related outputs as static files, but ensure correctness for production.
- Make the sitemap reference in `site/public/robots.txt` absolute (not a relative path).
Rationale: This is required for Lighthouse SEO audits and is the least invasive change.
Alternative considered: generate robots dynamically. Rejected as unnecessary complexity.
### 3) Enforce crawlable links in modal/CTA surfaces
Decision: if an element semantically represents navigation (internal or outbound), it must be an `<a href="...">`.
- For modal components, ensure the CTA is rendered as an anchor with a real destination URL when appropriate.
- If the interaction is not navigation (e.g., close modal), use a `<button>` and ensure it is accessible.
Rationale: Lighthouse flags anchors without `href` as non-crawlable. Using correct semantics also improves accessibility.
Alternative considered: keep `<a>` without `href` and add JS click handlers. Rejected (SEO + a11y regression).
### 4) Process `global.css` through a production pipeline
Decision: stop serving the main global stylesheet as an unprocessed static asset.
Options:
- Import `global.css` from the Astro entry/layout so Vite can minify it for production builds.
- If the site intentionally keeps a standalone global CSS file, add an explicit build step to minify it and (optionally) run a basic unused rule trimming strategy.
Rationale: Lighthouse currently flags unminified CSS and unused CSS rules; bundling/minification is the most straightforward fix.
Alternative considered: leave it in `public/` and accept lower scores. Rejected (goal is 100s).
### 5) Font loading strategy optimized for mobile performance
Decision: make font loading non-blocking and deterministic.
- Prefer self-hosted fonts or Astro's recommended font strategy.
- Use `font-display: swap` and consider `preload` for critical weights.
- Avoid unnecessary font variants.
Rationale: render-blocking and late text rendering negatively impact Performance and perceived load.
Alternative considered: keep current remote font loading. Rejected if it remains render-blocking.
### 6) Theme token constraints for contrast
Decision: fix contrast failures at the token level first, with targeted overrides only when needed.
- Identify failing color pairs (foreground/background) in dark mode.
- Update tokens in `site/public/styles/global.css` so common surfaces always meet contrast expectations.
Rationale: token-level fixes prevent regressions across multiple components.
Alternative considered: per-component overrides only. Rejected as brittle.
### 7) CSP / Best Practices compliance
Decision: address Lighthouse Best Practices issues by aligning nginx headers with modern expectations without breaking the site.
- Keep security headers in `deploy/nginx.conf`.
- If inline scripts/styles exist, prefer refactoring to external files to avoid unsafe CSP allowances; otherwise use a nonce/hash strategy.
Rationale: strong CSP improves security posture and can satisfy Lighthouse findings, but must be implemented in a way compatible with the build output.
Alternative considered: disable CSP to appease some checks. Rejected.
### 8) Images: reduce third-party variability
Decision: prioritize first-party control of above-the-fold images.
- Use Astro image tooling for locally hosted images.
- For third-party images that block 100s (e.g., external thumbnails), prefer either self-hosting (if allowed) or avoid rendering them above the fold during initial load.
Rationale: image delivery and caching audits can be impossible to satisfy if key images are served from third parties with unknown caching/format behavior.
Alternative considered: ignore image findings. Rejected if they prevent the 100 gate.
## Risks / Trade-offs
- [Lighthouse instability] Scores vary across runs/environments -> Mitigation: lock run configuration and use a clean profile in CI.
- [Build pipeline change risk] Moving `global.css` into bundling may change selector precedence -> Mitigation: diff visual output across themes and add a lightweight snapshot/DOM test where possible.
- [CSP breakage] Tight CSP can block analytics or inline scripts -> Mitigation: inventory scripts, move to external, or add nonce/hash strategy.
- [Third-party dependencies] External media assets may prevent 100 -> Mitigation: reduce third-party assets on gated pages or self-host where permissible.
- [Service worker caching] SW can serve stale assets after deploy -> Mitigation: version critical assets and disable caching for SW + critical CSS, ensure SW update flow.
## Migration Plan
- Implement fixes behind minimal, incremental commits (SEO correctness, crawlable anchors, contrast tokens, CSS pipeline/font loading, CSP).
- Deploy with cache-busting in place so critical style updates reach clients quickly.
- Rollback: revert to a known-good image tag/digest and confirm SW cache versioning does not pin stale assets.
## Open Questions
- Which URLs are included in the Lighthouse gate (home only vs additional pages)?
- Are 100 scores required even when pages include third-party embeds/thumbnails, or should we scope the gate to pages that can be made first-party deterministic?
- Should schema.org structured data be implemented for all pages or only key landing/content types?