This guide will walk you through setting up the Portfolio Tracker application from scratch, including Firebase configuration, Vercel deployment, and available alternatives.
- Prerequisites
- Firebase Setup
- Local Development Setup
- Vercel Deployment
- Price Data Provider Alternatives
- Infrastructure Alternatives
- Troubleshooting
Before you begin, ensure you have:
- Node.js 18.x or higher (Download)
- npm or yarn package manager
- A Google account (for Firebase)
- A Vercel account (free tier available at vercel.com)
- Git installed on your machine
Firebase provides the backend infrastructure (database, authentication) for this application.
- Go to the Firebase Console
- Click "Add project" or "Create a project"
- Enter a project name (e.g.,
portfolio-tracker) - (Optional) Enable Google Analytics if desired
- Click "Create project"
- In your Firebase project, navigate to Build → Authentication
- Click "Get started"
- Enable the following sign-in methods:
- Email/Password: Click "Enable" and save
- Google: Click "Enable", add your support email, and save
- Navigate to Build → Firestore Database
- Click "Create database"
- Choose a starting mode:
- Production mode (recommended): Start with secure rules, you'll configure them next
- Test mode: Open access (not recommended for production)
- Select a Cloud Firestore location (choose closest to your users, e.g.,
europe-west1for Europe) - Click "Enable"
- In Firestore Database, go to the Rules tab
- Replace the default rules with the following:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can only read/write their own user document
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Assets belong to users
match /assets/{assetId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
allow create: if request.auth != null && request.auth.uid == request.resource.data.userId;
}
// Asset allocation targets belong to users
match /assetAllocationTargets/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Expenses belong to users
match /expenses/{expenseId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
allow create: if request.auth != null && request.auth.uid == request.resource.data.userId;
}
// Expense categories belong to users
match /expenseCategories/{categoryId} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
allow create: if request.auth != null && request.auth.uid == request.resource.data.userId;
}
// Monthly snapshots belong to users
match /monthlySnapshots/{snapshotId} {
allow read: if request.auth != null && request.auth.uid == resource.data.userId;
allow create, update: if request.auth != null && request.auth.uid == request.resource.data.userId;
allow delete: if request.auth != null && request.auth.uid == resource.data.userId;
}
// Hall of Fame - personal rankings belong to users
match /hall-of-fame/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
allow create, update: if request.auth != null &&
request.auth.uid == userId &&
request.resource.data.userId == userId;
allow delete: if request.auth != null && request.auth.uid == userId;
}
// Price history readable by all authenticated users
// Only backend can write (via Admin SDK)
match /priceHistory/{document} {
allow read: if request.auth != null;
allow write: if false;
}
}
}- Click "Publish" to save the rules
- In the Firebase Console, click the gear icon → Project settings
- Scroll down to "Your apps" section
- Click the Web icon (
</>) to add a web app - Register your app with a nickname (e.g.,
Portfolio Tracker Web) - Copy the Firebase configuration object - you'll need these values:
const firebaseConfig = {
apiKey: "AIza...",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project.appspot.com",
messagingSenderId: "123456789",
appId: "1:123456789:web:abc123"
};For server-side operations (API routes), you need Admin SDK credentials:
- In Project settings → Service accounts tab
- Click "Generate new private key"
- Click "Generate key" to download the JSON file
- IMPORTANT: Keep this file secure and never commit it to Git
- Save this file - you'll need it for environment variables
git clone https://github.com/your-username/net-worth-tracker.git
cd net-worth-trackernpm installCreate a .env.local file in the root directory:
# Firebase Client SDK (public - safe to expose)
NEXT_PUBLIC_FIREBASE_API_KEY=your_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 (server-side - keep secret!)
# Option A: Use the entire service account JSON (RECOMMENDED)
FIREBASE_SERVICE_ACCOUNT_KEY={"type":"service_account","project_id":"...","private_key":"-----BEGIN PRIVATE KEY-----\n..."}
# Option B: Use individual fields (for local development)
# 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-----\n"
# Cron Job Security
CRON_SECRET=your_secure_random_string_here
# App URL (for cron jobs and redirects)
NEXT_PUBLIC_APP_URL=http://localhost:3000
# Registration Control (optional - for restricting signups)
NEXT_PUBLIC_REGISTRATIONS_ENABLED=true
NEXT_PUBLIC_REGISTRATION_WHITELIST_ENABLED=false
NEXT_PUBLIC_REGISTRATION_WHITELIST=
# Development Features (optional - for testing/demo)
NEXT_PUBLIC_ENABLE_TEST_SNAPSHOTS=falseHow to get the values:
- Firebase Client SDK values: From Firebase Console → Project Settings → Your apps
FIREBASE_SERVICE_ACCOUNT_KEY: Paste the entire content of the downloaded JSON fileCRON_SECRET: Generate a random string (e.g., useopenssl rand -hex 32)NEXT_PUBLIC_ENABLE_TEST_SNAPSHOTS: Set totrueto enable dummy data generation in Settings page (for development, testing, or demo purposes). Warning: Test data is saved to the same Firebase collections as real data. You can delete all dummy data using the "Elimina Tutti i Dati Dummy" button in Settings. See README.md for full feature documentation. Recommended: Keepfalsein production environments.
For detailed Firebase Admin SDK configuration on Vercel, see VERCEL_SETUP.md
npm run devOpen http://localhost:3000 in your browser.
- Navigate to
/register - Create an account with email/password or Google sign-in
- Log in and start adding your assets!
- Create a new GitHub repository
- Push your local code:
git remote add origin https://github.com/your-username/your-repo.git
git branch -M main
git push -u origin main- Go to Vercel Dashboard
- Click "Add New..." → "Project"
- Import your GitHub repository
- Configure the project:
- Framework Preset: Next.js (auto-detected)
- Root Directory:
./(leave default) - Build Command:
npm run build(auto-configured) - Output Directory:
.next(auto-configured)
- In the "Configure Project" section, expand "Environment Variables"
- Add all the variables from your
.env.local(see Local Development Setup) - IMPORTANT: Set environment to Production, Preview, and Development
- For
NEXT_PUBLIC_APP_URL, use your Vercel deployment URL (e.g.,https://your-app.vercel.app)
Recommended approach for Firebase Admin SDK:
- Use
FIREBASE_SERVICE_ACCOUNT_KEYwith the full JSON content (see VERCEL_SETUP.md for details)
- Click "Deploy"
- Wait for the deployment to complete (usually 1-2 minutes)
- Visit your deployed app at
https://your-app.vercel.app
Vercel Cron Jobs are configured in vercel.json file in the project root.
The vercel.json file contains:
{
"crons": [
{
"path": "/api/cron/monthly-snapshot",
"schedule": "0 19 * * *"
}
]
}What this does:
path: The API endpoint to call (creates monthly portfolio snapshots)schedule: When to run (cron syntax format)
The schedule uses standard cron syntax: minute hour day month dayOfWeek
| Field | Values | Example |
|---|---|---|
| minute | 0-59 | 0 = at the start of the hour |
| hour | 0-23 (UTC) | 19 = 19:00 UTC (20:00 CET, 21:00 CEST) |
| day | 1-31 | 28-31 = days 28 through 31 |
| month | 1-12 or * | * = every month |
| dayOfWeek | 0-6 or * | * = every day of week |
Current default (0 19 * * *):
- Runs every day at 19:00 UTC
- Good for testing, but creates daily snapshots
Recommended for production (0 19 28-31 * *):
- Runs only on days 28-31 of each month at 19:00 UTC
- Covers all month lengths (Feb=28/29, Apr/Jun/Sep/Nov=30, others=31)
- Creates true monthly snapshots at month-end
Custom time examples:
0 22 28-31 * *- 22:00 UTC (23:00 CET, 00:00 CEST)0 20 1 * *- 1st day of each month at 20:00 UTC0 18 15 * *- 15th day of each month at 18:00 UTC
Timezone note:
- All times are in UTC
- Italy is UTC+1 (winter) or UTC+2 (summer)
- 19:00 UTC = 20:00 CET (winter) or 21:00 CEST (summer)
-
Edit
vercel.jsonin your project root:{ "crons": [ { "path": "/api/cron/monthly-snapshot", "schedule": "0 19 28-31 * *" } ] } -
Commit and push to GitHub:
git add vercel.json git commit -m "Update cron schedule to run at month-end only" git push -
Vercel auto-redeploys with the new configuration (usually takes 1-2 minutes)
-
Verify in Vercel Dashboard → Your Project → Settings → Cron Jobs
Important: The cron endpoint is protected by the CRON_SECRET environment variable.
- Without the correct secret, the endpoint returns 401 Unauthorized
- Set
CRON_SECRETin Vercel environment variables (see Step 3) - The secret is automatically passed by Vercel's cron system
Manual test (useful after configuration changes):
curl "https://your-app.vercel.app/api/cron/monthly-snapshot?secret=YOUR_CRON_SECRET"Replace:
your-app.vercel.appwith your actual Vercel URLYOUR_CRON_SECRETwith the value from your environment variables
Expected response:
{
"message": "Snapshots created successfully",
"results": [...]
}- Go to Vercel Dashboard → Your Project → Deployments
- Click on the latest deployment
- Go to Functions tab
- Find
/api/cron/monthly-snapshotin the list - Click to view execution logs and any errors
When triggered, the endpoint (/app/api/cron/monthly-snapshot/route.ts):
- Updates all asset prices from Yahoo Finance
- Calculates current portfolio metrics
- Creates a snapshot document in Firestore
- Updates Hall of Fame rankings
- Returns success/failure status
Note: The implementation is in /app/api/cron/monthly-snapshot/route.ts if you need to customize the logic.
This application currently uses Yahoo Finance for stock/ETF price data via the yahoo-finance2 npm package. Yahoo Finance is free, reliable, and has extensive ticker coverage.
- ✅ Free: No API key required, no rate limits for reasonable use
- ✅ Global Coverage: US, European, Asian exchanges
- ✅ Real-time Data: Delayed 15-20 minutes (acceptable for portfolio tracking)
- ✅ No Registration: Works out of the box
If you want to use a different provider, here are some alternatives:
- Website: alphavantage.co
- Pricing: Free tier (5 API calls/minute, 500 calls/day)
- Coverage: Stocks, forex, crypto, commodities
- API Key: Required (free registration)
- Implementation: Requires custom integration (not included)
How to implement:
- Register for API key at Alpha Vantage
- Replace
yahooFinanceService.tswith Alpha Vantage API calls - Update
/api/prices/quoteand/api/prices/updateroutes - Handle rate limiting (5 calls/min on free tier)
- Website: finnhub.io
- Pricing: Free tier (60 API calls/minute)
- Coverage: Stocks, forex, crypto, economic data
- API Key: Required (free registration)
- Implementation: Requires custom integration (not included)
How to implement:
- Register for API key at Finnhub
- Install
finnhubnpm package or use fetch API - Replace
yahooFinanceService.tswith Finnhub client - Update API routes to use Finnhub endpoints
- Website: twelvedata.com
- Pricing: Free tier (800 API calls/day, 8 calls/minute)
- Coverage: Stocks, forex, crypto, ETFs, indices
- API Key: Required (free registration)
- Implementation: Requires custom integration (not included)
How to implement:
- Register for API key at Twelve Data
- Install
twelvedatanpm package - Replace
yahooFinanceService.tswith Twelve Data client - Update API routes and handle rate limits
- Replacing the service layer: Modify
src/services/yahooFinanceService.ts - Updating API routes: Modify
/api/prices/quoteand/api/prices/update - Handling rate limits: Implement queuing or caching
- Testing ticker formats: Different providers may use different symbols
- Error handling: API-specific error codes and responses
Development effort: Approximately 4-8 hours depending on provider complexity.
If you decide to implement an alternative provider, consider:
- Creating a generic
PriceProviderinterface - Implementing provider-specific classes (e.g.,
YahooFinanceProvider,AlphaVantageProvider) - Using environment variables to switch between providers
While this guide focuses on Firebase + Vercel, the application architecture is flexible enough to support alternatives.
- Type: NoSQL document database
- Pricing: Free tier (512MB storage)
- Migration effort: Medium (Firestore and MongoDB are both document-based)
Changes required:
- Replace
firebase-adminwithmongodbnpm package - Update service layer to use MongoDB queries instead of Firestore
- Modify authentication (use Clerk, Auth0, or custom JWT)
- Update security rules → implement server-side authorization checks
- Type: PostgreSQL database with real-time features
- Pricing: Free tier (500MB database, 2GB bandwidth)
- Migration effort: High (SQL vs NoSQL paradigm shift)
Changes required:
- Replace Firestore collections with PostgreSQL tables
- Migrate to Supabase Auth (similar to Firebase Auth)
- Rewrite queries from NoSQL → SQL
- Update all service layer files
- Type: Serverless MySQL / PostgreSQL
- Pricing: Free tiers available
- Migration effort: High (SQL migration)
Changes required:
- Similar to Supabase migration
- Use Prisma ORM for type-safe database access
- Implement authentication separately (NextAuth.js recommended)
- Pricing: Free tier (100GB bandwidth, 300 build minutes/month)
- Cron Jobs: Via Netlify Scheduled Functions
- Migration effort: Low
Changes required:
- Create
netlify.tomlconfiguration - Convert Vercel Cron to Netlify Scheduled Functions
- Deploy via Netlify CLI or GitHub integration
- Pricing: Free tier ($5/month credit)
- Cron Jobs: Via Railway Cron Jobs or external scheduler
- Migration effort: Low-Medium
Changes required:
- Configure Railway deployment settings
- Set up environment variables in Railway dashboard
- Implement cron jobs via Railway's built-in scheduler or use external service (e.g., cron-job.org)
- Pricing: VPS cost (e.g., DigitalOcean from $5/month)
- Cron Jobs: Standard Linux cron
- Migration effort: Medium
Requirements:
- Create Dockerfile for Next.js app
- Set up reverse proxy (nginx)
- Configure systemd or docker-compose for auto-restart
- Set up cron jobs in Linux crontab
Changes required:
- Create production
Dockerfile - Configure PM2 or similar process manager
- Set up SSL certificates (Let's Encrypt)
- Manual deployment process
For most users, Firebase + Vercel is the best choice because:
- ✅ Generous free tiers
- ✅ Minimal configuration
- ✅ Automatic scaling
- ✅ Built-in authentication
- ✅ Real-time updates (Firestore)
- ✅ Easy cron job setup
Consider alternatives only if:
- You need SQL features (joins, complex queries)
- You're already using a different provider ecosystem
- You have specific compliance requirements
Cause: yahoo-finance2 package imported in a client component (runs in browser)
Solution:
- Always use server-side API routes for price fetching
- Import
yahoo-finance2only in/apiroutes or server components - Client components should call
/api/prices/quoteendpoint
Cause: Firebase Admin SDK private key formatting issue on Vercel
Solution:
- See detailed guide in VERCEL_SETUP.md
- Use
FIREBASE_SERVICE_ACCOUNT_KEYwith full JSON content instead of separate variables
Debugging steps:
- Check Vercel dashboard → Deployments → Your deployment → Functions
- Verify
CRON_SECRETenvironment variable is set - Check cron schedule syntax in
vercel.json - View function logs for errors
- Test endpoint manually:
curl https://your-app.vercel.app/api/cron/monthly-snapshot?secret=YOUR_CRON_SECRET
Possible causes:
- Invalid ticker format (use
.DEfor XETRA,.Lfor London, etc.) - Yahoo Finance API temporarily unavailable
- Network timeout
Solutions:
- Verify ticker symbol on Yahoo Finance
- Check API route logs in Vercel dashboard
- Add error handling and retry logic
Cause: Rounding errors or incorrect target percentages
Solution:
- Go to Settings page
- Verify all target percentages sum to exactly 100%
- Use the formula-based allocation feature for automatic calculation
Cause: Authentication state not loaded or user not logged in
Solution:
- Ensure
AuthContextproperly wraps your app inlayout.tsx - Check that
useAuth()hook is used insideAuthProvider - Verify Firebase Auth configuration in environment variables
Possible causes:
- No expenses created for the current year
- Date filter excluding expenses
- Chart data calculation error
Solutions:
- Check expense table has entries
- Verify year/month filters in UI
- Check browser console for JavaScript errors
If you encounter issues not covered here:
-
Check the logs:
- Vercel: Dashboard → Deployments → Functions tab
- Browser: Developer Tools → Console tab
-
Review configuration:
- Verify all environment variables are set correctly
- Check Firebase security rules allow your operations
-
Search existing issues:
- GitHub Issues: Check if someone else had the same problem
- Stack Overflow: Search for error messages
-
Open an issue:
- Provide error messages, logs, and steps to reproduce
- Include environment details (Node version, deployment platform)
After completing the setup:
- Create your first assets: Navigate to "Patrimonio" page and add your holdings
- Set allocation targets: Go to "Impostazioni" and configure your target allocation
- Add expenses: Track your income and expenses in "Tracciamento Spese"
- Create first snapshot: Manually create a snapshot or wait for the monthly cron job
- Monitor FIRE progress: Visit the "FIRE" page to track your financial independence journey
For detailed feature documentation, see the main README.md.
- Never commit
.env.localor Firebase service account JSON files to Git - Never share your
CRON_SECRETor Firebase Admin credentials publicly - Always use environment variables for sensitive data
- Enable Firestore security rules to prevent unauthorized access
- Regularly review Firebase Console → Authentication → Users for suspicious activity
- Use the registration control system to limit who can create accounts (see README.md)
Add these to .gitignore:
.env.local
.env
firebase-adminsdk-*.json
serviceAccountKey.json
This project is licensed under the AGPL-3.0 License - see the LICENSE file for details.