Skip to content

fix: reduce CLS on home page#782

Closed
franklinjavier wants to merge 3 commits intoTanStack:mainfrom
franklinjavier:franklinjavier/fix-home-cls
Closed

fix: reduce CLS on home page#782
franklinjavier wants to merge 3 commits intoTanStack:mainfrom
franklinjavier:franklinjavier/fix-home-cls

Conversation

@franklinjavier
Copy link

@franklinjavier franklinjavier commented Mar 26, 2026

Summary

  • Remove React.Suspense / LazyBrandContextMenu wrapper around hero splash logo — eliminates fallback→loaded layout shift on page load
  • Add route loader with ensureQueryData to prefetch blog posts server-side — fixes hydration mismatch (<a> vs <div>) that also occurs in production
  • Change font-display: swapoptional and preload Inter font to prevent text reflow CLS
  • Add explicit width/height to navbar logo images and marquee brand logos
  • Match Suspense fallback wrapper structure in navbar to the loaded BrandContextMenu component

Test plan

  • Hard refresh home page — hero logo and H1 should not shake
  • Verify dark mode splash logo still displays correctly
  • Check no hydration mismatch errors in console
  • Check marquee logos render without layout shift
  • Confirm stats/blog/showcases still load (with or without DATABASE_URL)

Summary by CodeRabbit

Performance Improvements

  • Fonts now preload for faster page rendering
  • Server-side prefetching of recent posts for quicker initial load
  • Optimized brand logo assets with explicit sizing for more consistent, efficient rendering

UI Updates

  • Simplified and streamlined splash/logo presentation and loading
  • Standardized brand logo sizing in marquees and navigation

Style

  • Adjusted font rendering behavior (font-display set to optional) for improved loading experience

- Remove Suspense/lazy boundary around hero splash logo to prevent
  fallback→loaded layout shift
