Short Answer
Strict Mode changes development checks, not production behavior. It can make components re-render an extra time, rerun Effects with setup/cleanup, and rerun ref callback functions to expose impure rendering and missing cleanup.
It does not directly change what production users see. If production still has duplicate requests or duplicate subscriptions, the root cause is usually application side effects, dependency handling, caching, or where the request is triggered.
The Next.js App Router defaults to Strict Mode. The official docs state that since Next.js 13.5.1, Strict Mode is enabled by default with the App Router; Pages Router projects can configure it with reactStrictMode in next.config.js.
What Behavior Looks Different?
| Symptom | Development cause | What to inspect |
|---|---|---|
| Component functions or console logs appear twice | React performs an extra re-render to find render-phase side effects | Requests, global writes, mutations of props or state during render |
useEffect appears to run twice |
Effect setup and cleanup are intentionally rerun | Missing cleanup, AbortController, unsubscribe logic |
| Listeners, subscriptions, or timers duplicate | Mount cleanup and setup are exercised again | removeEventListener, clearInterval, unsubscribe |
| ref callback or third-party widgets initialize twice | Ref callbacks are rerun to reveal missing ref cleanup | DOM plugins, charts, maps, dispose/destroy methods |
How to Configure reactStrictMode in Next.js
If you use the App Router, you usually do not need to set reactStrictMode: true explicitly. If you use the Pages Router, or your team wants the intent documented in config, put it in next.config.js.
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = nextConfig;
Do not permanently disable Strict Mode just because a development request appears twice. That symptom usually points to a real effect problem: non-idempotent setup, missing cleanup, non-cancelable requests, or stale responses.
Common Mistake: Fetching in an Effect Without Cleanup
Code like this often produces duplicate-looking requests or stale responses during development with Strict Mode:
useEffect(() => {
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => setUser(data));
}, [userId]);
A more resilient effect can be cleaned up when the component unmounts or the dependency changes:
useEffect(() => {
const controller = new AbortController();
async function loadUser() {
const res = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
const data = await res.json();
setUser(data);
}
loadUser().catch((error) => {
if (error.name !== "AbortError") {
throw error;
}
});
return () => controller.abort();
}, [userId]);
If you use React Query, SWR, or server-side data fetching in Next.js, handle duplicate request concerns through caching, deduping, and server/client boundaries instead of adding one-off flags inside components.
Debugging Checklist
- Keep render pure: no requests, global writes, direct prop mutation, or state mutation during render.
- Clean up subscriptions, timers, event listeners, and third-party instances created inside
useEffect. - Make requests cancelable, ignore stale responses, or use a data layer with caching and deduping.
- Clean up DOM resources initialized from ref callback functions.
- Use
reactStrictMode: falseonly temporarily for legacy migration or isolating a third-party compatibility issue.
FAQ
Does Strict Mode reduce production performance?
No. The extra re-render, Effect rerun, and ref callback rerun behavior is development-only.
Why does the issue disappear when I turn Strict Mode off?
That usually means Strict Mode exposed a side-effect bug. Turning it off hides the symptom, but missing cleanup or impure render logic can still surface during route changes, rapid mount/unmount flows, concurrent rendering, or real user interactions.
What is different between App Router and Pages Router?
The App Router currently defaults to Strict Mode. Pages Router projects generally configure the behavior explicitly with reactStrictMode in next.config.js.