Skip to main content

Identifying Slow Components the Compiler Helps Solve

Not every slow React app benefits equally from the compiler. Some apps are slow due to network latency, others due to large bundle size, still others due to inefficient rendering. The compiler helps only if rendering is the bottleneck. Accurate diagnosis prevents wasting effort optimizing the wrong layer.

I spent a week profiling a "slow" e-commerce app, expecting to find rendering issues. Instead, I discovered that 70% of the slow page load was network (API calls), 15% was bundle parsing, and only 15% was rendering. The compiler optimized that 15%, but the real wins came from API caching and code-splitting.

The Diagnosis Process

Step 1: Establish a Performance Budget

Define what "slow" means for your app:

MetricBudget
FCP (First Contentful Paint)< 1.5 seconds
LCP (Largest Contentful Paint)< 2.5 seconds
INP (Interaction to Next Paint)< 200 milliseconds
CLS (Cumulative Layout Shift)< 0.1
JS bundle size< 200 KB
React component render time< 100 ms per interaction

If your app meets all budgets, it's not slow. If it exceeds any, dig deeper.

Step 2: Use Lighthouse to Identify the Bottleneck Category

Open Chrome DevTools → Lighthouse → Performance:

Performance Score: 45
Metrics:
- First Contentful Paint: 2.1s (exceeds 1.5s budget)
- Largest Contentful Paint: 3.8s (exceeds 2.5s budget)
- Interaction to Next Paint: 180ms (within budget)
- Cumulative Layout Shift: 0.05 (within budget)

Opportunities:
- Eliminate render-blocking resources: 1.2s potential saving
- Defer offscreen images: 0.5s
- Reduce JavaScript: 0.3s

In this example, the bottleneck is not rendering (INP is good) but resource loading (network + parsing). Optimizing React rendering here won't help much.

Step 3: Use React Profiler to Isolate Rendering Issues

If Lighthouse shows rendering is an issue (high INP or LCP due to React rendering), open React Profiler:

// App.jsx
function App() {
const [data, setData] = useState(null);

useEffect(() => {
fetch("/api/data").then(r => r.json()).then(setData);
}, []);

return (
<div>
<Header />
{data && <MainContent data={data} />}
<Footer />
</div>
);
}
  1. Open React DevTools → Profiler tab.
  2. Record a performance trace while the page loads.
  3. Stop recording when MainContent has rendered.

The Profiler shows:

Phase 1 (Duration: 0.2s):
App: 2ms
Header: 15ms
MainContent: 45ms (SLOW—this is the bottleneck)
Footer: 3ms

Phase 2 (subsequent renders):
App: 1ms
Header: 3ms (skipped by memo)
MainContent: 42ms (still slow—compiler might not help here)
Footer: 1ms

If MainContent takes 45ms on first render and compiler memoization can't help (because data is new), the bottleneck is inside MainContent, not in prop passing.

Step 4: Drill Into the Slow Component

Expand MainContent in the Profiler to see its children:

MainContent: 45ms
DataTable: 40ms (rendering 50 rows)
SearchBar: 3ms
Sidebar: 2ms

DataTable is the real bottleneck. Now measure with a finer granularity:

function DataTable({ items }) {
const start = performance.now();
const rendered = items.map(item => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.price}</td>
<td>{formatCurrency(item.total)}</td> // Expensive function?
</tr>
));
const end = performance.now();
console.log(`DataTable render: ${end - start}ms`);
return <table>{rendered}</table>;
}

Profile the component to see where time is spent. If formatCurrency is called 50 times and is expensive, that's the problem.

Case Study 1: Unnecessary Re-renders (Compiler Helps Here)

Symptoms:

  • Profiler shows component renders every time parent renders.
  • Component's props haven't changed.
  • Lighthouse shows LCP slow.

Example:

function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment: {count}</button>
<ExpensiveChart data={chartData} /> {/* chartData never changes */}
</div>
);
}

function ExpensiveChart({ data }) {
// This re-renders every time App re-renders, even though data is the same
const chart = renderChartLibrary(data); // 100ms each render
return <svg>{/* ... */}</svg>;
}

Diagnosis: Profiler shows ExpensiveChart renders 10 times in 10 seconds (once per button click), but data never changes.

Fix: React Compiler solves this. The compiler memoizes ExpensiveChart automatically (it sees that data is the same).

Before compiler: 10 renders × 100ms = 1 second wasted
After compiler: 1 render, 0 redundant re-renders = 0 seconds wasted

Case Study 2: Expensive Computation (Compiler Helps If Not Memoized Yet)

