Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogCoursesPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Courses
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
PerformanceJune 25, 20264 min read

Data Hydration Strategies: Improving LCP and INP Performance

Master data hydration using stale-while-revalidate and Server-Sent Events to drastically improve LCP and INP for a snappier, more responsive user experience.

web performancedata hydrationstale-while-revalidateSSEfrontend engineeringCore Web VitalsPerformanceWeb VitalsFrontend

Last month, I spent three days wrestling with a dashboard that felt sluggish despite having a decent Time to First Byte. The issue wasn't the server speed; it was the "loading state waterfall" that occurred after the initial shell hit the browser. We were fetching fresh data on every mount, forcing the user to stare at a spinner while the main thread choked on data processing, killing our Interaction to Next Paint (INP) scores.

To fix this, I moved away from standard useEffect fetching toward a more sophisticated model. We needed a way to display cached data immediately while pushing updates via the network.

The Problem with Traditional Data Hydration

Most modern frameworks rely on fetching data during the initial render or immediately after. If your bundle is large, the overhead of parsing and executing JavaScript—combined with the network request for state—blocks the main thread. This is a classic recipe for poor INP and LCP.

When you trigger a heavy data fetch during hydration, the main thread stays busy. If a user tries to click a button during this period, the browser remains unresponsive. I’ve seen this add around 400ms of delay to interaction times in production environments.

Implementing Stale-While-Revalidate for Instant UI

The first step in our optimization was implementing a stale-while-revalidate pattern. This allows us to serve the last known state from the Cache API while simultaneously fetching fresh data in the background. If you're new to this, check out my previous guide on Service Workers: Implementing Stale-While-Revalidate for Web Performance.

By serving the cache first, the LCP element—usually a header or a main data grid—renders instantly. The user sees "stale" but accurate data, which is far better than a blank screen or a loading spinner.

JAVASCRIPT
// Simple SWR-like implementation
async function getHydrationData(url) {
  const cache = await caches.open(CE9178">'v1-data');
  const cachedResponse = await cache.match(url);
  
  // Background revalidation
  const fetchPromise = fetch(url).then(async (res) => {
    await cache.put(url, res.clone());
    return res.json();
  });

  return cachedResponse ? cachedResponse.json() : fetchPromise;
}

Bridging the Gap with Server-Sent Events

While stale-while-revalidate handles the initial load, it doesn't solve the "freshness" problem for long-lived sessions. If the data changes on the server, the user is stuck with the stale cache until the next reload.

We integrated Server-Sent Events (SSE) to push updates from the backend. This is significantly lighter than WebSockets because it’s unidirectional and uses standard HTTP. When the server pushes an event, we update the local cache and the UI state, keeping the application fresh without expensive polling.

Architecting Cache-Aware Data Hydration

When you combine these two, your architecture shifts from "Request-Response" to "Stream-Observe." Here is how the flow looks:

  1. Initial Load: Browser checks the cache and hydrates the UI with stale data (improving LCP).
  2. Background Fetch: The browser fetches the latest data in the background, updating the cache once it arrives.
  3. Live Sync: An SSE connection remains open. When the server has an update, it pushes the delta to the client.
  4. State Update: The client patches the UI state, ensuring the user sees the most recent information without a hard refresh.

This approach significantly reduces the time the main thread spends on blocking tasks. For deeper insights into how backend latency plays into this, I often use the Server-Timing API for INP Optimization: Debugging Backend Latency to ensure our SSE events aren't being delayed by slow database queries.

Lessons Learned and Trade-offs

This isn't a silver bullet. The biggest trade-off is complexity. You now have to manage "cache invalidation" logic. If your data structure changes, the old cache might break your components. We solved this by versioning our cache keys (e.g., v1-data, v2-data).

I’m still experimenting with how to handle large payloads via SSE. Currently, we only push small diffs. If the payload is larger than a few kilobytes, we revert to a standard fetch to avoid memory pressure.

If you’re struggling with TBT or general responsiveness, you might also look into Improving INP via Selective Hydration and React Suspense. Combining these strategies—selective hydration for the UI and SWR/SSE for the data—is how you achieve that "instant" feel users expect today.

FAQ

Does this increase memory usage? Yes, holding multiple versions of data in the cache can consume memory. Keep your cache size limited using the Cache API expiration policies.

Is SSE better than WebSockets for this? For most data hydration scenarios, yes. SSE is easier to implement, automatically handles reconnection, and works perfectly over standard HTTP/2 streams.

How does this affect SEO? Since the initial cache is rendered as HTML, search engines see the content immediately. Just ensure your server provides the initial state correctly during the first render.

Next time, I want to explore if we can use the Speculation Rules API to pre-fetch these cache entries before the user even navigates to the dashboard, potentially making the "stale" load feel even faster. Until then, stick to measuring your INP—if you can't measure it, you can't optimize it.

Back to Blog

Similar Posts

PerformanceJune 25, 20264 min read

Browser Caching and Network Congestion: A Guide to HTTP/3 Performance

Master browser caching and network congestion management. Learn how to use HTTP/3 and smarter cache headers to stop resource contention and boost speed.

Read more
PerformanceJune 24, 20264 min read

TTFB Optimization: Using Resource Hints for Connection Warming

Master TTFB optimization by implementing resource hints like preconnect and dns-prefetch. Learn to warm up connections to slash latency before it happens.

Read more
PerformanceJune 24, 20264 min read

Main Thread Optimization: Preventing UI Congestion with Backpressure

Master main thread optimization by implementing backpressure-aware UI patterns. Learn to use adaptive throttling and request prioritization to keep UIs smooth.

Read more