A Next.js application for making NFL game picks and tracking your record against family and friends.
- 🔐 Google Authentication with Firebase
- 🏈 Live NFL game data from ESPN API
- 💾 Secure pick storage in Firestore
- 🎨 Modern UI with Chakra UI v3 and Tailwind CSS
- 📱 Responsive design for all devices
- 🏆 Track your weekly and overall record
- Frontend: Next.js 15, React 19, TypeScript
- Styling: Chakra UI v3, Tailwind CSS
- Authentication: Firebase Auth (Google)
- Database: Firestore
- API: ESPN API for NFL game data
https://site.web.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard
week: NFL week number (1-18 for regular season, 19-22 for postseason)year: Season year (e.g., 2025)seasontype: Optional (1=preseason, 2=regular, 3=postseason)
const response = await fetch(
'https://site.web.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard?week=1&year=2025'
);{
"leagues": [{
"events": [
{
"id": "401437678",
"name": "Houston Texans at Buffalo Bills",
"date": "2025-09-04T23:15Z",
"competitions": [{
"competitors": [
{
"team": {
"id": "33",
"displayName": "Houston Texans",
"logo": "https://a.espncdn.com/i/teamlogos/nfl/500/33.png"
},
"score": 20,
"homeAway": "away"
},
{
"team": {
"id": "13",
"displayName": "Buffalo Bills",
"logo": "https://a.espncdn.com/i/teamlogos/nfl/500/13.png"
},
"score": 24,
"homeAway": "home"
}
]
}],
"status": {
"type": {
"state": "post",
"completed": true,
"description": "Final"
}
}
}
]
}]
}Since ESPN's API response has a complex nested structure, we normalize it into a simpler format:
interface NormalizedGame {
eventId: string;
date: string;
away: {
id: string;
name: string;
logo: string;
abbreviation?: string;
record?: string;
score?: number;
};
home: {
id: string;
name: string;
logo: string;
abbreviation?: string;
record?: string;
score?: number;
};
status: {
state: "pre" | "in" | "post";
displayText: string;
detail?: string;
};
}{
"eventId": "401437678",
"date": "2025-09-04T23:15Z",
"away": {
"id": "33",
"name": "Houston Texans",
"logo": "https://a.espncdn.com/i/teamlogos/nfl/500/33.png",
"abbreviation": "HOU",
"record": "10-7",
"score": 20
},
"home": {
"id": "13",
"name": "Buffalo Bills",
"logo": "https://a.espncdn.com/i/teamlogos/nfl/500/13.png",
"abbreviation": "BUF",
"record": "11-6",
"score": 24
},
"status": {
"state": "post",
"displayText": "Final",
"detail": "20-24"
}
}Fetches games for a specific week and year. Handles both fetching from ESPN API and caching in Firestore.
Query Parameters:
week(required): Week numberyear(optional): Season year (defaults to current year)refreshScores(optional): Set to "true" to force refresh from ESPN
Response: Array of NormalizedGame objects
Returns the current NFL week number based on ESPN's API.
Response:
{
"week": 1,
"year": 2025,
"seasonType": 2
}Manages user's game picks.
GET: Returns user's picks for a week POST: Saves new picks
Request/Response format:
{
"gameId": "401437678",
"selectedTeam": "33",
"week": 1,
"year": 2025,
"timestamp": {
"seconds": 1725494400,
"nanoseconds": 0
}
}Returns all users' picks for a week (used for displaying what others picked).
games collection:
- Document ID: ESPN event ID
- Fields: Normalized game data plus
week,year, andlastUpdatedtimestamps
users collection:
- Document ID: Firebase auth UID
- Fields: User profile information
picks collection:
- Document ID: Auto-generated
- Fields: User pick data with indexes on userId, week, and year
{
"eventId": "401437678",
"date": "2025-09-04T23:15Z",
"away": {
"id": "33",
"name": "Houston Texans",
"logo": "https://a.espncdn.com/i/teamlogos/nfl/500/33.png",
"score": 20
},
"home": {
"id": "13",
"name": "Buffalo Bills",
"logo": "https://a.espncdn.com/i/teamlogos/nfl/500/13.png",
"score": 24
},
"status": {
"state": "post",
"displayText": "Final",
"detail": "20-24"
},
"week": 1,
"year": 2025,
"lastUpdated": "2025-01-05T20:30:00Z"
}Firebase Cloud Functions run every 5 minutes to:
- Check ESPN API for score updates
- Update active games in Firestore
- Only updates games that are in progress or about to start
updateGameScores (Scheduled every 5 minutes)
- Fetches current week from ESPN API
- Updates all active/pre-game scores in Firestore
- Includes rate limiting to avoid excessive API calls
forceUpdateWeek (Callable function)
- Manually trigger update for specific week
- Used for testing or immediate updates
- Accepts
weekandyearparameters
onGameComplete (Firestore trigger)
- Automatically processes completed games
- Updates user pick records (win/loss)
- Calculates weekly standings
processCompletedGames (HTTP trigger)
- Batch processes multiple completed games
- Updates leaderboards and statistics
Since ESPN's API may not have postseason data immediately, the app includes mock data for playoff weeks:
- Week 19: Wild Card Weekend
- Week 20: Divisional Round
- Week 21: Conference Championships
- Week 22: Super Bowl
The app automatically detects postseason based on date and uses mock data until ESPN updates their API.
- If ESPN API fails, the app falls back to cached data in Firestore
- If no cached data exists, shows an error message
- Rate limiting prevents excessive calls to ESPN API
- Automatic retries with exponential backoff for network errors
The main dashboard (src/components/dashboard/dashboard.tsx) consumes the APIs:
- Fetches current week on mount to determine which week to display
- Loads games for the selected week via
/api/games - Loads user's existing picks via
/api/user-picks - Loads all users' picks via
/api/all-picksfor social features
Each game is rendered using the GamePickCard component:
- Shows team logos, names, and records
- Displays current score or game time based on status
- Allows picking winners before game starts
- Shows results after games complete
- Displays what other users picked (after game starts)
The WeekDropdown component provides:
- Regular season weeks 1-18
- Postseason weeks 19-22 with labels (Wild Card, Divisional, etc.)
- Automatic navigation to current week on load
- Scores refresh every 30 seconds for active games
- Status updates (pre -> in -> post) trigger UI changes
- Pick buttons disable when games start
cd nfl-picks
npm install- Go to Firebase Console
- Create a new project
- Enable Authentication:
- Go to Authentication > Sign-in method
- Enable Google provider
- Create Firestore Database:
- Go to Firestore Database
- Create a new database in test mode
- Get your Firebase configuration:
- Project Settings > General > Your apps
- Copy the Firebase config
- In Firebase Console, go to Project Settings > Service accounts
- Click "Generate new private key"
- Save the JSON file securely
- Copy the values from the JSON file for your environment variables
-
Copy
.env.exampleto.env.local:cp .env.example .env.local
-
Fill in your Firebase credentials in
.env.local:# Firebase Configuration (Client-side) NEXT_PUBLIC_FIREBASE_API_KEY=your_actual_api_key NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_project_id NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_project.appspot.com NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_sender_id NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id # Firebase Admin SDK Configuration (Server-side) FIREBASE_ADMIN_PROJECT_ID=your_project_id FIREBASE_ADMIN_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your_project.iam.gserviceaccount.com FIREBASE_ADMIN_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYour private key here\n-----END PRIVATE KEY-----"
npm run devOpen http://localhost:3000 in your browser.
- Sign In: Click "Sign in with Google" to authenticate
- View Games: See this week's NFL games with team logos
- Make Picks: Click on a team to select your winner
- Track Progress: Your picks are saved and you can see your results
nfl-picks/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # API routes
│ │ │ ├── games/ # Game data fetching and caching
│ │ │ ├── current-week/ # Current week detection
│ │ │ ├── user-picks/ # User pick management
│ │ │ └── all-picks/ # All users' picks for social features
│ │ ├── layout.tsx # Root layout with providers
│ │ └── page.tsx # Main page (authentication)
│ ├── components/ # React components
│ │ ├── auth/ # Authentication components
│ │ ├── dashboard/ # Main dashboard and game cards
│ │ ├── layout/ # Layout components (week dropdown)
│ │ └── ui/ # Reusable UI components
│ └── lib/ # Utility files
│ ├── firebase.ts # Client-side Firebase config
│ ├── firebase-admin.ts # Server-side Firebase config
│ ├── espn-data.ts # ESPN API data normalization
│ ├── espn-cache.ts # Caching utilities
│ └── mock-postseason-data.ts # Temporary playoff data
├── functions/ # Firebase Cloud Functions
│ ├── src/
│ │ ├── index.ts # Function exports
│ │ ├── scheduled-game-update.ts # Automated score updates
│ │ ├── force-update.ts # Manual update trigger
│ │ └── lib/ # Shared utilities
│ └── package.json
├── public/ # Static assets
├── firebase.json # Firebase configuration
└── package.json # Dependencies
- 🔒 Firebase ID token verification on all API routes
- 🛡️ Server-side authentication with Firebase Admin SDK
- 🚫 No direct database access from client
- ✅ Environment variables for sensitive data
- 🏆 Leaderboard to compete with family
- 📊 Statistics and analytics
- 🔔 Notifications for game results
- 📅 Historical pick tracking
- 👥 Multiple week competitions
-
"Unauthorized - Invalid token" error
- Make sure your Firebase Admin SDK credentials are correct
- Check that Google Authentication is enabled in Firebase Console
-
"Failed to fetch NFL games" error
- The ESPN API might be temporarily unavailable
- Check your internet connection
-
Build errors
- Make sure all environment variables are set
- Run
npm installto ensure all dependencies are installed
If you run into issues:
- Check the browser console for error messages
- Verify your Firebase configuration
- Make sure all environment variables are properly set
This project is great for learning:
- Next.js App Router and Server Components
- Firebase Authentication and Firestore
- API integration and data fetching
- Modern React patterns with TypeScript
- Component-based UI design with Chakra UI
Happy coding and good luck with your NFL picks! 🏈