Protocol Guide is deployed as a PWA (Progressive Web App) on Netlify. The frontend is built using Expo Web, while the Express API server must be deployed separately (e.g., Railway, Fly.io, or as Netlify Functions).
| Setting | Value | Status |
|---|---|---|
| Build Command | pnpm install && pnpm build && npx expo export --platform web && cp -r public/* dist/ |
OK |
| Publish Directory | dist |
OK |
| Node Version | 20 |
OK |
| CI Mode | true |
OK |
All security headers are properly configured:
- X-Frame-Options:
DENY- Prevents clickjacking - X-Content-Type-Options:
nosniff- Prevents MIME sniffing - X-XSS-Protection:
1; mode=block- XSS protection - Referrer-Policy:
strict-origin-when-cross-origin- Referrer control - Content-Security-Policy: Configured for Supabase and Anthropic API connections
- SPA routing enabled (
/* -> /index.htmlwith status 200) - API routes are commented out (server deployed separately)
| Path | Cache-Control |
|---|---|
/assets/*, /*.js, /*.css |
public, max-age=31536000, immutable |
/service-worker.js, /sw.js |
public, max-age=0, must-revalidate |
/*.html |
public, max-age=0, must-revalidate |
Configure these in Netlify Dashboard > Site Settings > Environment Variables:
| Variable | Description | Example | Required |
|---|---|---|---|
ANTHROPIC_API_KEY |
Claude API for AI responses | sk-ant-api03-... |
YES |
GOOGLE_API_KEY |
Gemini Embedding 2 Preview for embeddings (Voyage AI removed 2026-03-24) | AIza... |
YES |
SUPABASE_URL |
Supabase project URL | https://xxx.supabase.co |
YES |
SUPABASE_ANON_KEY |
Supabase anonymous key (client-side) | eyJ... |
YES |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service key (server-side only) | eyJ... |
YES |
DATABASE_URL |
PostgreSQL connection string | postgresql://... |
YES |
JWT_SECRET |
JWT signing secret (32+ chars) | Generate with openssl rand -base64 32 |
YES |
| Variable | Description | Example | Required |
|---|---|---|---|
STRIPE_SECRET_KEY |
Stripe API secret key | sk_live_... or sk_test_... |
YES |
STRIPE_PUBLISHABLE_KEY |
Stripe publishable key | pk_live_... or pk_test_... |
YES |
STRIPE_WEBHOOK_SECRET |
Stripe webhook endpoint secret | whsec_... |
YES |
STRIPE_PRO_MONTHLY_PRICE_ID |
Monthly subscription price ID | price_... |
YES |
STRIPE_PRO_ANNUAL_PRICE_ID |
Annual subscription price ID | price_... |
YES |
| Variable | Description | Example | Required |
|---|---|---|---|
NEXT_AUTH_SECRET |
NextAuth session encryption | your-32-char-secret |
NO |
NEXT_AUTH_URL |
NextAuth callback URL | https://protocol-guide.com |
NO |
BUILT_IN_FORGE_API_URL |
Manus Forge API URL | https://forge.manus.ai/api |
NO |
BUILT_IN_FORGE_API_KEY |
Manus Forge API key | mfk_... |
NO |
PORT |
Server port (for backend deployment) | 3000 |
NO |
- Production: Use
sk_live_*andpk_live_*Stripe keys - Preview/Staging: Use
sk_test_*andpk_test_*Stripe keys - JWT_SECRET: Must be different per environment
- SUPABASE_SERVICE_ROLE_KEY: NEVER expose to client; only use server-side
pnpm install # Install dependencies
pnpm build # Build server bundle (esbuild -> dist/index.js)
npx expo export # Export Expo web app (-> dist/)
--platform web
cp -r public/* dist/ # Copy PWA assets (manifest.json, icons, sw.js)After successful build, dist/ contains:
dist/
index.html # Entry point
favicon.ico # Favicon
metadata.json # Build metadata
manifest.json # PWA manifest (copied from public/)
icon-192.png # PWA icon (copied from public/)
icon-512.png # PWA icon (copied from public/)
sw.js # Service worker (copied from public/)
_expo/
static/
css/ # Bundled CSS
js/ # Bundled JavaScript
assets/ # Images, fonts
- Server bundle: ~64 KB
- Web bundle: ~2.9 MB (uncompressed)
- Total assets: ~1.2 MB
- Server build: ~3ms
- Expo web export: ~4-5 seconds
- Total: ~30-60 seconds on Netlify
- All environment variables configured in Netlify Dashboard
- Stripe webhook endpoint created:
https://your-domain.com/api/webhooks/stripe - Supabase project is running and accessible
- Database migrations applied (
pnpm db:push) - API server deployed and accessible (if using separate backend)
-
Connect Repository
- Go to Netlify Dashboard
- Click "Add new site" > "Import an existing project"
- Connect GitHub repository
- Select
Protocol Guide Manusrepository
-
Configure Build Settings
- Build command: Auto-detected from
netlify.toml - Publish directory: Auto-detected as
dist - Base directory: Leave empty (root)
- Build command: Auto-detected from
-
Set Environment Variables
- Go to Site Settings > Environment Variables
- Add all CRITICAL and STRIPE variables from checklist above
- Use "Sensitive variable" for API keys
-
Deploy
- Click "Deploy site"
- Wait for build to complete (~1-2 minutes)
-
Verify Deployment
- Site loads without errors
- PWA can be installed (Chrome shows install prompt)
- Service worker registered
- API endpoints respond (if using Netlify Functions)
- Verify Sentry error tracking is working
- Test authentication flow
- Test Stripe payment flow
- Verify AI chat responses work
- Check browser console for errors
- Test on mobile devices
Since the Express server is not deployed on Netlify, choose one:
Uncomment the API redirect in netlify.toml:
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/api/:splat"
status = 200Create netlify/functions/api.ts adapter.
Production URL: https://protocol-guide-production.up.railway.app
# Install Railway CLI
npm install -g @railway/cli
# Login and deploy
railway login
railway init
railway up| Variable | Description | Required |
|---|---|---|
DATABASE_URL |
Supabase PostgreSQL connection string | Yes |
SUPABASE_URL |
Supabase project URL | Yes |
SUPABASE_ANON_KEY |
Supabase anonymous/public key | Yes |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service role key (server-only) | Yes |
ANTHROPIC_API_KEY |
Claude API key for RAG responses | Yes |
GOOGLE_API_KEY |
Gemini Embedding 2 Preview API key (Voyage AI removed 2026-03-24) | Yes |
STRIPE_SECRET_KEY |
Stripe secret key for payments | Yes |
STRIPE_WEBHOOK_SECRET |
Stripe webhook signing secret | Yes |
JWT_SECRET |
JWT signing secret (min 32 chars) | Yes |
UPSTASH_REDIS_REST_URL |
Upstash Redis URL for caching/rate limits | Yes |
UPSTASH_REDIS_REST_TOKEN |
Upstash Redis auth token | Yes |
PAUBOX_API_KEY |
Paubox email API key (HIPAA-aligned, BAA-covered). Replaces Resend (BANNED per Ops Rules — no BAA). AWS SES is the v2 alternative. | Yes |
SENTRY_DSN |
Sentry error tracking DSN | Yes |
CRON_SECRET |
Secret for cron job authentication | Yes |
NODE_ENV |
Set to production |
Yes |
LOG_LEVEL |
Pino log level (info for production) |
No |
- Endpoint:
GET /api/trpc/system.health - Expected: HTTP 200 with JSON health status
- Railway auto-restarts on health check failure
- Railway auto-deploys from
mainbranch (if GitHub integration configured) - Or manual deploy via
railway upfrom project directory - Build command:
pnpm build - Start command:
node dist/server/index.js
# Install Fly CLI
brew install flyctl
# Login and deploy
fly auth login
fly launch
fly deploy| Error | Solution |
|---|---|
pnpm: command not found |
Netlify auto-installs pnpm; check Node version |
expo: command not found |
Ensure expo is in dependencies, not devDependencies |
| TypeScript errors | Build skips type checking; run pnpm check locally first |
| Issue | Solution |
|---|---|
| 404 on refresh | Verify SPA redirect in netlify.toml |
| CORS errors | Add API domain to CSP in netlify.toml |
| API timeout | Backend server may be cold starting |
- Enable Netlify Edge Functions for faster responses
- Use Netlify CDN for static assets (automatic)
- Consider Netlify Image CDN for optimized images
- Errors: Sentry (configure
SENTRY_DSN) - Uptime: Netlify Analytics or Pingdom
- Performance: Netlify Analytics
- Logs: Netlify Functions logs or backend provider logs
Consider adding a /api/health endpoint that returns:
{
"status": "ok",
"version": "1.0.0",
"timestamp": "2024-01-22T12:00:00Z"
}# Local development
pnpm dev
# Build verification
pnpm build && npx expo export --platform web
# Type checking
pnpm check
# Run tests
pnpm test
# Database migrations
pnpm db:pushLast updated: 2026-01-22