What this post covers
To improve LCP, transfer size, and CLS on an auth-gated app home, how you measured matters as much as "what you fixed." On the same day I also touched SEO (JSON-LD) on the public pricing page and decorative-motion accessibility.
Day summary
| Task | What I wanted | What I did | Result |
|---|---|---|---|
| Measurement | Compare before/after with a trustworthy protocol | compare only by the median of 3 reloads; exclude single outlier runs | comparison unshaken by dev-server trace variance |
| Prefetch + CLS | Data arrives fast but without layout jump | align the server prefetch query string with the client provider default + reserve skeleton height | cache hit kept, skeleton→content CLS suppressed |
| Carousel | Mount images only for in-viewport slides | mount images only for in-view slides | unnecessary transfer cut |
| SEO | Inject JSON-LD safely on the server | inject the JSON-LD graph in the server page.tsx; sanitize < | crawler-friendliness and consistency secured |
| A11y | Disable decorative ripple under prefers-reduced-motion | disable ripple under prefers-reduced-motion | decorative motion removed for reduced-motion users |
1. Lab measurement protocol
Background (concepts)
- LCP (Largest Contentful Paint): the time until the largest content on screen is painted.
- Trace outlier: a single dev-server Performance trace has jittery numbers — an anomaly you shouldn't conclude from a single value.
What the situation was
Taking a single Chrome Performance trace gave jittery numbers. A single dev-server run often has outliers. I wanted to compare before/after with a trustworthy protocol.
Core work
Compare before/after only by the median of 3 reloads of the Chrome Performance trace. Exclude single outlier runs from conclusions.
Takeaways
- Lighthouse SEO/A11y scores and trace LCP ms are different instrumentations.
- Next step: re-measure with the same protocol on a production
build && start.
→ Lab performance measurement variance and comparison protocol · Lighthouse measurement accuracy
2. SSR prefetch and CLS
Background (concepts)
- CLS (Cumulative Layout Shift): the degree of layout being pushed during loading. It worsens when skeleton height differs from actual content.
- Prefetch: fetching data ahead on the server and putting it in the client cache.
What the situation was
I prefetched the list API via SSR, but if the client query key differs by even one character, it's a cache miss → the skeleton → content transition becomes noticeable and CLS can actually get worse. The goal was data arriving fast but without layout jump.
Core work
When prefetching and hydrating the list API on the server, align the query string with the client provider default. The skeleton suppresses CLS by sharing the page-size row count and body minHeight. Suppressed skeleton→content CLS while keeping the cache hit.
Takeaways
- When prefetch fetches data fast, the skeleton→content transition becomes noticeable and CLS can worsen — treat prefetch, skeleton skip, and height reservation as one set.
→ SSR prefetch and client query-cache hydrate · CLS and skeleton layout reservation
3. Carousel images
Background (concepts)
- Out-of-viewport image deferral: mounting all slide images makes transfer large. The lazy attribute alone may not reduce "the number of images mounted in the DOM."
What the situation was
Mounting all slide images makes transfer unnecessarily large. I wanted to mount images only for in-viewport slides.
Core work
The carousel mounts images only for in-view slides. The lazy attribute alone may not reduce "the number of images mounted in the DOM." Cut unnecessary transfer.
Takeaways
- For carousels, not mounting out-of-viewport slide images cuts transfer more directly than lazy loading.
→ Carousel out-of-viewport image deferral
4. JSON-LD
Background (concepts)
- JSON-LD: a structured-data (JSON) format placed in a
<script>so search engines understand the page content. Used for SEO.
What the situation was
The public pricing page needed structured data, and putting it in the client only is disadvantageous for crawlers and consistency. I wanted to inject JSON-LD safely on the server.
Core work
Inject the JSON-LD graph into the public pricing page from the server page.tsx. Sanitize <. Secured crawler-friendliness and consistency.
Takeaways
- JSON-LD should be separated from client UI and injected as a sanitized script in the server
page.tsx.
→ JSON-LD structured-data server injection
5. Decorative-motion accessibility
Background (concepts)
prefers-reduced-motion: a media query indicating whether the user prefers reduced motion. Decorative motion is disabled here.
What the situation was
Decorative motion like ripple was being shown as-is even to prefers-reduced-motion users.
Core work
Disabled ripple under prefers-reduced-motion. Removed decorative motion for reduced-motion users.
Takeaways
animate-pulsehas a different purpose than a spread ripple — a dedicated@keyframes+ stagger fits.