If you’re not familiar with React StrictMode, you’re probably not using a framework like Next.js that enables it by default. Strict Mode aims to help you find bugs during development. But it feels more like a band-aid that can reveal bugs or hide them, especially when mixing with server-side rendering (SSR).
In this post, I will assume you’re using SSR and Strict Mode too as they are usually enabled by default with Next.js.
Enter the rabbit hole#
Now let’s suppose you write the following component, that renders a simple <time /> markup for a given date. Nothing fancy:
export default function LocalTime({ date }: { date: Date }) {
const isoString = date.toISOString();
return <time dateTime={isoString}>{date.toLocaleString()}</time>;
}Let’s say you also have a simple home page, in your typical React application:
import LocalTime from "./local-time";
export default function Home() {
return (
<main>
It is <LocalTime date={new Date("02/03/2026, 10:00 AM")} />
</main>
);
}If your application uses SSR, what you will see always depends on your locale and on the time zone of the server; in this case, with an en-US locale and a UTC timezone, the server will render the following page, even in development:
It is 2/3/2026, 10:00:00 AMHowever, that’s usually not what you want for your clients. Let’s say your client lives in Singapore, they should expect to see a time in a different timezone (GMT+8 in the case of Singapore). If you’re a bit experienced, you know your component must run on the client, and there’s a "use client;" directive in React that ensures a component can be an entry point to client rendering. You update the LocalTime component:
"use client"; // new directive
export default function LocalTime({ date }: { date: Date }) {
const isoString = date.toISOString();
return <time dateTime={isoString}>{date.toLocaleString()}</time>;
}Your development server hot-reloads and this time you see:
It is 2/3/2026, 6:00:00 PMSuccess! However, when you refresh the page, you notice there’s a flicker: the UTC time shows first, then the local time appears. You pull up the console, and you see a pretty large error message:
Uncaught Error: Hydration failed because the server rendered text didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.We’ve hit a classic issue with React SSR/CSR: hydration mismatch due to a time zone difference between the server and the client. And more importantly, you might not have noticed that error at all. The flicker might not have caught your attention, you might not have looked at the console.
You might just have deployed it to production. And to your surprise, once in production the page would show the UTC time only. It seems the component did not run on the client. Not a great user experience:
It is 2/3/2026, 10:00:00 AMIn reality, there was no reason for your component to re-render on the client again after hydration. This happened in development only due to React Strict Mode, so we’ve been led down the wrong path; thinking the application worked where it didn’t. You think this example is contrived? At Canterly, we have already faced 3 scenarios similar to this one; each time React Strict Mode was hiding an issue.
The reasoning behind Strict Mode#
React brings a flavor of functional programming (FP) to UI. In FP, when the output of a function is defined only by its input, and it has no side effects, we call it a pure function. The React equivalent means that the rendering of a component should be idempotent: it depends only on the component properties, its state and its context. These 3 sources of information are the rendering inputs in React; you should not use any other inputs.
That is not to say React does not handle side effects. However, you should confine side effects to events handlers, and in case you can’t find a suitable event handler for your side effect, React dedicates a whole phase of its update to side effects which you declare in the use*Effect() hooks. The steps below are an over-simplification of an update with React (more about it here):
- If there are no updates to component inputs, go to the last step;
- Render updated components;
- Run side effects;
- Return to the top;
- Commit changes to the DOM.
Idempotency allows React to track component changes by looking only at their inputs. However, it’s easy to break idempotency if you unknowingly use an external input, modify properties incorrectly or rely on a variable during rendering that is not provided through its properties, state or context.
To help uncover these issues, React introduced Strict Mode. Wrapping your component in <StrictMode> adds an extra render on mount and after the side effects run, to check that rendering was not impacted:
- If there are no updates to component inputs, go to the last step;
- Render updated components;
- Run declared side effects;
- Strict mode: render all components once more and check there are no differences with the output in 2.
- Return to the top;
- Commit changes to the DOM.
If your React component is pure, then that extra render should be idempotent and similar to the first.
False positives with Strict Mode#
Returning to our example: the first server-side render produces an incorrect UTC time, which could have been caught if it was not immediately redrawn by the extra Strict Mode render that occurred when React mounted the component. Instead, the developer saw the local time.
This little example illustrates that this approach is just as likely to catch a bug as to hide one, since Strict Mode can lead to false positives. Especially if you’re not careful with checking hydration issues.
It’s worth noting that a more careful developer could also catch the hydration mismatch. And they might be tempted to address the locale & time zone hydration mismatch by simply silencing it, since this component does not have any children:
"use client";
export default function LocalTime({ date }: { date: Date }) {
const isoString = date.toISOString();
return (
<time dateTime={isoString} suppressHydrationWarning>
{date.toLocaleString()}
</time>
);
}Perhaps unexpectedly, once the component uses the property suppressHydrationWarning, the UTC time rendered server side re-appears and is not replaced by the local time in development. Even in the presence of Strict Mode, the component is not regenerated again. This could have revealed the error again; but requires employing a property that is usually discouraged.
Beyond Strict Mode#
Since React 18, the React Compiler has grown a new linter. I highly recommend the React Compiler ESLint plugin. While it does not address all the objectives of Strict Mode, there’s a significant amount of overlap between the two tools. In my own usage, I have found that the linter helps me better with identifying violations of the Rules of React.
Unfortunately, when it comes to our little example, the linter does not identify that date.toLocaleString() requires a time zone and a locale that are not declared as input to the component. The linter is still a work in progress; I hope that these limitations will eventually be addressed.
I will leave you with a version of the component that follows the Rules of React (note that this version will not scale well if you have a lot of components to render):
"use client";
import { useEffect, useEffectEvent, useState } from "react";
export default function LocalTime({ date }: { date: Date }) {
const [formattedDate, setFormattedDate] = useState(
// Fix a locale and a time zone for idempotency
date.toLocaleString("en-US", { timeZone: "UTC" }),
);
const isoString = date.toISOString();
const onMount = useEffectEvent(() => {
setFormattedDate(date.toLocaleString());
});
useEffect(() => {
onMount();
}, []);
return <time dateTime={isoString}>{formattedDate}</time>;
}This may seem a little complicated for a component that just renders a date without any interactions; but remember that there’s a hidden side effect in the original rendering. And unfortunately, in this specific scenario, Strict Mode put us on the wrong path.
One of the selling points of Strict Mode is that it will help reveal the issues with components that violate the Rules of React. But I would argue that if the issues can’t be found without Strict Mode, then you might as well just ignore them: your component is probably too simple to care about them. Otherwise, you should always pay attention to hydration issues and ensure your project uses the React Compiler ESLint plugin.