The Glitched Goblet Logo

The Glitched Goblet

Where Magic Meets Technology

React 19.2 + React Compiler RC in Next.js 15.5: A Pragmatic Upgrade Guide

October 6, 2025

Intro

React shipped 19.2 with real, fun goodies (hello <Activity />, useEffectEvent, and SSR improvements), while Next.js 15.5 quietly added a first-class switch for the React Compiler. Put together, you can modernize rendering patterns, trim re-renders, and get cleaner Effects without turning your codebase into a memoization shrine. (React)

tldr; Upgrade your app to React 19.2, flip on the (experimental) React Compiler in Next 15.5, and adopt the new hooks/features incrementally. Expect fewer manual useMemo/useCallback, better SSR, and saner Effect event handling. (React)

Upgrade React + toolchain

Update your React packages:

# pnpm / npm / yarn. Pick your poison
pnpm up react@19.2.0 react-dom@19.2.0
pnpm up eslint-plugin-react-hooks@^6.1.1

Why these versions?

  • React 19.2 delivers new features and SSR fixes (plus a useId prefix change worth snapshot-testing).
  • eslint-plugin-react-hooks v6 ships compiler-aware lint rules and flat-config presets that play nicely with the new patterns. (React)

If you’re on Next 15.x already, bump to 15.5.x to get the best DX and docs parity:

pnpm up next@15.5.4

Next 15.5 includes infra and DX updates (Turbopack builds in beta, stable Node middleware, TS improvements) you’ll want in daily use. (Next.js)

Adopt new React 19.2 primitives

useEffectEvent

Move “event-like” logic out of Effects without breaking dependency rules.

import { useEffect, useEffectEvent } from 'react'

function ChatRoom({ roomId, theme }: { roomId: string; theme: string }) {
  const onConnected = useEffectEvent(() => {
    // always sees latest props/state
    showToast(`Connected with theme ${theme}`)
  })

  useEffect(() => {
    const conn = connect(roomId)
    conn.on('connected', onConnected)
    return () => conn.disconnect()
  }, [roomId])
}

Result: fewer spurious reconnects and no “disable the lint rule" moments. Update your lint config to use plugin:react-hooks/recommended (or recommended-legacy if you need the old behavior). (React)

<Activity />

Pre-render and keep hidden UI “warm” without clobbering visible work. It's useful for near-future navigations.

import { Activity } from 'react'

export default function Shell({ prefetchNext }: { prefetchNext: boolean }) {
  return (
    <Activity mode={prefetchNext ? 'hidden' : 'visible'}>
      <NextPageLikelyNeededSoon />
    </Activity>
  )
}

This helps with snappy navs and state preservation when users bounce back and forth. (React)

Notes worth snapshot-testing: - useId now prefixes with _r_ (changed again in 19.2). If you assert on IDs in tests or CSS selectors, adjust. (React)

Enable the React Compiler in Next 15.5 (experimental)

The React Compiler auto-memoizes components/hooks so you write less useMemo/useCallback and still avoid wasteful re-renders.

Install:

pnpm add -D babel-plugin-react-compiler

Turn it on in next.config.ts:

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    reactCompiler: true, // or { compilationMode: 'annotation' } for opt-in
  },
}

export default nextConfig

Next uses a custom SWC optimization to apply the compiler only where it matters, keeping builds fast. You can also run in “annotation” mode and opt in per file with "use memo" (and opt out with "use no memo"). (Next.js)

Reality check: In Next docs, the compiler is experimental and not recommended for production (yet). Try it behind an env flag and measure. (Next.js)

Verify the compiler is doing work

  • Add "use memo" to a leaf component and confirm render counts drop in React DevTools Profiler.
  • Record a performance profile: Chrome now exposes React Performance Tracks that make it easier to see scheduler and component work. Expect clearer traces when hidden/visible work is separated. (React)

Incremental adoption strategy

If enabling globally feels spicy, switch to annotation mode:

experimental: {
  reactCompiler: { compilationMode: 'annotation' },
}

Then opt-in surgically:

export function ExpensiveRow({ item }: { item: Item }) {
  'use memo' // let the compiler lock this down
  // heavy render...
}

Start with:

  1. Hot paths with prop/closure churn (lists, tables, dashboards).
  2. Components you previously “band-aided” with useCallback/useMemo.
  3. UI that rerenders due to stable props but unstable inline functions.

As you gain confidence, consider flipping the global true and opt out where needed with "use no memo". (Next.js)

Common pitfalls & how to debug

  • Mutation & impurity still bite. The compiler assumes React’s “keep components pure” guidance. Mutating props or external objects can produce head-scratchers, fix the code, don’t fight the tool. (See the compiler docs’ troubleshooting and “keeping components pure”.) (React)
  • Third-party libs. If a lib couples rendering with mutation, annotate the boundary ("use no memo") or keep that zone uncompiled until you can refactor/replace. (React)
  • Linting. Upgrade eslint-plugin-react-hooks and use its recommended config so useEffectEvent and compiler rules cooperate. (React)
  • Build time. Expect a small build-time hit. Next’s SWC filtering helps keep it modest. Track CI minutes after enabling. (Next.js)

Minimal example: Before vs After compiler

Before (manual memo)

// Lots of boilerplate that may or may not be correct:
const Cell = React.memo(function Cell({ value, onClick }) {
  const expensive = useMemo(() => crunch(value), [value])
  const handle = useCallback(() => onClick(expensive), [onClick, expensive])
  return <button onClick={handle}>{expensive.label}</button>
})

After (let the compiler work)

export function Cell({ value, onClick }: { value: X; onClick: (x: X) => void }) {
  'use memo' // if using annotation mode, omit if globally enabled
  const expensive = crunch(value) // compiler can memoize this
  const handle = () => onClick(expensive) // compiler can stabilize this
  return <button onClick={handle}>{expensive.label}</button>
}

Cleaner and similar perf once compiled. (Measure in your app!)

Bonus: Next.js 15.5 housekeeping while you’re here

  • Turbopack builds (beta): experiment on CI, faster cold builds are nice for PR velocity.
  • Stable Node.js Middleware: unify edge vs node handling where it makes sense.
  • Deprecations (next lint, etc.): scan release notes and clean up warnings to avoid 16.x surprises. (Next.js)

Next Steps

  • Rollout plan: Start with a canary env + traffic sampling. Flip reactCompiler only for that environment.
  • Perf budget: Add Profiler traces to PRs touching high-churn components. Watch React Tracks in Chrome. (React)
  • Codemod backlog: After the compiler stabilizes, remove redundant useMemo/useCallback where they no longer pull weight.
  • RSC & SSR: Evaluate 19.2 SSR tweaks (batched Suspense reveals, Web Streams APIs) if you stream HTML. Stick to Node Streams in Node for perf. (React)

Outro

React 19.2 brings tangible DX and perf wins. the React Compiler promises fewer re-render headaches with far less code. In Next 15.5 the switch is there, just flip it thoughtfully.