A component that dominates your profiler has no ✨ badge in React DevTools. The instinct is to assume the compiler bailed out for a reason you can’t see, then reach for useMemo, useCallback, and memo to compensate. That instinct is usually wrong, and it costs you the one signal that would have told you what actually happened.
React Compiler is designed to skip code it cannot safely verify rather than risk changing behavior. The skip is not a failure mode you need to reverse-engineer. It is a documented state that the tooling already reports — in your editor, in DevTools, and in the compiler’s own configuration. The work is to read those reports in the right order and decide whether the skip matters for the interaction you’re trying to fix.
What the compiler actually does when it meets code it can’t verify
The React docs are explicit: “React Compiler is designed to handle code that follows the Rules of React. When it encounters code that might break these rules, it safely skips optimization rather than risk changing your app’s behavior” (React Compiler: Debugging and Troubleshooting). The same page notes that compiler errors — build-time failures — are rare precisely because the compiler prefers to skip.
That design choice has a consequence for how you read a missing badge. A skipped component is not evidence of a bug in your code. It is evidence that the compiler could not prove the component was safe to memoize. Those are different claims, and they lead to different fixes.
The compiler’s initial release focuses on two things: skipping cascading re-renders of components, and skipping expensive calculations that happen during render inside components or hooks (React Compiler introduction). If your hot component is skipped, neither of those optimizations is being applied to it. Whether that matters depends on what the component is actually doing on the interaction you care about.
Two failure directions, not one
Teams conflate two problems that the compiler treats separately.
Skipped optimization is the safe, expected outcome. The compiler saw something it could not verify and moved on. Other components in the same file, and other files in the same build, still get compiled. The docs describe this as “perfectly okay” and note the compiler “can recover and continue optimizing other components in your codebase” (React Compiler installation guide).
Incorrect optimization is the dangerous outcome. The compiler compiled a component that violates the Rules of React in a way it could not detect statically. The debugging guide names the most common shape: code written to rely on memoization for correctness, where the compiler’s memoization differs from the manual approach and produces effects that over-fire, infinite loops, or missing updates (Debugging and Troubleshooting).
These require opposite responses. A skipped hot component needs a decision about whether to widen coverage or leave it alone. A miscompiled component needs the Rules-of-React violation found and fixed. Adding manual memoization to a skipped component does nothing for the first problem and can mask the second.
The diagnostic order that replaces guessing
Before you touch the component, read what the tooling already says. The order matters because each step narrows the question.
1. Check the ESLint plugin output for that component
The compiler ships an ESLint plugin that surfaces the same analysis in your editor, and it runs independently of the compiler — you can use it even if the compiler is not enabled in your build (React Compiler installation guide). When the plugin reports a Rules-of-React violation on a component, that is the compiler telling you it skipped that component. The docs are direct about the meaning: “When it does this, it means that the compiler has skipped over optimizing that component or hook” (same page).
If the plugin is silent on your hot component, the skip is not a Rules-of-React violation the plugin can see. That is your first fork in the road.
2. Confirm the badge state in React DevTools
React DevTools v5.0+ displays a “Memo ✨” badge next to components the compiler optimized (React Compiler installation guide). The badge is the ground truth for whether the compiler touched that component in the build you are profiling. Confirm it against a production build, not a dev build — the docs are explicit that dev-mode timings are not representative and that Strict Mode double-renders in development (useMemo reference).
If the badge is present and the component still dominates the profile, the compiler did its job and the cost is somewhere else — a child that re-renders, an expensive non-component function, or work outside React. If the badge is absent, you have confirmed a skip and can move to step three.
3. Isolate with "use no memo" only if behavior is wrong
The "use no memo" directive at the top of a function prevents the compiler from optimizing it (use no memo reference). It is a bisection tool: if a runtime problem disappears when you add it, the problem is likely compiler-related. The docs frame it as “a temporary debugging tool, not a permanent solution” and warn that it takes precedence over all compilation modes and other directives.
This step is for the second failure direction — incorrect optimization — not for a skipped component. If your hot component is skipped and behaving correctly, "use no memo" changes nothing and tells you nothing. Reaching for it here is a sign you skipped steps one and two.
Why a skipped hot component is often not the lever
The compiler’s memoization is per-component and per-hook. The docs state it plainly: “React Compiler only memoizes React components and hooks, not every function” and “React Compiler’s memoization is not shared across multiple components or hooks” (React Compiler installation guide).
That has a specific consequence for the component that dominates your profile. If the expensive work lives in a plain function called during render — a sort, a filter, a transform over a large array — the compiler will not memoize that function even when it compiles the component around it. The docs recommend implementing caching for that function outside React, and profiling first to confirm it is actually expensive before complicating the code.
So the question is not “why did the compiler skip my hot component” but “what is the hot component actually spending time on.” If the answer is a non-component function, the skip is irrelevant to the cost. If the answer is cascading re-renders of children, the skip matters and the fix is to widen coverage or restructure the tree.
A decision rule you can defend in review
Work through these in order. Each one produces a recorded answer, not a guess.
- Does the ESLint plugin report a violation on the component? If yes, fix the Rules-of-React violation. That widens compiler coverage and is the correct fix regardless of performance. If no, continue.
- Is the ✨ badge absent in a production build? If yes, you have a confirmed skip with no plugin-reported cause. Continue. If the badge is present, the compiler optimized the component and the cost is elsewhere.
- What does the profiler attribute the time to? If the cost is a non-component function called during render, the compiler’s per-component memoization will not help it. Cache that function outside React, guided by a measured before/after. If the cost is child re-renders, the skip is the lever — widen coverage or restructure so children receive stable props.
- Only then consider manual memoization. The React docs are clear that
useMemo,useCallback, andmemoare performance optimizations, not correctness tools, and that a single value that is “always new” is enough to break memoization for an entire component (memo reference). Add them where the profiler shows a measured cost, not where a badge is missing.
Step four is where most teams start. That is the mistake. Manual memoization on a skipped component does not restore the compiler’s optimization — it adds a second, hand-maintained caching layer whose correctness you now own. The docs note that with the compiler enabled you typically don’t need React.memo anymore because the compiler applies the equivalent automatically (memo reference). Adding it back to a skipped component is not the same optimization; it is a different one with different failure modes.
Configuration choices that change what “skipped” means
Two compiler options affect whether a component is compiled at all, and both are worth checking before you conclude anything about a skip.
compilationMode controls which functions the compiler selects. The default is intelligent detection; 'annotation' compiles only functions marked with "use memo" (Configuration reference). If your project is in annotation mode, a component without the directive is skipped by configuration, not by analysis. That is a different problem with a different fix.
panicThreshold determines whether the compiler fails the build or skips problematic components (same page). The docs show panicThreshold: 'none' as the recommended production setting, which means problematic components are skipped rather than failing the build. If your team set this, a skip is the configured behavior, not a surprise.
Check both before you profile. A skip caused by configuration is not a signal about your code.
What to record so the next person doesn’t guess
The discipline that separates a performance-engineering decision from a reflex is a recorded delta. Before you change anything, capture:
- The React and compiler versions, pinned exactly. The compiler’s behavior and the DevTools badge depend on both.
- The ESLint plugin output for the component, verbatim.
- The ✨ badge state in a production build.
- The profiler’s attribution for the interaction: which components rendered, how many times, and where the time went.
- The interaction’s INP or long-task duration from the Chrome DevTools Performance panel, measured on a throttled CPU.
After the change, capture the same five. If the delta is not measurable, the change was not a performance fix — it was a guess that happened to compile.
The React docs make the same point about useMemo: measure with console.time first, and if the logged time is not significant, memoization is not warranted (useMemo reference). The same standard applies to every decision that follows a skip.
FAQ
My component has no ✨ badge but the ESLint plugin reports nothing. What does that mean?
It means the compiler skipped the component for a reason the plugin did not surface as a Rules-of-React violation. Check your compilationMode and panicThreshold settings first — a skip caused by annotation mode or a permissive panic threshold is configuration, not analysis. If both are at defaults, the skip is the compiler’s safe behavior on code it could not verify, and the next question is whether the component’s cost is actually attributable to the missing optimization.
Should I add React.memo to a skipped hot component?
Only if the profiler shows the component re-rendering with props that are shallowly equal, and only after you have confirmed the skip is not a Rules-of-React violation you could fix instead. The docs note that memo is “completely useless if the props passed to your component are always different” (memo reference), which is common when a parent passes inline objects or functions. Fix the prop stability first; the memo wrapper is the last step, not the first.
Does "use no memo" tell me why the compiler skipped a component?
No. It tells the compiler to skip a function it might otherwise have compiled. It is a bisection tool for isolating whether a runtime problem is compiler-related, not a diagnostic for why a skip occurred. The docs describe it as a temporary debugging tool and warn against leaving it in place (use no memo reference).
Is React Compiler stable enough to rely on for this analysis?
The retrieved docs describe the compiler as experimental with rough edges in one page and reference React 19 compatibility in another. The debugging guide and configuration reference are the authoritative sources for current behavior, and both should be read at the version you have pinned. Do not generalize from one page’s framing to a claim about production readiness across all apps.
What if the expensive work is in a plain function, not a component?
The compiler will not memoize it. The docs state that the compiler “only memoizes React components and hooks, not every function” and recommend implementing caching for genuinely expensive functions outside React, after profiling confirms the cost (React Compiler installation guide). A skipped component that calls such a function is not the problem; the function is.
The short version
A missing ✨ badge is a question, not an alarm. Read the ESLint plugin output, confirm the badge in a production build, and check your compilationMode and panicThreshold before you conclude anything about the compiler’s analysis. If the skip is real and the profiler attributes the cost to child re-renders, widen coverage or restructure the tree. If the cost is a non-component function, cache it outside React. Reach for manual memoization last, with a measured delta in hand, and record the versions and numbers so the next person reads evidence instead of guessing at a bailout.