reactStrictMode

Controls React Strict Mode's development-only double invocation and legacy API warnings.

Signature
reactStrictMode?: boolean

Parameters

ParameterTypeDescription
$reactStrictModeoptionalboolean= true (App Router, since v13.5.1)Wraps the application in React.StrictMode. Development-only: it double-invokes renders, effects and state updater functions to surface impure code and missing effect cleanup, and warns about unsafe lifecycles and legacy APIs. Has no effect on a production build.

Changelog

  • PHP v13.5.1Strict Mode is true by default with the app router; the config is only needed to opt out, or to opt in for pages.

With the App Router this is already on. The reason you end up on this page is usually the symptom: a component logs twice, an effect fires twice on mount, a counter increments by two. That is Strict Mode deliberately running renders and effects twice in development to expose code that is not idempotent.

When to reach for it

  • Turning it on for a Pages Router app, where it is not the default.
  • Temporarily setting it to false to isolate whether a bug is double-invocation related.

What trips people up

The double render is the diagnosis, not the disease. An effect that fires twice is telling you it has no cleanup — a subscription never torn down, a fetch never aborted, an event listener stacking up. In production it runs once and leaks quietly instead. Setting false hides the symptom and keeps the bug.

None of this reaches production: React strips Strict Mode's extra passes from production builds, so false changes nothing about deployed behaviour. If you have inherited an app that cannot tolerate it everywhere, migrate page by page with <React.StrictMode> around the parts that are ready rather than disabling it globally.

Example

Example · typescript
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  reactStrictMode: true,
}

export default nextConfig

When to use it

  • Enable the checks in a Pages Router app that predates the default
  • Confirm whether a doubled network request is Strict Mode or a real bug
  • Keep the checks on while migrating a legacy app incrementally

Frequently asked questions

Why does my component render twice in development?

React Strict Mode, which is on by default in the App Router since Next.js 13.5.1, intentionally double-invokes renders and effects in development to surface impure code and missing cleanup. Production builds render once.

Does disabling reactStrictMode improve production performance?

No. Strict Mode is a development-only feature and its extra passes never run in a production build, so setting it to false changes nothing about deployed behaviour or speed.

How do I adopt Strict Mode in an app that is not ready for it?

Migrate incrementally. Rather than leaving it off across the whole project, wrap the components or route subtrees that are ready in <React.StrictMode> and expand the coverage as you fix effect cleanup.

More examples

What the doubled effect is telling you

The first effect looks fine until Strict Mode mounts, unmounts and remounts the component, leaving two intervals running and the counter jumping by two per second. The fix is not to disable Strict Mode: the missing clearInterval is a genuine leak that persists in production, where it simply goes unnoticed because the double mount never happens.

Example · typescript
'use client'

import { useEffect, useState } from 'react'

// BROKEN: no cleanup, so Strict Mode's mount/unmount/remount leaves two
// intervals running and the counter climbs by two per second.
// In production only one runs -- and it still leaks on unmount.
export function BrokenTicker() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    setInterval(() => setCount((c) => c + 1), 1000)
  }, [])

  return <p>{count}</p>
}

// FIXED: the cleanup tears down the first interval, so the second mount
// is harmless and the counter climbs by one per second.
export default function Ticker() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const id = setInterval(() => setCount((c) => c + 1), 1000)
    return () => clearInterval(id)
  }, [])

  return <p>{count}</p>
}

Opting out incrementally instead of globally

Turning the flag off applies to the whole application. The documented alternative is to migrate page by page: leave the global setting alone, or disable it once, and wrap the subtrees that are ready in <StrictMode> so new code keeps the checks while legacy screens are cleaned up.

Example · typescript
// next.config.ts -- last resort, and only while migrating
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  reactStrictMode: false,
}

export default nextConfig

// app/dashboard/layout.tsx -- preferred: re-enable per subtree
import { StrictMode } from 'react'

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <StrictMode>{children}</StrictMode>
}

Discussion

  • Be the first to comment on this lesson.