Building a Modern Pokemon Card Collection App: A Full-Stack Journey with Next.js and Firebase
As a developer and Pokemon card enthusiast, I wanted to create a modern web application that would allow collectors to showcase their prized cards, track their collection progress, and manage their sets efficiently (Credit to Void for this project idea!). This project served multiple purposes: it was an opportunity to learn Cursor more effectively, test new technologies like Firebase, and practice thinking through architectural solutions like the three-tier caching system. Built entirely with Cursor's assistance, this project became an excellent learning experience in full-stack development with Next.js, Firebase, and external API integration.
Project Overview
The Pokemon Card Collection application is a comprehensive web platform that combines personal collection showcasing with set completion tracking. Unlike simple gallery apps, this project integrates real-time data from the Pokemon TCG API, implements user authentication, and provides a sophisticated tracking system for collectors who want to master complete sets.
Below is the MVP I came up with in just a few hours.
Authenticate
Sign in/Sign up
Collection
Showcase cards
Tracker
Track sets
Profile
Manage account
Key Features
- Personal Collection Showcase: Upload and display your favorite cards with personal notes and memories
- Set Completion Tracker: Track your progress toward completing entire Pokemon TCG sets
- Real-time Data Integration: Automatic synchronization with the Pokemon TCG API
- User Authentication: Secure Firebase Authentication with profile management
- Smart Caching System: Three-tier caching for optimal performance
- Responsive Design: Beautiful Pokemon-themed UI that works on all devices

The homepage showing general features and sign in option

The sign-in screen popup
Tech Stack
The application is built with modern web technologies:
- Next.js 16: App Router with TypeScript for type-safe development
- Firebase: Firestore for data persistence and Firebase Authentication
- Tailwind CSS: Utility-first styling with a custom Pokemon-themed color scheme
- Pokemon TCG API: External API for sets and card data
- React 19: Latest React features with hooks and context API
Architecture & Data Structure
Firebase Firestore Schema
One of the most interesting aspects of this project was designing an efficient Firestore schema that balances performance, security, and scalability. The database uses a nested structure that leverages Firestore's path-based security:
users/
└── {userId}/
├── (profile document)
├── cards/ # Showcase collection
│ └── {cardId}/
│ ├── title: string
│ ├── image: string (base64 in Firestore - future: Firebase Storage URL)
│ ├── notes: string
│ ├── uploadDate: Timestamp
│ └── set: string | null
│
└── setProgress/ # Set tracking
└── {apiSetId}/
├── setCode: string
├── collectedCardIds: string[]
├── cards: TrackedCard[]
├── totalCards: number
└── timestamps
pokemonData/ # Shared cache
├── metadata/
├── {setId}/ # Set documents
└── pokemonSet/pokemonSet/{setId}/ # Card data cache
Why This Structure?
The nested structure provides several advantages:
- Simplified Security Rules: Path-based rules (
users/{userId}/cards) automatically enforce ownership - No Composite Indexes: Can query by
uploadDatedirectly without needinguserId+uploadDatecomposite index - Better Data Locality: Related data is stored together, improving query performance
- Easier Deletion: Deleting a user document removes all associated data
- Intuitive Organization: Matches the mental model of "user has cards"
Core Features Deep Dive
1. Collection Showcase
The showcase collection allows users to upload their favorite cards with personal stories and memories. This is separate from the tracker feature, focusing on cards users want to display and share.
Key Implementation Details:
- Images stored as base64 strings in Firestore database (suitable for MVP, could be migrated to Firebase Storage with CDN for better performance and caching)
- Cards can be associated with Pokemon TCG sets for better organization
- Editable notes and memories section for each card
- Filter by set using data from the Pokemon TCG API

The main interface showing the collection showcase and navigation sidebar. The collection showcase page displays uploaded cards in a responsive grid layout.

