{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Nishikanta Ray",
  "title_listed": "How I Finally Finished InsightTrack — My Abandoned Self-Hosted Analytics Platform",
  "keywords": "",
  "domain": "机器学习",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://dev.to/nishikantaray/how-i-finally-finished-insighttrack-my-abandoned-self-hosted-analytics-platform-1k7l",
  "publish_date": "2026-05-24",
  "corpus_stage": "",
  "notes": "",
  "id": "1eed7a7046ae",
  "collected_at": "2026-10-02T14:04:48",
  "title": "How I Finally Finished InsightTrack — My Abandoned Self-Hosted Analytics Platform - DEV Community",
  "body_markdown": "This is a submission for the\nGitHub Finish-Up-A-Thon Challenge\nProject Details\nName\nInsightTrack\nGitHub\ngithub.com/nishikantaray/insighttrack\nStack\nReact 18 · Vite 5 · Tailwind CSS · Express 4 · Node.js 20 · PostgreSQL · DuckDB\nDemo\nWhat I Built\nInsightTrack\nis a self-hosted, privacy-first web analytics platform — my personal answer to the question\n\"Why do I have to send all my users' data to Google just to know how many people visited my site?\"\nIt tracks pageviews, sessions, conversions, user flows, and engagement — all on your own infrastructure, with no cookies and no third-party data sharing. The architecture uses\nPostgreSQL\nfor all writes and\nDuckDB\nas an embedded columnar OLAP engine for analytics reads, achieving 10-100× the query speed of a traditional row-store setup.\nI started InsightTrack during a long weekend when I was frustrated with the privacy implications of Google Analytics and the price tags on alternatives like Plausible and Fathom. The idea was simple: build a clean, self-hostable dashboard that you could drop a\n<script>\ntag into and be live in 5 minutes.\nI got a working v1 out. It had a dashboard, auth, an onboarding wizard, basic charts, funnels, and a Docker Compose file that actually ran. Then I stopped. The project sat in a local folder for months —\nalmost\ngood enough, but not quite. The DuckDB sync was fragile. There was no dark mode on half the pages. Dozens of planned features were never built. Query performance fell apart past 30 days of data.\nThe bones were good. I just never gave it the finish it deserved.\nHow it works\nYour Website\n  └── analytics.js (tracking script, ~2 KB)\n        │  POST /api/track/event\n        ▼\n  Express API (Node.js)\n        │  Writes\n        ▼\n  PostgreSQL ──── Incremental sync worker (every 5 min)\n                        │\n                        ▼\n                    DuckDB (OLAP reads)\n                        │\n                        ▼\n  React Dashboard ◀─── GET /api/analytics/*\nEnter fullscreen mode\nExit fullscreen mode\nWrite path\n: The tracking script sends events via\nnavigator.sendBeacon\n. Express validates and inserts into PostgreSQL. Response is immediate — DuckDB is never in the write path.\nSync path\n: A high-watermark worker copies new rows from PostgreSQL → DuckDB incrementally, never rebuilding from scratch.\nRead path\n: All 17 analytics endpoints read exclusively from DuckDB, running columnar aggregations 10-100× faster than a row-store query.\nAuth\n: JWT-based authentication. All analytics routes are protected. Only\n/api/track/*\nand\n/api/auth/*\nare public.\nPostgreSQL schema (source of truth)\nTable\nPurpose\nevents\nRaw event log — pageviews, clicks, custom events\nsessions\nSession metadata — duration, page count, entry/exit pages\nsites\nRegistered websites and their tracking keys\nfunnels\nFunnel definitions with ordered steps\ndaily_stats\nPre-aggregated daily rollups\nusers\nDashboard user accounts\nDemo\nLanding Page\nHero\nFeatures Grid\nHow It Works\nTech Stack\nComparison Table vs Google Analytics\nFooter & CTA\nFull landing page\n\n![Full Landing Page](https://raw.githubusercontent.com/nishikantaray/insighttrack/main/screenshots/01-landing-full.png)\nAuthentication & Onboarding\nLogin\nRegister\nDashboard\nLight Mode\nDark Mode\nKPI Cards + Sparklines\nTraffic Charts\nAnalytics Pages\nUser Flow (Sankey)\nReal-Time\nFunnels\nSettings\nTop Pages\nProfile\nDocumentation\nAPI Reference\nSidebar\nDark Landing\nStack:\nReact 18 + Vite 5 + Tailwind CSS 3 · Express 4 + Node.js 20 · PostgreSQL + DuckDB · Docker Compose\nThe Comeback Story\nWhere it was (v1)\nv1 had the core loop working: embed a tracking script → events land in PostgreSQL → a sync worker copies rows into a flat DuckDB table → the React dashboard reads from DuckDB. The fundamental architecture was sound.\nWhat v1 shipped:\nDashboard with 4 KPI cards (Visitors, Pageviews, Bounce Rate, Avg. Session)\nTraffic and pageview time-series charts (7d / 30d / 90d presets)\nTop pages table\nBasic conversion funnels\nDevice and country breakdown\nJWT authentication, register/login, onboarding wizard with tracking snippet\nDocker Compose deployment\nBut it had serious rough edges.\nPerformance wall.\nThe DuckDB sync layer used a single flat table. On 90+ days of data, a KPI query scanned every row — ~620 ms on a local M1 machine. There was no partitioning, no archival strategy, and a crash meant rebuilding the entire table from scratch.\nHalf-finished features.\nThere was a long list of planned but never-built ideas: UTM campaign tracking, comparison mode on charts, user flow analysis, a live visitor map on the Realtime page, engagement tracking, goal tracking, audience analytics, export to CSV/JSON — all unshipped.\nUX gaps.\nThe Settings page was a single form. The Documentation page was a placeholder. Half the pages lacked dark mode. There was no contextual help anywhere — a new user landing on the Funnels page had no idea what they were looking at.\nNo marketing story.\nThe project had no landing page, no comparison table, no \"why this instead of Google Analytics\" pitch. It existed but didn't explain itself.\nWhat changed (v1 → v2)\n1. Hot+Cold Analytics Architecture\nThis was the most technically significant change. I replaced the flat DuckDB sync with a\ntwo-tier data lake\n:\nHot tier\n— DuckDB in-memory tables holding the last 30 days of events and sessions\nCold tier\n— Hive-partitioned Parquet files on disk for historical data, organized by\nsite_id\nand\nevent_date\ndata-lake/events/\n  site_id=abc/\n    event_date=2026-01-01/\n      part-0001.parquet\n    event_date=2026-01-02/\n      part-0001.parquet\nEnter fullscreen mode\nExit fullscreen mode\nA\ntransparent\nUNION ALL\nview\nsits on top of both tiers, so every existing dashboard query kept working without modification:\nCREATE\nOR\nREPLACE\nVIEW\nevents\nAS\nSELECT\n*\nFROM\nevents_hot\nUNION\nALL\nSELECT\n*\nFROM\nread_parquet\n(\n'data-lake/events/site_id=*/event_date=*/part-0001.parquet'\n,\nhive_partitioning\n=\ntrue\n);\nEnter fullscreen mode\nExit fullscreen mode\nA\ndual-watermark incremental sync worker\nruns every 5 minutes, fetching only new rows from PostgreSQL using high-water marks (\nlast_event_id\nfor events,\nlast_synced\ntimestamp for sessions), then routing them to the correct tier based on age.\nResult: 90-day queries dropped from ~620 ms → ~25 ms. A 25× speedup.\nRecovery after a crash now resumes from the watermark instead of rebuilding the entire table.\n2. Tracking Script — what it captures automatically\nThe auto-generated\nanalytics.js\n(~2 KB) handles everything without configuration:\nData Point\nHow\nPage URL & path\nwindow.location.href\nReferrer\ndocument.referrer\nBrowser, OS, device\nnavigator.userAgent\n+ screen width heuristics\nCountry\nTimezone via\nIntl.DateTimeFormat\nSession\nAuto UUID in\nsessionStorage\nAnonymous user ID\nRandom UUID in\nlocalStorage\n— no cookies\nOutbound link clicks\nclick\nevent listener\nScroll depth\n25/50/75/100% milestones on page unload\nTime on page\nSeconds per page sent on unload\nHeatmap clicks\nx/y coordinates + CSS selector of every click\nRage clicks\n3+ rapid clicks on same element within 1 second\nCore Web Vitals\nLCP, FID, CLS, INP, TTFB via\nPerformanceObserver\nJS errors\nwindow.onerror\n+\nunhandledrejection\ncapture\nSite search\nForm submit interception\nUTM parameters\nutm_source\n,\nutm_medium\n,\nutm_campaign\n,\nutm_term\n,\nutm_content\nSPA navigation\npopstate\n+\npushState\n/\nreplaceState\nlisteners\nPrivacy built-in: DNT (\nnavigator.doNotTrack\n) and GPC (\nnavigator.globalPrivacyControl\n) are respected — if either is set, no data is collected.\nCustom events are available via\nwindow.analytics.track('event_name', { ...properties })\n.\n3. Complete v2 feature set\nCore Dashboard\nThe main dashboard now has comparison mode — every chart can overlay the current period against the previous period. All 4 KPI cards have sparkline mini-charts. Date picker supports custom ranges in addition to presets.\nReal-Time\nActive visitor count (users in last 5 minutes, with pulsing indicator)\nInteractive world map pinpointing active visitors by country\nLive event stream — feed of the last 50–100 events as they happen\nActive pages ranked by current visitor count\nReal-time device breakdown\nEngagement Analytics\nFour metrics measured automatically by the tracking script:\nMetric\nWhat it shows\nScroll Depth\nPer-page average scroll %; milestone table (25/50/75/100%)\nClick Heatmaps\nx/y dot map per page, top clicked elements ranked\nRage Click Detection\nPages and elements triggering 3+ rapid clicks within 1 second\nTime on Page\nAverage, median, min/max read time per page\nUser Flow & Funnels\nSankey diagram showing entry pages → transitions → exit pages with proportional path thickness\nMulti-step funnel builder (e.g., Landing → Signup → Checkout → Purchase) with per-stage drop-off and conversion rate\nSaved funnel definitions\nConversion & Goals\nGoal types: Pageview (URL match), Event (custom event), Duration (time on page), Scroll Depth (threshold)\nA/B test tracking — create experiments, assign variants, compare conversion rates\nRevenue attribution — attach dollar values to purchase events, see total revenue and average order value by traffic source\nAudience Analytics\nNew vs. returning visitors — pie/donut chart + daily trend\nCohort analysis — heatmap grid of Day 1 / Day 7 / Day 30 retention by first-visit week\nVisitor segments — filter by device, browser, OS, country and compare KPIs side by side\nContent Analytics\nEntry pages — top landing pages ranked by visitor count and entry rate\nExit pages — top departure pages ranked by exit count and exit rate\nPage flow — where visitors go after each page\nSite search — queries captured from form submit interception with frequency counts\nAcquisition Analytics\nFull UTM campaign dashboard — source, medium, campaign, term, content breakdowns with visitor counts, bounce rates, and session duration\nSocial media tab — automatic classification of Facebook, Twitter/X, LinkedIn, Instagram, YouTube, Reddit, Pinterest, TikTok traffic\nSearch keyword tracking — organic and paid keywords driving traffic\nUTM link builder — generate tagged URLs directly in the dashboard\nPerformance Monitoring\nCore Web Vitals collected automatically:\nMetric\nGood\nNeeds Improvement\nPoor\nLCP\n(Largest Contentful Paint)\n≤ 2.5s\n≤ 4.0s\n> 4.0s\nFID\n(First Input Delay)\n≤ 100ms\n≤ 300ms\n> 300ms\nCLS\n(Cumulative Layout Shift)\n≤ 0.1\n≤ 0.25\n> 0.25\nINP\n(Interaction to Next Paint)\n≤ 200ms\n≤ 500ms\n> 500ms\nTTFB\n(Time to First Byte)\n≤ 800ms\n≤ 1800ms\n> 1800ms\nColor-coded metric cards (green/yellow/red) + per-page breakdown table.\nJavaScript error tracking: message, source file, line/column, stack trace, error trend chart over time.\nReporting & Export\nData export — JSON/CSV download for KPI, traffic, pages, sources data; PNG export for charts\nAnnotations — pin notes to specific dates on charts (e.g., \"launched new homepage\") with category badges (deployment/marketing/incident)\nScheduled reports — configure daily/weekly/monthly email delivery with recipient list\nCustom dashboards — save personalized widget layouts\nMulti-Site Management\nAdd and manage unlimited websites under one account\nPer-site tracking snippets with one-click copy\nPer-site data retention policies (30/90/180/365 days)\nAutomated cleanup of expired events and sessions\n4. UX and documentation overhaul\nSettings page\nrebuilt as a 4-tab layout: General, Notifications/Alerts, Site Manager, and Privacy\nAll 13 analytics pages\ngained a collapsible\nPageNote\nbanner with a business explanation and a developer tip per page\nInfoTooltip\non every\nMetricCard\nand\nChartCard\nDocumentation page\nrebuilt with dual-audience tabs (Business Owner / Developer) and an interactive 4-view architecture diagram\nDark mode\nenforced consistently across every page and component\n5. Marketing landing page\nHero, features grid, 3-step \"How It Works\", tech stack cards, and a side-by-side comparison table vs. Google Analytics across 9 dimensions.\nBefore vs. After\nDimension\nv1 (Before)\nv2 (After)\n90-day query time\n~620 ms\n~25 ms (25×)\nDuckDB architecture\nSingle flat table, full scan\nHot (in-memory) + Cold (Parquet), UNION view\nSync on crash\nFull table rebuild\nResumes from dual watermark\nAnalytics pages\n7\n13\nTracking data points\n8\n19\nReal-Time page\nActive count only\nMap + event stream + device breakdown\nEngagement tracking\nNone\nScroll depth, heatmaps, rage clicks, time on page\nConversion & Goals\nFunnels only\nGoals, A/B testing, revenue attribution\nAudience analytics\nNone\nNew/returning, cohorts, segments\nContent analytics\nTop pages only\nEntry/exit pages, page flow, site search\nAcquisition analytics\nNone\nUTM campaigns, social, keywords, link builder\nPerformance monitoring\nNone\nCore Web Vitals + JS error tracking\nReporting\nNone\nExport, annotations, scheduled reports, custom dashboards\nMulti-site\nSingle site\nUnlimited sites with per-site settings\nComparison mode\nNone\nPrevious-period overlay on all charts\nExport\nNone\nCSV / JSON / PNG\nSettings page\nSingle form\n4-tab layout with site manager\nContextual help\nNone\nPageNote\n+\nInfoTooltip\non every page\nDark mode coverage\nPartial\nComplete\nMarketing landing page\nNone\nFull page with comparison table\nMy Experience with GitHub Copilot\nI'll be direct: I would not have shipped v2 without GitHub Copilot. Not because the individual problems were too hard — but because there were\nso many\nof them, and I was coming back to code I hadn't touched in months.\nThe architecture lift.\nThe Hot+Cold DuckDB design required a new sync worker, new schema migrations, new UNION views, and updates to every existing query file simultaneously. Copilot helped me work through design decisions conversationally — I'd describe the constraint (\"crash recovery must resume from a watermark, not rebuild the whole table\") and it would walk me through the dual-watermark pattern, flag edge cases I hadn't considered (what happens to a row that ages from hot to cold\nbetween\ntwo sync cycles?), and generate the implementation that fit the existing codebase style.\nThe feature sweep.\nGoing from 8 tracked data points to 19, from 7 pages to 13, from basic funnels to engagement tracking, cohort analysis, Web Vitals, and revenue attribution is a context-switching marathon. Copilot compressed the ramp-up time on each feature dramatically. When I opened the Acquisition page for the first time in months, Copilot immediately understood the existing data model, knew which UTM fields were already being captured in the tracking script, and scaffolded the campaign dashboard components that matched the existing\nuseAnalytics\nhook pattern — not generic code, but code that fit\nthis\nproject.\nThe consistency work.\nThe\nPageNote\nand\nInfoTooltip\nadditions touched 13 pages. Adding Core Web Vitals collection to the tracking script required coordinating between\nanalytics.js\n, the Express ingest route, the DuckDB schema, and the React performance page — four files that had to stay in sync. Copilot held the full context across all of them.\nThe documentation.\nWriting accurate technical docs for a system you know deeply is surprisingly tedious. Copilot drafted the architecture document for the hot/cold system from the actual code — referencing the real SQL, the real file paths, the real config variables — and I edited for clarity rather than writing from scratch.\nThe interaction style that worked best wasn't \"write this for me.\" It was:\nhere's the constraint, here's the existing pattern, here's where I'm confused.\nCopilot as a collaborator who has read your entire codebase is a genuinely different experience from Copilot as autocomplete. The finish-up phase of a project — juggling context across a dozen half-done things — is exactly where that kind of always-available, always-context-aware pair programmer pays off most.\nInsightTrack v2 is the project I always meant to ship. It just needed time, a clear plan, and a better way to work through the backlog.\nInsightTrack is open source — self-host your own privacy-first analytics in minutes.",
  "metadata": {
    "title": "How I Finally Finished InsightTrack — My Abandoned Self-Hosted Analytics Platform - DEV Community",
    "description": "This is a submission for the GitHub Finish-Up-A-Thon Challenge              Project... Tagged with devchallenge, githubchallenge.",
    "keywords": "devchallenge, githubchallenge, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/nishikantaray/how-i-finally-finished-insighttrack-my-abandoned-self-hosted-analytics-platform-1k7l",
    "og:title": "How I Finally Finished InsightTrack — My Abandoned Self-Hosted Analytics Platform",
    "og:description": "This is a submission for the GitHub Finish-Up-A-Thon Challenge              Project...",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06edsas7ilt0jd93xdmy.png",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@",
    "twitter:title": "How I Finally Finished InsightTrack — My Abandoned Self-Hosted Analytics Platform",
    "twitter:description": "This is a submission for the GitHub Finish-Up-A-Thon Challenge              Project...",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06edsas7ilt0jd93xdmy.png"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/1eed7a7046ae/code/body-000.txt",
      "chars": 396,
      "language": "text"
    },
    {
      "file": "attachments/1eed7a7046ae/code/body-001.txt",
      "chars": 135,
      "language": "text"
    },
    {
      "file": "attachments/1eed7a7046ae/code/body-002.txt",
      "chars": 201,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8pqgknn1i3ts4cjhjoh.png",
      "bytes": 72462,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/002.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8pqgknn1i3ts4cjhjoh.png",
      "alt": "Demo1",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2y6xjgmfuskdoj8qtw15.png",
      "bytes": 54980,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/003.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2y6xjgmfuskdoj8qtw15.png",
      "alt": "Demo 2",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pepz87hmrjv6eqn44z4.png",
      "bytes": 79280,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/004.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pepz87hmrjv6eqn44z4.png",
      "alt": "Demo 3",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw2t7vzkbtijhdwjvlsp.png",
      "bytes": 76688,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/005.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw2t7vzkbtijhdwjvlsp.png",
      "alt": "Landing Hero",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8l21mcdvr4j7zi3ivfnx.png",
      "bytes": 92306,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/006.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8l21mcdvr4j7zi3ivfnx.png",
      "alt": "Features",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foap45iuqis6ujvlkasn9.png",
      "bytes": 65610,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/007.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foap45iuqis6ujvlkasn9.png",
      "alt": "How It Works",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft52r01nh3mxbbuj4fmcw.png",
      "bytes": 54288,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/008.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft52r01nh3mxbbuj4fmcw.png",
      "alt": "Tech Stack",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwpy1ryy0a98816pxq5v.png",
      "bytes": 76686,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/009.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwpy1ryy0a98816pxq5v.png",
      "alt": "Comparison Table",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym3hceny1k0o934zqc73.png",
      "bytes": 83420,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/010.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym3hceny1k0o934zqc73.png",
      "alt": "Footer",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bu26ruxxaqusgvqedbt.png",
      "bytes": 59624,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/011.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bu26ruxxaqusgvqedbt.png",
      "alt": "Login",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxvyr24iw0dp2e4lfp8x.png",
      "bytes": 57046,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/012.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqxvyr24iw0dp2e4lfp8x.png",
      "alt": "Register",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr46a7h84a2b2miz96f6w.png",
      "bytes": 20260,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/013.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr46a7h84a2b2miz96f6w.png",
      "alt": "Onboarding",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7krkfhpva4t6z2yyjllc.png",
      "bytes": 120442,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/014.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7krkfhpva4t6z2yyjllc.png",
      "alt": "Dashboard",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fer8ump5czl04v5ru5p5j.png",
      "bytes": 68140,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/015.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fer8ump5czl04v5ru5p5j.png",
      "alt": "Dark Mode",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3770w02ywq42ujps9s1.png",
      "bytes": 68158,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/016.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr3770w02ywq42ujps9s1.png",
      "alt": "KPI Cards",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kgkhailbka92mlsca7i.png",
      "bytes": 54454,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/017.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kgkhailbka92mlsca7i.png",
      "alt": "Charts",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxy8djxp7lebozsrgscuc.png",
      "bytes": 59962,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/018.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxy8djxp7lebozsrgscuc.png",
      "alt": "Dashboard Bottom — Countries & Funnels",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxi2hkddv2a78mnn3t7mz.png",
      "bytes": 54078,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/019.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxi2hkddv2a78mnn3t7mz.png",
      "alt": "User Flow",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdf3a5cdo9lvcz86x5wnp.png",
      "bytes": 55726,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/020.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdf3a5cdo9lvcz86x5wnp.png",
      "alt": "Realtime",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatqioo3sgl1el5zhntf1.png",
      "bytes": 52050,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/021.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fatqioo3sgl1el5zhntf1.png",
      "alt": "Funnels",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkm26u5ky261199vc2at.png",
      "bytes": 66586,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/022.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkm26u5ky261199vc2at.png",
      "alt": "Settings",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favicp4al1j02d6ikuom4.png",
      "bytes": 37668,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/023.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favicp4al1j02d6ikuom4.png",
      "alt": "Pages",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthgyzr9j880zt4daq048.png",
      "bytes": 51838,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/024.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fthgyzr9j880zt4daq048.png",
      "alt": "Profile",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4tiquur19quffgr0jpo.png",
      "bytes": 131692,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/025.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4tiquur19quffgr0jpo.png",
      "alt": "Docs",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbqrdakspgsfoqvxx629.png",
      "bytes": 86240,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/026.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbqrdakspgsfoqvxx629.png",
      "alt": "API Reference",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxma2pzbl85es1feck72.png",
      "bytes": 30078,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/027.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxma2pzbl85es1feck72.png",
      "alt": "Sidebar",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ndzbg1xwpqyu56c33ff.png",
      "bytes": 73174,
      "reused": true,
      "local_path": "attachments/1eed7a7046ae/images/028.png",
      "src": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ndzbg1xwpqyu56c33ff.png",
      "alt": "Dark Landing",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://dev.to/challenges/github-2026-05-21",
      "label": "GitHub Finish-Up-A-Thon Challenge",
      "type": "a"
    },
    {
      "href": "https://github.com/nishikantaray/insighttrack",
      "label": "github.com/nishikantaray/insighttrack",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb8pqgknn1i3ts4cjhjoh.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2y6xjgmfuskdoj8qtw15.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pepz87hmrjv6eqn44z4.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw2t7vzkbtijhdwjvlsp.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8l21mcdvr4j7zi3ivfnx.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foap45iuqis6ujvlkasn9.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft52r01nh3mxbbuj4fmcw.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwpy1ryy0a98816pxq5v.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fym3hceny1k0o934zqc73.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr46a7h84a2b2miz96f6w.png",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxy8djxp7lebozsrgscuc.png",
      "label": "",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:49:51Z",
  "source_url": "https://dev.to/nishikantaray/how-i-finally-finished-insighttrack-my-abandoned-self-hosted-analytics-platform-1k7l",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 15870,
  "expected_body_images": 27,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/1eed7a7046ae/pages/source.html",
  "local_html": "attachments/1eed7a7046ae/pages/article.html",
  "body_base_path": "attachments/1eed7a7046ae/pages/",
  "body_markdown_path": "attachments/1eed7a7046ae/pages/article.md"
}