⚠️ Malware / Trojaner / VirenKein Trojaner: Behörden überwachen WhatsApp-Chats mit einem einfachen Trick - CHIP(05.09.2026 um 18:00 Uhr)
📰 IT Security NachrichtenDie Cyberangriffe der KW36/2026 im Überblick - Computer Weekly(05.09.2026 um 18:42 Uhr)
⚠️ Malware / Trojaner / VirenYou don't want this Sleepwalker backdoor on your Windows machine(24.08.2026 um 23:39 Uhr)
⚠️ Malware / Trojaner / VirenCrooks push Mac malware through fake OpenAI Codex ads(25.08.2026 um 11:15 Uhr)
🔧 AI Nachrichten OpenAI explains how its naughty AI agents attacked Hugging Face(27.08.2026 um 01:45 Uhr)
⚠️ Malware / Trojaner / VirenATF responds to 'major' cybersecurity incident after ransomware gang's claims(27.08.2026 um 17:25 Uhr)
🔧 AI Nachrichten Industry that built the problem offers to sell you the solution(28.08.2026 um 13:01 Uhr)
⚠️ Malware / Trojaner / VirenKein Trojaner: Behörden überwachen WhatsApp-Chats mit einem einfachen Trick - CHIP(05.09.2026 um 18:00 Uhr)
📰 IT Security NachrichtenDie Cyberangriffe der KW36/2026 im Überblick - Computer Weekly(05.09.2026 um 18:42 Uhr)
⚠️ Malware / Trojaner / VirenYou don't want this Sleepwalker backdoor on your Windows machine(24.08.2026 um 23:39 Uhr)
⚠️ Malware / Trojaner / VirenCrooks push Mac malware through fake OpenAI Codex ads(25.08.2026 um 11:15 Uhr)
🔧 AI Nachrichten OpenAI explains how its naughty AI agents attacked Hugging Face(27.08.2026 um 01:45 Uhr)
⚠️ Malware / Trojaner / VirenATF responds to 'major' cybersecurity incident after ransomware gang's claims(27.08.2026 um 17:25 Uhr)
🔧 AI Nachrichten Industry that built the problem offers to sell you the solution(28.08.2026 um 13:01 Uhr)

26 🕛 kürzlich 5 Min Lesezeit
0

Why My Analytics Was Logging Every Page Visit Twice (And How I Fixed It)

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

I built a custom analytics system into my portfolio backend — a Django REST API that records page visits — and wired the React frontend to call it whenever someone lands on a project or blog post detail page. It worked, except for one problem: every visit was logged twice.



This is the story of chasing the wrong root cause, wasting time on a fix that didn't work, and landing on a dead-simple solution that lives outside React entirely.






The Setup



The backend is a Django REST API with a PageVisit model. The frontend calls a single endpoint whenever a user lands on a detail page:




CODE
// POST /api/analytics/track/
{ "page_type": "project", "object_id": 33 }






On the React side, the call lives in a useEffect inside ProjectPage:




CODE
useEffect(() => {
if (project?.id) api.trackPageView('project', Number(project.id));
}, [project?.id]);






The project data comes from a useProject hook that reads from localStorage cache first, then fetches from the API.






The Symptom



After visiting two project pages, the admin showed this:




CODE
637  Project  33
636 Project 33
635 Personal Projects -
634 Project 30
633 Project 30






Two records per project visit, every single time.






First Suspect: React StrictMode



My first instinct was React StrictMode. In development, <React.StrictMode> intentionally double-invokes effects to surface side effects — it mounts the component, runs cleanup, then remounts it. If the effect fired on both the original mount and the simulated remount, that would explain exactly two records per visit.



The standard fix is a useRef guard: store the last tracked ID in a ref, and skip the call if it matches.




CODE
const trackedProjectId = useRef<string | null>(null);

useEffect(() => {
if (project?.id && trackedProjectId.current !== String(project.id)) {
trackedProjectId.current = String(project.id);
api.trackPageView('project', Number(project.id));
}
}, [project?.id]);






The logic: on the first run, the ref is null, so we track and set it to "33". On StrictMode's simulated remount, React preserves state and refs, so the ref is still "33" — the condition is false, and the second call is skipped.