Symptoms:

  • Profiler shows a component takes 150+ ms to render.
  • Computation happens on every parent re-render.
  • No useMemo around the computation.

Example:

function UserList({ users }) {
// Sorting 10,000 users on every render
const sorted = users.sort((a, b) => a.name.localeCompare(b.name)); // 50ms
return <ul>{sorted.map(u => <li>{u.name}</li>)}</ul>;
}

Diagnosis: Profiler shows 50ms per render. Users are sorted even when users haven't changed.

Fix: React Compiler memoizes the sort. If users is the same, the compiler reuses the sorted array.

Before compiler: 50ms per render
After compiler: 50ms first render, 0ms if users unchanged

Case Study 3: External API Calls (Compiler Can't Help)

Symptoms:

  • Profiler shows component is fast (50ms).
  • But LCP or INP is high (2+ seconds).
  • Slow page load or slow interaction response.

Example:

function ProductDetails({ productId }) {
const [product, setProduct] = useState(null);
const [reviews, setReviews] = useState([]);

useEffect(() => {
fetch(`/api/products/${productId}`).then(r => r.json()).then(setProduct); // 1.5s
fetch(`/api/products/${productId}/reviews`).then(r => r.json()).then(setReviews); // 1.5s
}, [productId]);

// Rendering is fast, but data fetching is slow
return <div>{product && <Reviews data={reviews} />}</div>;
}

Diagnosis: Profiler shows rendering takes 20ms. Lighthouse shows LCP is 3s. Network tab shows two API calls taking 1.5s + 1.5s = 3s.

Fix: Compiler can't help. The bottleneck is network, not React rendering. Solutions:

  • Cache API responses (HTTP cache-control headers).
  • Fetch data earlier (in a parent component, before navigation).
  • Use GraphQL or a data-fetching layer to batch requests.
  • Implement request deduplication.

Case Study 4: Unoptimized Child Components (Compiler Helps)

Symptoms:

  • Large list of items, each item component is slow.
  • Items re-render when siblings are added/removed.
  • Profiler shows many child renders for unchanged items.

Example:

function CommentThread({ comments }) {
return (
<div>
{comments.map(comment => (
<CommentCard key={comment.id} comment={comment} onReply={handleReply} />
))}
</div>
);
}

function CommentCard({ comment, onReply }) {
const parsed = parseMarkdown(comment.text); // 10ms per render
return <div>{parsed}</div>;
}

Diagnosis: With 100 comments, Profiler shows 100 × 10ms = 1 second when rendering the thread.

Fix: Compiler memoizes. Each CommentCard is memoized. When a new comment is added, only the new card renders; the other 100 don't.

Before compiler: 100 renders × 10ms = 1 second
After compiler: 1 new render × 10ms = 10ms (rest skipped)

Diagnostic Checklist

Use this checklist to determine if the compiler will help:

QuestionYes = Compiler HelpsNo = Other Issue
Do components render when props haven't changed?YesNo
Is a component's render time > 20ms?YesNo
Does render time increase with a large list?YesNo
Does parent re-render trigger child re-renders unnecessarily?YesNo
Is LCP/INP slower than network latency alone?YesNo
Are you using memo() to skip renders?Yes (compiler replaces it)No (not needed)

If most answers are "Yes," the compiler will help significantly. If most are "No," look elsewhere: network, bundle size, third-party scripts.

Key Takeaways

  • Use Lighthouse to identify whether rendering, network, or bundle size is the bottleneck.
  • Use React Profiler to pinpoint which components are slow.
  • Compiler helps most with unnecessary child re-renders and memoizable computations.
  • Compiler can't help with network latency, external API calls, or bundle parsing.
  • Measure performance before and after enabling the compiler to validate improvements.

Frequently Asked Questions

If Lighthouse shows high LCP but React Profiler shows fast renders, what's happening?

LCP measures the time until the largest visual element appears. If rendering is fast but the API call is slow, LCP stays high. The bottleneck is not React rendering.

How slow is "slow enough" to optimize with the compiler?

If a component takes > 30ms to render and renders frequently (every user interaction), the compiler helps. If it takes 5ms and renders once per page load, optimization ROI is low.

Can the compiler help if my app is primarily I/O-bound (waiting for API calls)?

No. The compiler only optimizes rendering. If your app is network-bound, optimize caching, request batching, or data fetching architecture instead.

What if Profiler shows a component is slow but the compiler bails out on it?

That component has an unsafe pattern the compiler can't optimize (see Bailouts and Debugging). Fix the bailout or refactor the component to make it optimizable.

Further Reading