DEV Community

Cover image for Why Your Next.js 15 App Is Still Slow (And How to Fix React 19 Hydration Lag)
Safdar Ali
Safdar Ali

Posted on • Originally published at safdarali.in

Why Your Next.js 15 App Is Still Slow (And How to Fix React 19 Hydration Lag)

A team upgraded to Next.js 15 and React 19, enabled the React Compiler, and expected Lighthouse scores to magically improve.

They didn't.

LCP remained above 3 seconds on mobile.
INP spiked whenever users interacted with filters.
Hydration still felt sluggish.

The stack was modern.

The performance wasn't.

After debugging multiple production applications, I've noticed the same pattern repeatedly:

Upgrading frameworks doesn't automatically eliminate runtime bottlenecks.

Hydration still runs on the main thread.
Client components still execute JavaScript.
Layout shifts still hurt Core Web Vitals.

Here's how I diagnosed and fixed these issues.


The Upgrade Myth

Many developers assume that upgrading to the latest stack automatically solves performance problems.

While Next.js 15 and React 19 bring significant improvements, they don't magically fix:

  • Layout shifts during hydration
  • Excessive client-side JavaScript
  • Large client component trees
  • Unnecessary re-renders
  • Delayed event handlers

The gap between:

"Build succeeds"

and

"Application feels fast"

is where most performance issues live.


Before Optimization

Real-World Metrics

Metric Before Target
LCP 3.8s < 2.5s
CLS 0.18 < 0.1
INP 420ms < 200ms
Client JS 312 KB < 180 KB

Lighthouse looked acceptable.

Actual user experience did not.

That's why I always start with:

  • Chrome Performance Panel
  • React Profiler
  • Layout Shift Overlay
  • Web Vitals

instead of Lighthouse alone.


Fix #1: Eliminate Layout Shifts During Hydration

The Largest Contentful Paint element was a dashboard chart.

The server rendered a placeholder.

When the client chart mounted, it resized the container and shifted the layout.

Result:

  • CLS increased
  • LCP was delayed

Diagnosis

Open:

Chrome DevTools → Performance → Record
Enter fullscreen mode Exit fullscreen mode

Enable:

Experience → Layout Shift Regions
Enter fullscreen mode Exit fullscreen mode

The purple overlays immediately revealed the culprit.


Solution

Reserve space before hydration.

export default async function DashboardPage() {
  const stats = await getDashboardStats();

  return (
    <section className="min-h-[280px] rounded-xl border p-4">
      <h1>{stats.title}</h1>

      <ChartSlot data={stats.series} />
    </section>
  );
}
Enter fullscreen mode Exit fullscreen mode

Additional improvements:

  • Fetch chart data on the server
  • Pass serializable props only
  • Lazy load chart libraries
  • Keep container dimensions fixed

Results

Metric Before After
LCP 3.8s 2.1s
CLS 0.18 0.04

Fix #2: React 19 Hydration Lag and Poor INP

INP measures how quickly the UI responds after interaction.

A common App Router issue:

  1. User clicks a filter
  2. Hydration is still running
  3. React delays the interaction
  4. INP spikes

The Problem

Originally:

Dashboard
 ├── Filters
 ├── Table
 └── Pagination
Enter fullscreen mode Exit fullscreen mode

Everything lived inside one large client component.

Hydration had to complete the entire tree before interactions felt responsive.


The Solution

Split hydration boundaries.

Before

"use client";

// filters
// table
// pagination
Enter fullscreen mode Exit fullscreen mode

After

FilterBar.tsx
DataTable.tsx
page.tsx
Enter fullscreen mode Exit fullscreen mode

The idea:

  • Hydrate filters first
  • Stream table later
  • Keep interactive components small

Additional optimization:

  • Move state into URL search params
  • Render filtered results on the server
  • Defer analytics using idle callbacks

Results

Metric Before After
INP 420ms 168ms

Fix #3: What React Compiler Doesn't Fix

Many developers believe React Compiler removes the need for optimization.

Not always.

Consider:

const handlers = {
  archive: () => onArchive(rowId),
  export: () => exportRow(rowId),
};
Enter fullscreen mode Exit fullscreen mode

A new object gets created every render.

React Compiler may not optimize this if:

  • Mutable module state exists
  • External references are involved
  • Reference stability cannot be guaranteed

Manual Optimization Still Matters

const handlers = useMemo(
  () => ({
    archive: () => onArchive(rowId),
    export: () => exportRow(rowId),
  }),
  [rowId, onArchive]
);
Enter fullscreen mode Exit fullscreen mode

Rule of Thumb

If your helper:

  • Reads module scope
  • Uses mutable state
  • Depends on refs

Verify in React Profiler before removing memoization.


React 19 Hydration Mismatch Example

Another issue I frequently encounter:

<p>{new Date(ts).toLocaleDateString()}</p>
Enter fullscreen mode Exit fullscreen mode

Server output:

May 31, 2026
Enter fullscreen mode Exit fullscreen mode

Client output:

31/05/2026
Enter fullscreen mode Exit fullscreen mode

Hydration mismatch.


Better Approach

<p suppressHydrationWarning>
  {formattedDate}
</p>
Enter fullscreen mode Exit fullscreen mode

Or format the value on the server before rendering.

Hydration retries create additional work and can negatively impact INP.


Production Optimization Checklist

My workflow:

1. Record a Performance Trace

Check:

  • Long Tasks
  • Layout Shifts
  • Main Thread Blocking

2. Use React Profiler

Identify:

  • Unnecessary re-renders
  • Large hydration boundaries
  • Expensive components

3. Split Client Islands

Hydrate:

  • Search
  • Filters
  • Menus

before heavier components.

4. Reserve Layout Space

Prevent:

  • Chart jumps
  • Image shifts
  • Font reflows

5. Test Like Real Users

Use:

  • 4G throttling
  • CPU throttling
  • Mobile viewport

Don't trust desktop Lighthouse alone.


Final Metrics

Metric Before After
LCP 3.8s 2.1s
CLS 0.18 0.04
INP 420ms 168ms
Client JS 312 KB 174 KB

Final Thoughts

Next.js 15 performance optimization isn't a version upgrade.

It's:

  • Better hydration boundaries
  • Smaller client components
  • Stable layouts
  • Reduced JavaScript
  • Measured improvements

React 19 is powerful.

But if your application still hydrates a massive client tree on page load, users will feel it.

The fastest apps aren't the ones running the newest framework versions.

They're the ones doing the least work.


☕ Enjoyed This Article?

If this article saved you hours of debugging:

👉 Buy me a coffee: https://buymeacoffee.com/safdarali

👉 Subscribe to my YouTube channel: https://www.youtube.com/@safdarali_?sub_confirmation=1

I regularly publish content on:

  • React
  • Next.js
  • Frontend Performance
  • AI Developer Workflows
  • Modern Web Engineering

Thanks for reading! 🚀

Top comments (0)