What this post covers
I'm summarizing three things I learned improving landing-page performance. I leave out specific products and numbers and cover only general concepts that apply anywhere. The biggest lesson was pinning down the real cause of slow LCP with diagnosis — my intuition ("shrink the image") was wrong.
What I worked on today
| Task | What I wanted | What I did | Result |
|---|---|---|---|
| LCP diagnose/improve | I wanted to fix slow LCP | Confirmed render delay via breakdown → server-ified the surface | Hydration, not the image, was the bottleneck |
| Scroll motion | I wanted impressive landing motion | Flashy scroll-dependent production → settled on entrance stagger reveal | Reduced accessibility/bounce risk |
| FAQ structuring | Expose the FAQ as a rich search result too | FAQ accordion + structured data 1:1 with the screen | Policy-compliant, rich-result candidate |
1. When LCP is slow but the image is small, suspect hydration
The situation. The landing's LCP (the moment the largest content is painted) was slow. Intuitively "shrink the hero image" came to mind, but the image was already small. I had to confirm the real cause through diagnosis.
The core concept. LCP can be split into four segments (TTFB / load delay / load time / render delay). If the image is small and the server response is normal yet render delay is large, the culprit is not download but the fact that the LCP element is inside a client component boundary, so it only paints once hydration finishes. Hydration is the process of later attaching events with JavaScript to the static HTML the server produced, bringing it to life — and if the LCP element is trapped inside it, the paint is held hostage by JavaScript execution. If the main thread is idle (TBT≈0) but only LCP is slow, this is highly likely the case.
What I did. I confirmed via the breakdown that render delay was the bottleneck, and set the target as removing the landing's client JS, not the image. I converted the hero and top story sections to server components + CSS animation, and pushed heavy context providers (auth/query/motion/service worker) down into an app-surface-only layout, dropping them from the static landing chunk entirely. As a side finding, I also learned the framework auto-preloads the <img> in the initial HTML, so I split the brand mark — which had been displaying a large source at small size — into a dedicated small asset.
The lesson. When LCP is slow but TBT is 0 and the image is small, look at element render delay — an LCP element inside a client boundary lets hydration push the paint. The prescription is removing that surface's client JS, not the asset. If the score plateaus because of the measurement simulation's lower bound, stopping is the right call (avoid over-investment).
→ LCP element render delay — hydration pushes the paint
2. Restrained entrance stagger over flashy scroll motion
The situation. Trying to make the landing section motion impressive, I pushed a flashy scroll-dependent production (grabbing the scroll and progressing step by step), but the feedback was repeatedly "it's too much."
The core concept. Scroll-jacking is a technique that intercepts the user's scroll and grabs the screen at will; it induces motion sickness and bounce, and accessibility guidance (Motion Sensitivity) treats it as a violation. The alternative is a stagger reveal — a restrained production where elements appear with slight time offsets as they enter the viewport, without grabbing the scroll.
What I did. I removed the scroller itself and settled on a viewport-entrance stagger reveal. For reduced-motion users, I put a gate to render the final state immediately.
The lesson. A short entrance stagger reveal is often the universal answer over flashy scroll-dependent motion.
3. Structured data is 1:1 with the screen
The situation. While adding an FAQ, I also aimed for a rich search result (FAQ snippet). But if the on-screen accordion and the structured data fed to search engines diverge, it becomes a policy violation.
The core concept. Structured data (JSON-LD) is machine-facing information that tells search engines "this is an FAQ." The FAQPage type must be 1:1 with the Q&A actually visible on screen (visible-content parity). Sneaking in something different is a penalty.
What I did. I added the FAQ as a native <details> accordion and built the structured data from the same items array as the screen (pure builder + a pattern that omits the script when there are no items).
The lesson. FAQ structured data must be 1:1 with the on-screen Q&A.