I pushed this fix. The visits still doubled.






The Real Cause: A Full Remount, Not a Preserved One



React StrictMode's "double-invoke effects" behavior comes in two flavors. In the version I was thinking of, state and refs are preserved between the simulated unmount and remount — so a ref guard works perfectly. But if the component is fully remounting (fresh component instance, all state and refs reset to initial values), the ref starts as null again on every mount, and the guard is useless.



That's what was happening here. The component wasn't getting StrictMode's gentle "simulate and preserve" treatment — it was being torn down and recreated. Each fresh mount started with trackedProjectId.current = null, saw a project with id = 33, passed the guard, and fired the tracking call.



I confirmed this by looking at the hook. The useProject hook initializes from localStorage cache:




CODE
function useFetchSingle<T>(fetcher: () => Promise<T>, cacheKey: string) {
const [data, setData] = useState<T | null>(() => readCacheSingle<T>(cacheKey));
// ...
useEffect(() => {
fetcher().then((res) => { setData(res); /* write cache */ });
}, []);
}






On a full remount, useState runs its initializer again. If the project is cached, data starts as the cached project immediately — project.id is available on the very first render, so the tracking effect fires right away. Then the component remounts again from scratch, the same initializer runs, the same project loads from cache, and the effect fires again.



A ref-based guard can't survive a full remount. By definition, refs reset to their initial value on a new component instance.






The Fix: Move Deduplication Outside React



If the component lifecycle can't be trusted to hold state across mounts, the guard needs to live somewhere that can: a module-level variable. In JavaScript, module-level variables are initialized once per page load and persist for the lifetime of the session, completely independent of component mount/unmount cycles.



I added a Set at the top of api.ts, outside the api object:




CODE
const _tracked = new Set<string>();

export const api = {
// ...
trackPageView: (
pageType: 'professional_projects' | 'personal_projects' | 'audio_works' | 'blog_post' | 'project',
objectId?: number,
) => {
const key = `${pageType}:${objectId ?? ''}`;
if (_tracked.has(key)) return Promise.resolve();
_tracked.add(key);
setTimeout(() => _tracked.delete(key), 3000);
return fetch(`${BASE_URL}/analytics/track/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ page_type: pageType, ...(objectId != null && { object_id: objectId }) }),
}).catch(() => {});
},
};






How it works:




  1. On first call with "project:33", the key isn't in the set — we add it and fire the request.

  2. Any duplicate call within the next 3 seconds hits _tracked.has(key) and returns immediately without a network request.

  3. After 3 seconds, the key is removed, so legitimate return visits (user navigates away and comes back) are recorded correctly.



The 3-second window is long enough to absorb any double-mount behavior (which happens within milliseconds), and short enough that it doesn't suppress real repeat visits.






Component Code Stays Clean



Because the deduplication now lives in api.ts, the component code doesn't need any guard logic:




CODE
// ProjectPage.tsx
useEffect(() => {
if (project?.id) api.trackPageView('project', Number(project.id));
}, [project?.id]);









CODE
// BlogPostPage.tsx
api.blogPost(lookup).then((data) => {
setPost(data);
if (data.id) api.trackPageView('blog_post', Number(data.id));
// ...
});






Clean call sites, no leaking implementation details into components.






Takeaway



When a useRef guard doesn't fix a double-invocation problem, the component is fully remounting — not doing React StrictMode's state-preserving remount. In that case, any component-level guard will be reset on every mount and is useless.



The fix is to move the deduplication to a layer that outlives the component: a module-level variable. It's outside React's rendering model entirely, so no mount/unmount cycle can touch it.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 42%
🟡 In Evaluierung 28%
🟢 Keine Auswirkung 12%
Spannende Innovation 18%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
How to Evaluate Live &amp; Voice Agents in ADK
1 Quelle
Artificial Analysis overhauls its Intelligence Index after GPT-6 Astra scoring drew skepticism
1 Quelle
Serious vulnerability threatens tens of thousands of Exchange servers
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Why My Analytics Was Logging Every Page Visit Twice (And How I Fixed It)

Thematisch verwandte Begriffe: Analytics, Logging, Every, Page · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...