The add new card interface showing the upload form
2. Set Completion Tracker
The tracker feature is where the application really shines. Users can track their progress toward completing entire sets without needing to upload images for every card.
Three-Tier Caching System:
The tracker implements a sophisticated caching hierarchy for optimal performance:
- User's setProgress (fastest): User-specific collection status
- Shared pokemonData collection (fast): Card data cached for all users
- Pokemon TCG API (fallback): Direct API calls when data isn't cached
User Progress
Fastest - User-specific
~10ms
Shared Cache
Fast - Shared data
~50ms
Pokemon TCG API
Fallback - External
~500ms
// Simplified caching logic
async function loadCardsForSet(setId: string, userId: string) {
// Tier 1: Check user's setProgress
const userProgress = await getUserSetProgress(userId, setId);
if (userProgress?.cards) {
return userProgress.cards;
}
// Tier 2: Check shared pokemonData cache
const cachedData = await getCachedSetData(setId);
if (cachedData?.cards) {
return cachedData.cards;
}
// Tier 3: Fetch from API and cache
const apiData = await fetchFromPokemonTCGAPI(setId);
await cacheSetData(setId, apiData);
return apiData.cards;
}
Progress Tracking:
- Real-time progress calculation (X/Y cards collected)
- Visual progress bars with completion percentages
- Support for multiple simultaneous set tracking
- Automatic card data loading when sets are selected
- Ability to delete sets you are tracking in case you do not want to complete them anymore
![]()
The set completion tracker showing progress bars, card grid, and collection status
![]()
You can see we can expand to a given set that the user is tracking and the first 3 Pokemon cards are ticked as the user has already obtained them
![]()
Here we have added a new set to track
![]()
Here we demonstrate we have the ability to delete a set
![]()
3. Pokemon TCG API Integration
Integrating with the Pokemon TCG API presented several challenges:
Rate Limiting & Retry Logic:
The API has rate limits, so I implemented exponential backoff retry logic:
async function fetchWithRetry(
url: string,
maxRetries = 3,
delay = 1000
): Promise<Response> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url);
if (response.ok) return response;
if (response.status === 429) {
// Rate limited - exponential backoff
await new Promise(resolve =>
setTimeout(resolve, delay * Math.pow(2, attempt))
);
continue;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
}
throw new Error('Max retries exceeded');
}
Data Synchronization:
Manual sync endpoints allow administrators to pre-populate the cache:
/api/sync-sets: Syncs all available sets from the API/api/sync-cards: Syncs cards for a specific set- Server-side timeout handling (60s for sync operations)
- Optional authentication via
SYNC_SECRETenvironment variable
Pokemon TCG API
Fetch data
Sync Endpoint
Process & cache
Firestore Cache
Store shared data
All Users
Fast access
4. User Authentication & Profiles
Firebase Authentication provides secure user management:
- Email/password authentication
- Profile management (display name, email, avatar)
- Secure password changes with re-authentication
- Account deletion with confirmation dialogs
- Protected routes using React Context
Security Rules Example:
// Firestore Security Rules
match /users/{userId}/cards/{cardId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
match /users/{userId}/setProgress/{setId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
match /pokemonData/{document=**} {
allow read: if true; // Public read for shared data
allow write: if request.auth != null; // Authenticated write
}
UI/UX Design
The application features a Pokemon-themed design with:
- Color Scheme: Pokemon-inspired colors (reds, blues, yellows)
- Responsive Grid Layout: Card gallery adapts to screen size
- Modal Interactions: Smooth animations for card details
- Loading States: Skeleton screens and progress indicators
- Error Handling: User-friendly error messages

User profile page with settings for display name, email, password, and avatar
Performance Optimizations
Several optimizations ensure fast load times:
- Client-Side Caching: React Query or similar for API response caching
- Image Optimization: Currently using base64 storage in Firestore (planned migration to Firebase Storage with CDN for improved caching and performance)
- Lazy Loading: Components loaded on demand
- Shared Data Cache:
pokemonDatacollection reduces API calls - Efficient Queries: Indexed Firestore queries with proper ordering
Challenges & Solutions
Challenge 1: API Rate Limiting
Problem: The Pokemon TCG API has strict rate limits that could break the user experience.
Solution: Implemented three-tier caching system and retry logic with exponential backoff.
Challenge 2: Large Dataset Management
Problem: Some Pokemon sets have 200+ cards, making initial loads slow.
Solution: Progressive loading with pagination and shared caching across all users.
Challenge 3: Real-time Progress Updates
Problem: Need to update progress calculations instantly when users mark cards.
Solution: Optimistic UI updates with Firestore real-time listeners for consistency.
Future Enhancements
While the MVP is complete, several enhancements could improve the application:
- Image Storage Migration: Move from base64 strings in Firestore database to Firebase Storage with CDN caching for improved performance and reduced database size
- Social Features: Share collections with friends, follow other collectors
- Trading System: Facilitate card trading between users
- Advanced Analytics: Collection value estimation, rarity statistics
- Export Functionality: PDF collection reports, CSV exports
- Oops I forgot to add signout functionality👀
Development Approach & Learning Goals
This project was intentionally designed as a learning exercise with several key objectives:
Learning Cursor
One of the primary goals was to become more proficient with Cursor, an AI-powered code editor that has revolutionized how I approach development. Throughout this project, I used Cursor to:
- Generate boilerplate code and component structures
- Refactor and optimize existing code
- Debug issues and implement error handling
- Explore best practices for Next.js and Firebase integration
Working with Cursor taught me the importance of providing clear context, iterating on AI suggestions, and understanding when to accept or modify generated code. The tool significantly accelerated development while helping me learn new patterns and techniques.
Exploring New Technologies
This project provided hands-on experience with technologies I wanted to explore:
- Firebase: First-time implementation of Firestore and Firebase Authentication
- Next.js 16: Latest App Router patterns and server components
- External API Integration: Working with the Pokemon TCG API and handling rate limits
- TypeScript: Advanced type safety and interface design
Each technology presented unique challenges and learning opportunities that wouldn't have been as clear from documentation alone.
Architectural Problem-Solving
Perhaps the most valuable learning came from thinking through architectural challenges. The three-tier caching system is a perfect example:
The Problem: The Pokemon TCG API has rate limits, and some sets contain 200+ cards. Direct API calls would be slow and could hit rate limits, creating a poor user experience.
The Thought Process:
- First, I considered caching at the user level only, but realized this would duplicate data across users
- Then, I thought about a shared cache, but what if a user's data was more recent?
- Finally, I designed the three-tier system: check user data first (fastest, most personalized), then shared cache (fast, efficient), then API (fallback, always available)
This iterative thinking process—identifying the problem, considering multiple solutions, and finding the optimal balance—was a key learning outcome of using Cursor. The tool helped me explore different approaches quickly, but the architectural decisions came from understanding the trade-offs.
Lessons Learned
1. Schema Design Matters
The nested Firestore structure simplified security rules and improved query performance. Taking time to design the schema upfront saved significant refactoring later. Cursor was particularly helpful in exploring different schema patterns and their implications.
2. Caching is Critical
The three-tier caching system dramatically improved user experience. Users rarely wait for API calls after initial data sync. This solution emerged from thinking through the problem systematically and considering multiple approaches before settling on the optimal solution.
3. External API Integration
Working with external APIs requires robust error handling, retry logic, and fallback strategies. Never assume APIs will always be available. Cursor helped generate the retry logic, but understanding when and why to use exponential backoff came from thinking through the problem.
4. User Experience First
Separating "showcase collection" from "set tracker" created a clearer mental model for users. Not every collected card needs to be showcased. This architectural decision improved both user experience and data organization.
5. AI-Assisted Development
Using Cursor effectively requires understanding when to trust the AI and when to apply your own judgment. The tool excels at generating code, but architectural decisions, trade-offs, and optimization strategies benefit from human reasoning combined with AI assistance.
Conclusion
Building this Pokemon Card Collection application was an excellent learning experience in full-stack development. The project combines modern web technologies, efficient data architecture, and thoughtful UX design to create a tool that Pokemon card collectors can actually use and enjoy.
The application demonstrates several important concepts:
- Full-stack development with Next.js and Firebase
- External API integration with proper error handling
- Efficient database design with Firestore
- Performance optimization through smart caching
- User authentication and security best practices
Whether you're a developer looking to build similar applications or a collector interested in tracking your Pokemon cards, this project showcases how modern web technologies can create powerful, user-friendly applications.
Interested in the technical details? Check out the project repository for the complete source code, documentation, and setup instructions. The application is built with Next.js 16, Firebase, and TypeScript, making it a great reference for modern full-stack development patterns.
Disclaimer: Pokemon and Pokemon character names are trademarks of Nintendo, The Pokemon Company, Game Freak, and Creatures Inc. This is a fan project and is not affiliated with or endorsed by The Pokemon Company or Nintendo.