- Add route loader to prefetch stats, blog posts, and showcases
  server-side (fire-and-forget so DB errors don't block the page)
- Change font-display from swap to optional and preload Inter font
  to prevent text reflow on load
- Add explicit dimensions to navbar logo images and marquee brand logos
- Match Suspense fallback wrapper in navbar to loaded component structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@netlify
Copy link

netlify bot commented Mar 26, 2026

👷 Deploy request for tanstack pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit a24f644

@coderabbitai
Copy link

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

Adds explicit image dimensions and tightens logo sizing, adjusts a Suspense fallback, preloads Inter font, changes Inter font-display to optional, and adds a TanStack Router loader using queryClient.ensureQueryData to prime recent posts before rendering.

Changes

Cohort / File(s) Summary
Navbar & Brand Images
src/components/Navbar.tsx
Added explicit width/height props to NetlifyImage and two <img> logo variants; modified React.Suspense fallback to wrap LogoSection in an extra <div> (twMerge('flex items-center group flex-shrink-0')).
Trusted By Marquee
src/components/TrustedByMarquee.tsx
Set explicit width={96} and height={56} on brand <img> elements and changed Tailwind sizing from w-auto h-auto to fixed w-24 h-14 (keeps existing max constraints).
Index Route / Data Prefetching
src/routes/index.tsx
Added a route loader that calls queryClient.ensureQueryData(recentPostsQueryOptions) to prime the cache; removed React.lazy + React.Suspense for BrandContextMenu/logo area and simplified splash/logo to a single NetlifyImage (splash-light.png).
Root Head / Font Preload
src/routes/__root.tsx
Added a head.links preload entry for Inter-latin.woff2 (rel: 'preload', as: 'font', type: 'font/woff2', crossOrigin: 'anonymous').
Font Rendering Behavior
src/styles/app.css
Changed @font-face font-display for both Inter variants from swap to optional.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant RouteLoader as Route Loader
    participant QueryClient
    participant DataAPI as Data Source
    participant Renderer as Server Renderer

    Browser->>RouteLoader: GET /
    RouteLoader->>QueryClient: ensureQueryData(recentPostsQueryOptions)
    QueryClient->>DataAPI: fetch recent posts (if missing)
    DataAPI-->>QueryClient: recent posts data
    QueryClient-->>RouteLoader: cached data ready
    RouteLoader->>Renderer: render route (uses cached data)
    Renderer-->>Browser: HTML response (prefetched data hydrated)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through lines with glee,
Logos sized for all to see,
Fonts arrive before the show,
Posts cached swift — away I go,
Tiny hops, a tidy tree!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective of the changeset: reducing Cumulative Layout Shift (CLS) on the home page through multiple optimizations (Suspense removal, font preloading, explicit image dimensions).
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

franklinjavier and others added 2 commits March 26, 2026 00:11
Stats and showcases queries need DATABASE_URL — when they fail on the
server, the cached error state causes server/client render divergence.
Only prefetch blog posts (file-based) in the route loader.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
prefetchQuery is fire-and-forget — server may finish loading before
render while client starts in loading state, causing element mismatch
(<a> vs <div>). Use ensureQueryData to guarantee consistent state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/routes/index.tsx (2)

61-66: Make the fire-and-forget prefetch explicit.

Line 65 is intentionally non-blocking. Prefixing the call with void makes that intent obvious and avoids no-floating-promises ambiguity, while preserving TanStack Router/Query's non-awaited loader-prefetch behavior. (tanstack.com)

Suggested diff
   loader: ({ context: { queryClient } }) => {
     // Prefetch blog posts server-side (file-based, no DB needed).
     // Stats and showcases are DB-dependent — let them load client-side
     // to avoid hydration mismatches when DATABASE_URL isn't set.
-    queryClient.prefetchQuery(recentPostsQueryOptions)
+    void queryClient.prefetchQuery(recentPostsQueryOptions)
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/index.tsx` around lines 61 - 66, The loader’s call to
queryClient.prefetchQuery is intentionally non-blocking but should be made
explicit to avoid no-floating-promises lint issues; update the loader (the arrow
function that destructures context: { queryClient } and calls
queryClient.prefetchQuery) to prefix the call with void (i.e., use void
queryClient.prefetchQuery(...)) so the intent of a fire-and-forget prefetch is
clear while preserving TanStack Router/Query’s non-awaited behavior.

137-146: Consider an empty alt for the hero splash.

With the adjacent <h1> already announcing TanStack, Line 143 will likely read as duplicate output for screen-reader users. If this image is decorative brand art rather than unique content, alt="" is the better fit. (w3.org)

Suggested diff
               <NetlifyImage
                 src="/images/logos/splash-light.png"
                 width={500}
                 height={500}
                 quality={85}
                 className="w-[300px] pt-8 xl:pt-0 xl:w-[400px] 2xl:w-[500px]"
-                alt="TanStack Logo"
+                alt=""
                 loading="eager"
                 fetchPriority="high"
               />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/index.tsx` around lines 137 - 146, The hero splash image currently
uses <NetlifyImage> with alt="TanStack", which duplicates the adjacent <h1>;
change the alt prop to an empty string (alt="") on the NetlifyImage instance to
mark it decorative and avoid redundant screen-reader output, keeping all other
props (src, width, height, className, loading, fetchPriority) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/routes/index.tsx`:
- Around line 61-66: The loader’s call to queryClient.prefetchQuery is
intentionally non-blocking but should be made explicit to avoid
no-floating-promises lint issues; update the loader (the arrow function that
destructures context: { queryClient } and calls queryClient.prefetchQuery) to
prefix the call with void (i.e., use void queryClient.prefetchQuery(...)) so the
intent of a fire-and-forget prefetch is clear while preserving TanStack
Router/Query’s non-awaited behavior.
- Around line 137-146: The hero splash image currently uses <NetlifyImage> with
alt="TanStack", which duplicates the adjacent <h1>; change the alt prop to an
empty string (alt="") on the NetlifyImage instance to mark it decorative and
avoid redundant screen-reader output, keeping all other props (src, width,
height, className, loading, fetchPriority) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d43d5834-cb1e-45c4-89c2-3bcfe618696f

📥 Commits

Reviewing files that changed from the base of the PR and between c5aa9ec and 69ba6f0.

📒 Files selected for processing (1)
  • src/routes/index.tsx

@tannerlinsley
Copy link
Member

This took way too many liberties and was clearly vibe coded. Definitely some good stuff in here, but I'd like to see more granular fixes I can merge 1 by 1.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants