Hydration optimization is key to faster sites. Learn how selective serialization reduces total blocking time by preventing massive JSON parsing on the client.
Last month, I spent three days staring at a Chrome DevTools performance profile that made no sense. Our homepage had a decent Largest Contentful Paint (LCP), but the Total Blocking Time (TBT) was hovering around 600ms on mid-tier mobile devices. The main thread was simply choking during the hydration phase.
We were shipping a massive, monolithic JSON object in our server-side rendered HTML. The browser had to parse this entire blob before React could even start the hydration process. If you’re struggling with similar performance regressions, you’re likely hitting the same wall: the cost of deserializing large state objects on the main thread.
When we talk about web performance, we often focus on network speed or image optimization. However, javascript serialization is a silent killer. In a typical Next.js or React SSR setup, the server serializes the initial state into a <script> tag. The browser then parses this script, reconstructs the object, and passes it to the client-side store.
If that object is 500KB of minified JSON, the main thread is locked for a significant duration while the parser works. This directly inflates your total blocking time. During this window, any user interaction—a tap, a scroll, a keystroke—is effectively ignored. It feels like the site is frozen, even if the visual content is already painted.
We initially tried moving these scripts to a Web Worker, but that broke our state consistency. We needed a way to keep the data accessible to the initial render while breaking up the heavy lifting. This is where hydration optimization becomes a structural challenge rather than a simple configuration tweak.
Instead of dumping the entire application state into a single global object, we shifted to a pattern of progressive delivery. We identified non-critical data—things like user activity feeds or secondary recommendation widgets—and removed them from the initial server-side payload.
Here is how we approached the refactor:
useEffect call or a lightweight useSWR hook after the initial hydration completes.By doing this, we cut the initial JSON payload from 500KB down to about 80KB. The TBT dropped from 600ms to roughly 140ms. The UI became interactive nearly four times faster, even though the total amount of data transferred remained the same.
If your frontend architecture relies on a single, massive state object, you're fighting against the browser's single-threaded nature. You can also Improve INP via Selective Hydration and React Suspense to ensure that the browser remains responsive while components are mounting.
However, don't ignore the data layer. If your serialization is heavy, your hydration will be slow. I’ve seen teams try to fix this by deferring scripts using async or defer, but that doesn't help if the parser still has to process the data to make it available to the runtime.
You should also be aware of how your components handle updates. If you aren't careful, you might trigger unnecessary re-renders during the hydration phase, which complicates the performance picture. You can learn more about this in my guide on React rendering: Tracing Prop Changes from Update to DOM Patch.
Q: Does selective serialization hurt SEO? A: Not if you keep the critical content (the stuff that affects LCP) in the initial HTML. Only defer the non-critical state that the user doesn't need to see immediately.
Q: Is this only for large apps? A: No. Even smaller apps can suffer from TBT issues if the data is complex. If you notice a "Long Task" warning in your lighthouse report during the load phase, you should look into this.
Q: Can I just use JSON.parse manually?
A: You can, but it doesn't solve the main thread blockage. The browser still has to parse the string. The goal is to reduce the size of the string being parsed.
I’m still not 100% happy with our current implementation. We have to maintain two separate data fetching paths—one for the server and one for the client—which adds complexity. Next time, I’d like to experiment with a more automated serialization approach, perhaps using a library that handles the chunking for us.
Performance is a moving target. The moment you optimize one area, you’ll find a bottleneck somewhere else. Keep measuring, keep profiling, and don't be afraid to pull apart your state management if it's holding your users back.
Master browser resource prioritization using Fetch Metadata and Document Policy. Stop network congestion and improve your LCP by controlling request scheduling.