"Flickering" looks like one bug but is really two, with completely different causes. Tell them apart first and you're 90% done.

Step 1: Which kind of flicker is it?

  • A. One flash on load: the moment the page appears you see a white/wrong screen, then it switches — e.g. it flashes light before dark mode kicks in, or shows a logged-out view for a split second before your real screen. → Usually a hydration mismatch or theme flash.
  • B. Nonstop flicker: the screen keeps shaking or feels like it's refreshing forever, the tab gets slow, the fan spins up. → Usually an infinite re-render loop.

To be sure, open browser DevTools with F12Console tab and read the red error:

  • Hydration failed / Text content did not matchA
  • Maximum update depth exceededB

---

B. Nonstop flicker: infinite re-render loop (most common)

If the console says Maximum update depth exceeded, that's it. The cause is almost always the same — a useEffect sets state, and that new state re-triggers the same useEffect, forever.

Paste this to your AI (Cursor / v0 / Bolt / Lovable)

> The console shows Maximum update depth exceeded and my screen flickers nonstop. It looks like a useEffect calls setState with a wrong dependency array, causing an infinite loop. Find which useEffect is the culprit and fix it — either correct the dependency array or use the updater form (setCount(c => c + 1)). Fix the root cause, don't just hide the warning.

If you can read the code (two official React fixes)

1) Update based on the previous value — drop the dependency

js

// Problem: count changes every run, so the effect re-runs forever
useEffect(() => {
  setCount(count + 1);
}, [count]);

// Fix: updater function + empty deps
useEffect(() => {
  setCount(c => c + 1);
}, []);

2) Don't pass an object/function as a dependency — build it inside the effect

js

// Problem: a new options object every render → infinite loop
const options = { serverUrl, roomId };
useEffect(() => {
  createConnection(options);
}, [options]);

// Fix: build it inside the effect
useEffect(() => {
  const options = { serverUrl, roomId };
  createConnection(options);
}, [roomId, serverUrl]);

Quick check: suspect any useEffect(() => { ... }) with no dependency array at all first — without [] it runs on every render and loops easily.

---

A. One flash on load: hydration mismatch / theme flash

With frameworks like Next.js, the server pre-renders HTML and the browser renders again on first paint. If those two don't match, the screen jumps once. The console shows Text content did not match / Hydration failed.

Common causes (from the Next.js docs)

  • Using window, localStorage, or Date() directly in your rendering code
  • Branching the UI on checks like typeof window !== 'undefined'
  • Reading the dark/light theme only in the browser, so the first paint is the wrong theme
  • A browser extension (translator, dark-mode extension) editing the HTML → test in an incognito window to confirm

Fix 1: Defer browser-only parts with useEffect

Render the same thing on server and first client paint, then switch.

jsx

import { useState, useEffect } from 'react'

export default function App() {
  const [isClient, setIsClient] = useState(false)
  useEffect(() => { setIsClient(true) }, [])
  return <h1>{isClient ? 'Browser-only content' : 'Prerendered'}</h1>
}

Fix 2: Turn off server rendering for one component (maps, charts, browser-only widgets)

jsx

import dynamic from 'next/dynamic'
const NoSSR = dynamic(() => import('../components/no-ssr'), { ssr: false })

Fix 3: For values that must differ (clocks, dates), silence just that element

jsx

<time dateTime="2016-10-25" suppressHydrationWarning />

Note: this is an escape hatch that works only one level deep, and React won't patch the mismatched text — don't overuse it.

If it's a dark-mode flash (white → dark)

With next-themes, add suppressHydrationWarning to the top-level <html> tag, and only render theme-dependent UI after the component has mounted.

jsx

// layout.jsx (app directory)
<html lang="en" suppressHydrationWarning>

// A component that renders differently by theme
const [mounted, setMounted] = useState(false)
useEffect(() => { setMounted(true) }, [])
if (!mounted) return null

next-themes injects a script that runs before the page renders, so by default there should be no flash. If it still flickers, you're most likely missing one of the two above (the suppressHydrationWarning, or rendering before mount).

---

A universal message to hand your AI

> My screen [flashes once on load / flickers nonstop]. The console error is [paste the exact message]. Find the component causing it and fix it the official way. Fix the root cause — don't just hide the warning.

When there's no confirmed fix yet (what you can do now)

  • Disable browser extensions (translators, dark-mode add-ons) and check in an incognito window. If it stops, the extension was the culprit.
  • Copy the full console error and paste it to your AI as-is — the first line usually points to the exact file and line number.