I’ve been running nexo-ta.com (nexo-ta.com) since 2021 as a side project — a privacy-first tool for analyzing Nexo transaction exports. It started as a simple React app and grew organically over the years. The tech debt finally caught up, so I rebuilt the entire thing from scratch.

nexo-ta.com v4 landing page

Why rebuild?

The old stack was showing its age:

  • Create React App — officially dead, no longer maintained
  • Redux with 4 slices — massive overkill for what’s essentially “parse CSV, show charts”
  • 15+ CSS Module files — scattered styling with no consistency
  • gridjs — poor React integration, required raw HTML for links
  • Zero tests — not a single unit or E2E test

The new stack

BeforeAfter
Create React AppVite 8
React 18React 19
Redux (4 slices)Zustand (1 store)
CSS ModulesTailwind CSS v4
gridjsTanStack Table v8
phosphor-reactLucide React
No tests101 tests

The entire state management went from 4 Redux slices + a store config + typed hooks file down to a single Zustand store. One file. The loadCSV action parses the CSV and calculates all balances/statistics in one call.

New features

Beyond the tech migration, I added several things I’d been wanting for a while:

  • Dark mode — system default with manual toggle, including adaptive logo/favicon
  • Date range filtering — 1M/3M/6M/1Y/All buttons on all charts
  • Transaction type filter — dropdown to filter by Interest, Exchange, Withdrawal, etc.
  • Portfolio metrics — Net Invested, Interest Earned, Unrealized P/L at a glance
  • 1-week change — quick pulse check on the dashboard
  • Demo mode — “Try Demo” button with realistic sample data
  • Mobile responsive — collapsible sidebar for smaller screens

nexo-ta.com v4 demo

Testing

Going from zero tests to 101 felt good. The test pyramid:

  • 84 unit tests (Vitest) — CSV parser, balance calculator, TX linkage for 19+ blockchains, formatting, localStorage, date filtering
  • 17 E2E tests (Playwright) — full demo flow, page navigation, search, pagination, dark mode persistence, save/restore

The demo CSV generator (generate_demo.py) uses a fixed random seed, so the test data is deterministic and reproducible.

CI/CD

Three chained GitHub Actions workflows:

  1. CI — lint, typecheck, unit tests across Node 20/22/24, production build, E2E
  2. Deploy — FTP upload (only fires after CI passes on main)
  3. Release — creates a GitHub release from CHANGELOG.md (only after Deploy succeeds)

Main branch is protected — all 9 CI checks must pass before merging.

What I learned

  • Zustand is criminally underrated. The migration from Redux felt like removing a splint.
  • Tailwind CSS v4’s @custom-variant for class-based dark mode is clean but not obvious if you’re coming from v3’s config file approach.
  • Recharts tooltips need itemStyle and labelStyle in addition to contentStyle to properly theme for dark mode. The docs don’t make this obvious.
  • The Nexo CSV export doesn’t distinguish between interest earned in-kind vs. earned as NEXO tokens. Both show up as Input: NEXO, Output: NEXO. This means the “Earn in NEXO” breakdown can’t be accurately split per-asset.

The rebuild was also supported by AI tooling, mainly Claude Code for implementation assistance and faster iteration, while I handled architecture, review, and final decisions.

The source code is on GitHub (github.com) and the app is live at nexo-ta.com (nexo-ta.com).