Back to Blog
Weekend Project

Building a Modern Pokemon Card Collection App: A Full-Stack Journey with Next.js and Firebase

Discover how I built a comprehensive Pokemon card collection application with Next.js, Firebase, and the Pokemon TCG API. Learn about the architecture, features, and implementation challenges.

Stanley Ho
November 12, 2025
12 min read
#Next.js#Firebase#TypeScript#Full-Stack#React#Pokemon TCG API#Web Development

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.

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

Pokemon Collection App - Homepage

The homepage showing general features and sign in option

Pokemon Collection App - Sign in

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:

  1. Simplified Security Rules: Path-based rules (users/{userId}/cards) automatically enforce ownership
  2. No Composite Indexes: Can query by uploadDate directly without needing userId + uploadDate composite index
  3. Better Data Locality: Related data is stored together, improving query performance
  4. Easier Deletion: Deleting a user document removes all associated data
  5. 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

Pokemon Collection App - Main Interface

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

Pokemon Collection App - Add New Card

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:

  1. User's setProgress (fastest): User-specific collection status
  2. Shared pokemonData collection (fast): Card data cached for all users
  3. Pokemon TCG API (fallback): Direct API calls when data isn't cached
// 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

Set Tracker Interface

The set completion tracker showing progress bars, card grid, and collection status

Set Tracker set expanded example

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

Set Tracker new set added

Here we have added a new set to track

Set Tracker delete set

Here we demonstrate we have the ability to delete a set

Set Tracker deleted set result

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_SECRET environment variable

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

Profile Management

User profile page with settings for display name, email, password, and avatar

Performance Optimizations

Several optimizations ensure fast load times:

  1. Client-Side Caching: React Query or similar for API response caching
  2. Image Optimization: Currently using base64 storage in Firestore (planned migration to Firebase Storage with CDN for improved caching and performance)
  3. Lazy Loading: Components loaded on demand
  4. Shared Data Cache: pokemonData collection reduces API calls
  5. 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:

  1. First, I considered caching at the user level only, but realized this would duplicate data across users
  2. Then, I thought about a shared cache, but what if a user's data was more recent?
  3. 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.

Related Posts

Weekend Project
November 17, 2025

Learning Gateway API: Building a Website to Master Kubernetes Networking

Discover why I built a dedicated learning website for Gateway API, how it helped me understand the concepts better, and my journey from Ingress NGINX to the future of Kubernetes networking.

#Gateway API#Kubernetes#Networking#Learning#AKS#Ingress#NGINX#Education
Stanley Ho
9 min read

Enjoyed this article? Check out more DevOps insights on my blog.

View All Posts