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

8.1 KiB

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 hrefs) 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.

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?