Complete guide for deploying EmitKit to Vercel with Neon PostgreSQL, Redis, PWA, and Push Notifications.
- Prerequisites
- Infrastructure Setup
- Environment Variables
- Deployment Steps
- Post-Deployment Configuration
- Testing
- Troubleshooting
- Vercel Account: vercel.com
- Neon Account: neon.tech
- Upstash Account (recommended): upstash.com
- Git Repository: Code pushed to GitHub/GitLab/Bitbucket
- Node.js 20+: For local testing
- pnpm: Package manager
- Go to console.neon.tech
- Click "New Project"
- Name:
blip-production - Region: Choose closest to Vercel region (e.g.,
us-east-1) - Click "Create Project"
- In project dashboard, click "Connection string"
- Copy the Pooled connection string
- Format:
postgres://user:password@ep-xxx.us-east-1.aws.neon.tech/neondb?sslmode=require&pooling=true - Save for later (will be
DATABASE_URL)
# Set DATABASE_URL locally
export DATABASE_URL="your-neon-connection-string"
# Generate and push schema
pnpm run db:generate
pnpm run db:pushWhy Upstash? HTTP-based Redis perfect for serverless (no connection pooling issues).
- Go to console.upstash.com
- Click "Create Database"
- Name:
blip-production - Type: Regional
- Region: Same as Vercel (e.g.,
us-east-1) - Click "Create"
- In database dashboard, go to "Details" tab
- Copy REST URL or Redis connection string
- Format:
rediss://default:password@your-redis.upstash.io:6379 - Save for later (will be
REDIS_URL)
VAPID keys allow your server to send push notifications.
# Generate keys
pnpm dlx web-push generate-vapid-keys
# Output:
# Public Key: BL...
# Private Key: xyz...Save both keys securely! You'll need them for environment variables.
For a complete list of environment variables and their configuration, see Configuration Guide.
Set these in Vercel Project Settings → Environment Variables:
-
DATABASE_URL- Neon pooled connection string -
UPSTASH_REDIS_REST_URL- Upstash Redis URL -
UPSTASH_REDIS_REST_TOKEN- Upstash Redis token -
VERCEL_URL- Your Vercel app URL -
BETTER_AUTH_SECRET- Random 32-byte secret (generate:openssl rand -base64 32) -
PUBLIC_VAPID_KEY- VAPID public key -
VAPID_KEY- VAPID private key -
VAPID_SUBJECT- Your contact email -
TINYBIRD_TOKEN- Tinybird API token -
OPENAI_API_KEY- OpenAI API key (optional)
- Go to vercel.com/new
- Import your Git repository
- Select "SvelteKit" framework
- Don't deploy yet!
- Framework Preset: SvelteKit
- Build Command:
pnpm run build - Install Command:
pnpm install - Output Directory:
.svelte-kit(leave default)
- Leave as root (unless monorepo)
- Go to Project Settings → Environment Variables
- Add all variables from Environment Variables section
- Set environment: Production (and Preview if needed)
# Option 1: Deploy via Vercel Dashboard
# Click "Deploy" button
# Option 2: Deploy via CLI
vercel --prod- Wait for build to complete (~2-3 minutes)
- Visit your production URL
- Check logs for errors: Project → Deployments → [Latest] → Logs
If you used a placeholder domain, update these after first deployment:
VERCEL_URL="https://your-actual-domain.vercel.app"
BETTER_AUTH_URL="https://your-actual-domain.vercel.app"Then redeploy:
vercel --prodVisit: https://your-app.vercel.app/api/healthcheck (create this endpoint if needed)
Or check Vercel logs for database connection messages.
Create a test event and verify it broadcasts via SSE.
- Go to Project Settings → Domains
- Add your custom domain
- Follow DNS setup instructions
- Update
VERCEL_URLenvironment variable - Redeploy
- Visit your production URL
- Look for install icon in address bar (⊕)
- Click to install
- Verify app opens in standalone window
- Visit your production URL in Safari/Chrome
- Tap Share → Add to Home Screen
- Tap icon on home screen
- Verify app opens fullscreen (no browser UI)
// In browser console on your production site
const { subscribeToPush, getPublicVapidKey } =
await import('$lib/features/notifications/notifications.remote.ts');
// Get VAPID public key
const { publicKey } = await getPublicVapidKey();
// Register service worker and subscribe
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey
});
// Save subscription
await subscribeToPush({
endpoint: subscription.endpoint,
p256dhKey: subscription.keys.p256dh,
authKey: subscription.keys.auth,
channelIds: ['your-channel-id']
});curl -X POST https://your-app.vercel.app/api/v1/events \
-H "Content-Type: application/json" \
-d '{
"channelId": "your-channel-id",
"organizationId": "your-org-id",
"title": "Test Notification",
"description": "Testing push notifications",
"notify": true
}'- Should see notification on your device
- Click notification → should open app to channel
Error: Connection timeout or Failed to connect
Solutions:
- Verify
DATABASE_URLincludes?sslmode=require&pooling=true - Check Neon database is not suspended (free tier auto-suspends)
- Verify Vercel region matches Neon region for lower latency
Error: Redis connection failed
Solutions:
- Verify
REDIS_URLis correct (should start withrediss://) - Check Upstash database is active
- Try regenerating Upstash password
Error: Events not streaming to clients
Solutions:
- Check Redis pub/sub is working (
REDIS_URLconfigured) - Verify
vercel.jsonhas SSE headers configured - Check Vercel function logs for errors
- Ensure
X-Accel-Buffering: noheader is set
Error: Notifications not received
Solutions:
- Verify VAPID keys are correct (PUBLIC_VAPID_KEY must match subscription)
- Check browser console for service worker errors
- Verify subscription was saved to database
- Test with
notify: trueon event creation - Check Vercel function logs for web-push errors
Error: UnauthorizedRegistration
Solution: VAPID keys mismatch. Regenerate subscription with correct public key.
Error: No install prompt on mobile/desktop
Solutions:
- Verify site is served over HTTPS (Vercel does this automatically)
- Check
manifest.jsonis accessible at/manifest.json - Verify service worker is registered (check DevTools → Application → Service Workers)
- Clear cache and try again
- Check PWA icons exist at
/static/pwa-*.svg
Error: Type error or build fails
Solutions:
- Run
pnpm run checklocally - Ensure all dependencies are in
package.json(not just devDependencies) - Check Node.js version matches (should be 20+)
- Clear Vercel cache: Project Settings → General → Clear Cache
Before going live:
- Database migrations applied
- All environment variables configured
- Better Auth secret is random and secure
- VAPID keys generated and configured
- PWA icons created (replace placeholders)
- Tested PWA installation on real device
- Tested push notifications end-to-end
- Custom domain configured (if applicable)
- Monitoring/error tracking setup (optional: Sentry)
- Backup strategy for database
- Rate limiting configured on API endpoints (optional)
- Add indexes to frequently queried columns
- Use Drizzle's
withfor eager loading relations - Implement pagination for large lists
- Use Upstash Redis for zero cold starts
- Consider Redis caching for expensive queries
- Monitor Redis memory usage
- Limit concurrent SSE connections per user
- Implement reconnection logic on client
- Add heartbeat/keepalive messages
- API Authentication: Ensure all API endpoints check authentication
- Rate Limiting: Add rate limiting to prevent abuse
- Input Validation: Use Zod schemas for all user input
- CORS Configuration: Restrict CORS to known domains in production
- Secret Management: Never commit secrets to Git
- Database Security: Use parameterized queries (Drizzle does this automatically)
- Vercel Analytics: Page views, performance
- Database: Connection pool usage, query performance
- Redis: Memory usage, pub/sub messages
- Push Notifications: Delivery rate, failures
- Error Rate: Track 5xx errors
- Vercel Analytics: Built-in
- Neon Monitoring: Database metrics
- Upstash Monitoring: Redis metrics
- Sentry (optional): Error tracking
- Users: 1-1000 concurrent users
- Events: ~100 events/second
- SSE Connections: ~500 concurrent connections per region
- Push Notifications: Thousands per minute
- Add More Regions: If users are globally distributed
- Upgrade Database: If >10GB data or high query load
- Add Caching: Redis caching for frequently accessed data
- Queue System: BullMQ + Redis for background jobs
- Vercel Docs: vercel.com/docs
- Neon Docs: neon.tech/docs
- Upstash Docs: upstash.com/docs
- SvelteKit Docs: kit.svelte.dev
- Web Push Docs: web.dev/push-notifications
# Local development
pnpm run dev
# Type check
pnpm run check
# Database commands
pnpm run db:generate # Generate migrations
pnpm run db:push # Push to database
pnpm run db:studio # Open Drizzle Studio
# Deploy
vercel --prod
# View logs
vercel logs --prod
# Environment variables
vercel env ls
vercel env add VARIABLE_NAME production- Vercel Dashboard: https://vercel.com/dashboard
- Neon Console: https://console.neon.tech
- Upstash Console: https://console.upstash.com
- Your App: https://your-app.vercel.app
After successful deployment:
- Create Production Data: Add channels, test events
- Monitor Performance: Watch Vercel Analytics for issues
- User Testing: Install PWA on real devices and test
- Documentation: Update team on how to create events via API
- Iterate: Based on usage, optimize and add features
If you encounter issues not covered here:
- Check Vercel deployment logs
- Check Neon/Upstash dashboards for errors
- Test locally with production environment variables
- Review troubleshooting section
Good luck with your deployment! 🚀