JavaScript Cost: Hydration, Long Tasks, and Shipping Less JS
How JS hurts INP and load performance, how to find long tasks, and practical patterns to reduce client JS in modern apps (especially Next.js).
Frontend Interview Team
March 01, 2026
What you’ll learn
- Why “JS weight” is not just download size
- What long tasks are and how they affect INP
- Practical ways to ship less client-side JavaScript
30‑second interview answer
JS performance is dominated by main-thread work: parsing, compiling, executing, and rendering side effects. Big bundles can look fine on desktop but cause long tasks on mobile, which hurts INP. The best fixes are: reduce JS shipped to the client (server rendering/RSC, remove dependencies), code-split by route, and avoid expensive renders and heavy work in interaction handlers.
Case study (real): frontendinterview.in
Quick lab snapshot:
- /blog LCP: ~1.0s
- /blog TTFB: ~60ms
This is what “shipping less JS” looks like in practice:
- fast server response (TTFB)
- quick render of main content
The thing to protect as the site grows is interaction responsiveness (INP). As you add:
- more interactive filters
- animations
- third-party scripts
…INP will usually degrade first.
Suggested guardrail: periodically record a Performance trace while rapidly typing in the blog search box and clicking filters. If you see long tasks clustering around input events, it’s time to reduce client JS and rerenders.
INP mini playbook (practical)
If you want one “senior engineer” workflow for INP, it’s this:
Step 1: Reproduce on a slower profile
- Use DevTools CPU throttling (4× or 6×)
- Keep cache disabled for the first run
Step 2: Record a Performance trace while interacting
Do 2–3 interactions that represent the real user path:
- type quickly in the search box
- click a filter
- open a post and go back
Step 3: Look for the first bottleneck (don’t guess)
- Long tasks around the interaction? → you’re main-thread bound
- Lots of React renders? → reduce rerenders and client boundaries
- Heavy JSON/looping? → move work off the hot path
Step 4: Fix in priority order
-
Make less code run on interaction
- keep handlers tiny
- defer non-urgent work (
requestIdleCallback, setTimeout, or scheduling)
-
Reduce rerenders
- memoize expensive derived values
- avoid creating new objects/arrays in props unnecessarily
-
Reduce hydration / client JS
- keep
"use client"low in the tree - prefer server rendering for static content
- keep
-
Offload heavy work
- web worker for CPU-heavy parsing/transforms
React pattern: keep typing responsive
If typing causes heavy UI updates, use concurrency tools:
import { useMemo, useState, useTransition } from "react";
export function SearchList({ items }: { items: string[] }) {
const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
const filtered = useMemo(() => {
const q = query.trim().toLowerCase();
if (!q) return items;
return items.filter((x) => x.toLowerCase().includes(q));
}, [items, query]);
return (
<div>
<input
value={query}
onChange={(e) => {
const next = e.target.value;
// keep input snappy
startTransition(() => setQuery(next));
}}
/>
{isPending ? <div>Updating…</div> : null}
<ul>{filtered.map((x) => <li key={x}>{x}</li>)}</ul>
</div>
);
}This doesn’t magically fix everything, but it’s a strong tool when the UI update is expensive.
Mental model: JS has 4 costs
- Download
- Parse
- Compile
- Execute
Only #1 shows up clearly in Network; the rest show up in Performance.
Hydration (what it really means)
Hydration is making server-rendered HTML interactive by attaching event listeners and running client code.
Hydration can be expensive if:
- you render huge component trees
- you hydrate content that doesn’t need interactivity
- you ship heavy client libraries
Long tasks and INP
A long task is typically 50ms+ of uninterrupted main-thread work.
INP gets worse when:
- a user clicks/typing happens during long tasks
- your handler does expensive work before the next paint
Debugging: DevTools Performance → find long tasks around interaction.
Practical strategies (high ROI)
1) Make fewer components client-side
If you’re using Next.js, keep as much as possible server-rendered.
2) Split code by route and by feature
Avoid loading heavy code on pages that don’t need it.
3) Defer non-urgent work
- analytics
- heavy computations
- non-critical UI initialization
4) Kill dependency bloat
A single dependency can add 50–200KB and a ton of parse/execute time.
Common mistakes
- Measuring only bundle size, not long tasks
- Adding client-only libraries for minor features
- Doing heavy JSON parsing or sorting on the main thread
Production rule of thumb
- Optimize for low-end devices.
- Reduce client JS before micro-optimizing code.
- If INP is bad, find the long tasks first.
Quick recap
- JS cost is mostly parse/execute on main thread.
- Hydration can dominate INP.
- Best fix is often shipping less JS.