Skip to content

Improve Lighthouse performance score to 100 #25

Description

@dinooo13

Problem

The Lighthouse performance score for fmeyer.dev is currently sitting just below 80, while Accessibility, Best Practices, and SEO all score 100. The goal is to bring Performance up to 100 as well, so the site has a perfect Lighthouse profile across all four categories.

Current state

  • Performance: < 80
  • Accessibility: 100
  • Best Practices: 100
  • SEO: 100

Goal

Performance: 100 (alongside the other three already at 100).

Areas to investigate

Run a fresh Lighthouse audit (mobile + desktop) against the production site and the local pnpm generate + pnpm preview output, then address whichever of the following metrics are dragging the score down:

  • LCP (Largest Contentful Paint)
    • Hero images in public/hero/random-*.avif — verify dimensions, preload the image actually rendered, and ensure <NuxtImg> / <NuxtPicture> is used with explicit width/height and loading="eager" + fetchpriority="high" for the hero.
    • Profile photo in public/profile/ — same treatment if it appears above the fold.
  • CLS (Cumulative Layout Shift)
    • Make sure every image (hero, lab cards, talk cards) declares width/height or aspect-ratio.
    • Check that webfonts (if any) don't cause layout shifts; use font-display: swap and preload critical fonts.
  • TBT / INP (JavaScript cost)
    • Audit motion-v usage in app/components/landing/* — defer non-critical animations until after hydration / use IntersectionObserver so off-screen sections don't run on load.
    • Check the bundle for unused @nuxt/ui components and confirm tree-shaking is effective.
    • Consider nuxt.config.ts experimental.payloadExtraction and route rules for static pages.
  • Render-blocking resources
    • Inline critical CSS / verify Nuxt is doing so on prerendered pages.
    • Remove or defer any third-party scripts.
  • Caching / compression
    • Confirm the GitHub Pages (or whichever) deploy serves Brotli/Gzip and long-lived cache headers for hashed assets.
  • Image format & sizing
    • Verify @nuxt/image is generating responsive srcsets and that the served image isn't oversized for the viewport.
    • Convert any remaining non-AVIF/WebP images.

Acceptance criteria

  • Lighthouse Performance score is 100 on both mobile and desktop for the homepage (/).
  • Lighthouse Performance score is 100 (or 95+ minimum) on /labs, /labs/[slug], /speaking, and /speaking/[slug].
  • Accessibility, Best Practices, and SEO remain at 100.
  • No regressions in visual design or animations (entrance motion still feels intentional, just cheaper).
  • Include before/after Lighthouse screenshots in the PR description.

Out of scope

  • Redesigns or content changes.
  • Switching frameworks or hosting.

Activity

  1. dinooo13 commented on May 7, 2026

    @dinooo13
    OwnerAuthor

    Clarifying Questions

    I looked through the codebase before drafting these. Check the boxes that apply — no need to write anything unless an "Other" option fits better.


    Fonts

    app/assets/css/main.css declares Public Sans and Instrument Serif via Tailwind theme variables, but there are no @font-face rules, no <link rel="preload">, and no font files in public/. It's unclear where these fonts actually come from.

    • Where are Public Sans and Instrument Serif loaded from?
      • Google Fonts CDN (added somewhere not visible in the checked-in code, e.g. a .env or host config)
      • @nuxt/ui / Tailwind bundles or downloads them automatically
      • They are not loaded — the site falls back to system fonts silently
      • Other (please comment below)

    Deployment & hosting

    • Where is the production site hosted?

      • GitHub Pages
      • Netlify
      • Vercel
      • Cloudflare Pages
      • Other (please comment below)
    • Does the host already serve Brotli/Gzip compression and long-lived Cache-Control headers for hashed static assets?

      • Yes — handled automatically by the host
      • No / not sure — needs investigation

    Current failing metrics

    A score below 80 can have many causes. Knowing which Core Web Vitals are failing avoids optimising the wrong thing.

    • Which metrics are currently dragging performance below 80? (check all that apply)

      • LCP (Largest Contentful Paint) — the hero image or text is slow to appear
      • CLS (Cumulative Layout Shift) — elements shift after load
      • TBT (Total Blocking Time) — long JS tasks blocking the main thread
      • INP (Interaction to Next Paint) — slow response to user input
      • FCP (First Contentful Paint) — slow initial paint
      • Haven't run a detailed audit yet — unknown
    • Which device profile matters most?

      • Mobile (throttled CPU + network — hardest to pass)
      • Desktop
      • Both equally

    Images

    Hero.vue uses <NuxtImg> with loading="eager" for the profile photo but no fetchpriority="high". The hero background images (/hero/random-*.avif) are rendered via a plain <img> tag with no preload hint. Lab/talk card images lack explicit width/height.

    • Should fetchpriority="high" be added to the profile photo in Hero.vue?

      • Yes
      • No — it's small (72 × 72 px) and unlikely to be the LCP element
    • Should a <link rel="preload"> be added for the hero background AVIF that is rendered on load?

      • Yes — preload the first/default hero image
      • No — the hero background is decorative and not the LCP element
      • Not sure — depends on what Lighthouse flags as the LCP element
    • Should the JPEG profile photo (fabian-meyer-portrait.jpg) be converted to AVIF/WebP?

      • Yes, convert it
      • No — @nuxt/image handles format negotiation at serve time, so it's fine as JPEG on disk
      • Only if Lighthouse flags it explicitly
    • Are there any lab or talk content images in public/ that are large or unoptimised?

      • Yes, some images need attention
      • No, all are already well-optimised
      • Not sure

    Animations (motion-v)

    motion-v is used in all five landing components. Hero animations run immediately on load (blur/scale, staggered 0.1 s–0.5 s delays). Below-fold components use while-in-view with once: true.

    • How important are the Hero entrance animations (blur + scale on initial load) to the design?

      • Critical — keep them exactly as they are, even if they cost a few points
      • Important — reduce/defer where possible, but don't remove them
      • Flexible — can simplify or remove them if they're measurably hurting TBT/INP
    • Are the below-fold while-in-view animations (Focus, WorkExperience, LabsTeaser, SpeakingTeaser) worth keeping?

      • Yes, keep all of them
      • Keep them but defer/lazy-load the motion-v runtime until after hydration if possible
      • Fine to remove if they're contributing to bundle weight

    Scope & workflow

    • What should the first step be?

      • Run a full Lighthouse audit (mobile + desktop) first and report findings before any code changes
      • Skip straight to the obvious quick wins (fetchpriority, width/height attrs, font-display) and audit after
      • Run the audit and implement fixes together in one PR
    • For inner pages (/labs, /speaking, /labs/[slug], /speaking/[slug]): is 95+ acceptable if 100 isn't feasible on those routes?

      • Yes, 95+ is fine for non-homepage routes
      • No — must hit 100 everywhere
      • Only the homepage (/) matters for this issue; inner pages are out of scope for now

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions