Demo, all content is generated
Question

useEffect fetch runs twice and my analytics counts every visit double

Open · 132 views · asked by arash · edited

I have this in my dashboard page (client component):

useEffect(() => {
  track("dashboard_view");
  loadStats();
}, []);

Every page view shows up twice in my analytics. Empty dependency array, so it should run once? Claude Code says it's React 18 StrictMode and I should remove reactStrictMode from next.config. That feels like a hack.

What I’ve tried

Checked the network tab, two identical requests. Added a useRef flag which works but feels wrong.

Comment
Is that in dev (npm run dev) or in the deployed site? mei_lin · edited

3 answers

mei_lin · edited

If prod still doubles, something mounts that component twice or remounts it. Common ones:

  • the tracker is in layout.tsx and in page.tsx
  • a key on a parent that changes after load (e.g. key={user?.id} going from undefined to an id) remounts the whole subtree
  • a redirect: /dashboard/dashboard?tab=overview counts as two views if your tracker listens to route changes too

Log console.count("dashboard mount") in the component and a console.trace() inside the effect, then look at the stack on the second call.

Comment
There IS a key on the layout wrapper that uses the session. Will test tonight. arash · edited
That'll be it. Session is undefined on first render, then filled in, new key, full remount. dev_ana · edited
dev_ana · edited

In development, StrictMode mounts, unmounts and mounts every component once more on purpose, to surface effects that don't clean up. That's the double call you see locally. Don't turn it off. It doesn't happen in a production build.

Check with npm run build && npm start. If you see one call there, you're done.

For fetching data, the useRef guard is a band-aid. Better: fetch in a server component or with a library that dedupes (SWR / TanStack Query).

Comment
Checked the production build, still 2 events per visit. So not StrictMode? arash · edited
thandeka · edited

For page views specifically, most analytics tools (PostHog, Plausible, Vercel Analytics) have a Next.js integration that tracks route changes for you. Then there's no effect in your page at all and this whole class of double counting goes away.

Comment