About CardLedger: Engineering a Modern TCG Platform
CardLedger began with a strict engineering constraint: handle massive, relationship-heavy datasets (21,000+ cards and 3.4M+ price records) over the web, but make the experience feel as instant and fluid as a locally installed desktop app. Born from the frustration of slow, pagination-heavy web interfaces, it serves as both a functional TCG collection manager and a technical case study in building a high-performance, type-safe, and scalable architecture from the ground up.
For a deeper dive into the architectural decisions, read the full technical breakdown.
The Backend & Data Foundation
The foundation of CardLedger is a custom-built Extract, Transform, Load (ETL) pipeline designed to turn raw API data into a high-performance relational asset.
- The Ingestion Engine: A Node.js pipeline extracts raw API data and normalizes it into a relational schema, breaking flat responses into distinct entities. Sequential processing with retry logic and circuit breakers ensures resilience against API rate limits, while Prisma's idempotent upserts allow the script to run daily via GitHub Actions without manual intervention.
- Prisma & Advanced Filtering: The normalized data is managed by Prisma ORM. By leveraging Prisma's powerful relation filtering, the backend can execute complex, multi-parameter queries that would be impossible with the source APIs alone, managing over 3.4 million price history records.
The Frontend Architecture: A Local-First Approach
Delivering 21,000+ cards without sacrificing performance required a highly optimized data strategy:
- Dictionary Compression & Sync: To bypass traditional DB query latency, data is aggregated into dictionary-indexed JSON streams. This normalization reduces the raw payload from 8MB to 3.5MB, halving JSON.parse() blocking time and saving ~20MB of JS heap memory to ensure the main thread remains unblocked on low-end devices. A smart versioning protocol then compresses it further for network delivery.
- Local-First Search: To achieve a “native app” feel, the application uses a client-side indexing strategy. Data is cached locally with IndexedDB and queried via Zustand and uFuzzy. By utilizing a highly optimized micro-library alongside custom pre-calculated intersection maps, the app achieves 0.3ms filtering latency, eliminating network wait times and making browsing 21,000+ cards feel instantaneous.
- JIT Rendering Pipeline: To prevent main-thread blocking during rapid scrolling, the UI leverages JIT denormalization via react-virtuoso. By deferring complex object construction until viewport entry, the application eliminates 20-30ms UI freezes and maintains a perfectly fluid frame rate.
- Optimistic UI Patterns: Collection interactions—adding or removing cards—update instantly in the UI while tRPC handles database synchronization in the background. Automatic state rollback ensures data consistency during network failures.
- On-Demand Invalidation: To bridge the gap between Static Site Generation (SSG) and real-time data, the backend triggers targeted cache purges via a secure webhook whenever the ETL pipeline updates prices. This ensures users always see fresh data without nuking the entire CDN cache.
Security & Asset Optimization
- Robust Authentication: Security is handled via Better Auth (Google, Discord, Email). The implementation focuses on Server-Side Authentication to eliminate layout shifts and ensure a premium, flicker-free user experience.
- Cost-Optimized Asset Pipeline: A custom Node.js/Sharp script processes card images into highly optimized AVIF formats stored in Cloudflare R2. This pipeline eliminates runtime image transformation costs and proactively generates variants for all target breakpoints to prevent hydration errors.
About the Creator
My name is Viet Le, and I'm a full-stack developer with a passion for building high-quality, performant web applications. CardLedger is my capstone portfolio project, built from the ground up to demonstrate mastery of modern application architecture, from database design to frontend optimization.