The Real Story: Deploying Full-Stack Apps After Multiple Platform Failures

by Ko-Hsin Liang 20 min read DevOps & Deployment
  • Deployment
  • Angular
  • Express
  • Vercel
  • Render
  • Neon
  • Railway
  • PostgreSQL
  • Prisma
  • CORS
  • Cross-Origin Cookies
  • DevOps
  • Real-World Problems

The Real Story: Deploying Full-Stack Apps After Multiple Platform Failures

Last updated: October 1, 2025 | Node.js v20, Angular v20, Prisma v5.22 | Battle-tested across 3 platforms

The Honest Truth: This Wasn’t Easy

Spoiler alert: This deployment took three different platform attempts, countless debugging sessions, and multiple architectural pivots. But that’s exactly why this guide exists.

Most deployment tutorials show you the happy path. This one shows you the real path—including the failures, the frustrations, and most importantly, the solutions that actually work.

Our Journey in Three Acts:

  1. Act 1 - Railway’s Broken Promise: Started with Railway for both backend and database. Looked perfect on paper. Database failed repeatedly, support couldn’t fix it, moved on.
  2. Act 2 - Render’s Limited Free Tier: Switched backend and database to Render.com. Backend worked great, but database inspection requires paid tier. Can’t see tables or debug queries on free plan.
  3. Act 3 - The Final Architecture: Split the stack strategically—Vercel (frontend) + Render.com (backend) + Neon.tech (database). But then hit cross-origin cookie hell. Fixed with Vercel proxy.

By the end of this guide, you’ll have:

  • Your Angular frontend blazing fast on Vercel’s global CDN
  • Your Express + Prisma backend running reliably on Render.com with Docker
  • Your PostgreSQL database on Neon.tech’s generous free tier (10GB, table inspection, connection pooling)
  • Cross-origin cookies working via Vercel’s proxy (no more CORS nightmares)
  • A battle-tested deployment process that actually survives production

More importantly, you’ll understand why each architecture decision was made and how to debug when things inevitably go wrong.

Prerequisites: Your Deployment Toolkit

Before we begin our deployment journey, let’s make sure you have everything ready:

Required Accounts (all have generous free tiers)

  • GitHub - Your code repository
  • Vercel - For frontend hosting (vercel.com)
  • Render - For backend hosting (render.com)
  • Neon - For PostgreSQL database (neon.tech)

Required Tools Installed

  • Git - For version control
  • Node.js v20+ - Check with node --version
  • npm or yarn - Package manager

Your Codebase Structure

We’re deploying a monorepo with this structure:

your-repo/
├── frontend/          # Angular 20 app
│   ├── src/
│   ├── angular.json
│   └── package.json
└── backend/           # Express + Prisma API
    ├── src/
    ├── prisma/
    ├── Dockerfile     # This is crucial!
    └── package.json

The Journey: Why We Changed Platforms (Twice)

Attempt #1: Railway for Everything

The Dream: Single platform for backend + database. Beautiful UI, Git-based deploys, $5 free credit monthly.

The Reality:

  • Database connection kept dropping randomly
  • Error: P1001: Can't reach database server—even though it was “running”
  • Database would work for hours, then randomly fail with new deployment
  • The error logs were not helpful at all
  • After whole day of failures, we gave up

Lesson: Don’t marry yourself to a platform just because it’s convenient. If the database isn’t reliable, nothing else matters.

Attempt #2: Render for Backend + Database

The Switch: Moved everything to Render.com. Better reputation, more mature platform.

The Good:

  • Backend deployment: Flawless. Dockerfile support, environment variables, health checks—all perfect.
  • Database: Stable connections, no random failures.

The Showstopper:

  • Free tier database = no table inspection
  • Can’t see table schemas without upgrading to $7/month
  • Can’t run queries in the UI
  • Blind debugging: “Did my migration run? Who knows!”

Lesson: Database visibility is non-negotiable for development. Paying $7/month for basic SQL access feels wrong when free alternatives exist.

Attempt #3: The Winning Combination

Final Architecture:

  • Frontend: Vercel (free, fast, perfect for Angular)
  • Backend: Render.com (Docker support, $0/month, 750hrs free)
  • Database: Neon.tech (10GB free, full SQL access, table browser, connection pooling)

The Cross-Origin Plot Twist: When we split services across domains, cookies stopped working:

  • Vercel: https://your-app.vercel.app
  • Render: https://your-backend.onrender.com
  • Browsers: “These are different origins, no cookies for you!”

Even with SameSite=None and Secure=true, browsers blocked our auth cookies.

The Solution: Vercel proxy rewrites. Make backend calls appear same-origin:

  • Frontend calls: https://your-app.vercel.app/api/login
  • Vercel proxies to: https://your-backend.onrender.com/api/login
  • Browser thinks: “Same origin, cookies allowed!”

The Final Architecture: What Actually Works

graph LR
    A[Users] --> B[Angular Frontend
Vercel CDN] B -->|/api/* proxied| C[Express Backend
Render Container] C --> D[(PostgreSQL
Neon.tech)] style B fill:#0070f3,color:#fff style C fill:#0066ff,color:#fff style D fill:#00e599,color:#000

Why This Architecture Won

Frontend on Vercel:

  • Global CDN = sub-100ms load times worldwide
  • Automatic HTTPS and custom domains
  • Git-based deploys with preview URLs
  • Proxy rewrites solve cross-origin cookie issues
  • 100GB bandwidth/month free

Backend on Render.com:

  • Native Docker support (just add Dockerfile)
  • 750 free hours/month (sleeps after inactivity, wakes in <30s)
  • Environment variables, health checks, auto-deploy
  • Better reliability than Railway
  • Logs that actually help you debug

Database on Neon.tech:

  • 10GB storage free (vs 512MB on Render)
  • Built-in table browser (see your data without paying)
  • SQL editor in the UI
  • Connection pooling included
  • Branching for staging environments
  • Auto-suspend after 5min inactivity (saves compute)

The Secret Sauce: Vercel Proxy

  • Frontend and backend appear as same origin to browsers
  • Cookies work with SameSite=Lax (more compatible)
  • No complex CORS configuration needed
  • Configured in 3 lines of JSON

The key insight: Don’t fight browsers’ security model. Work with it by using proxies to make cross-origin requests appear same-origin.

Part 1: Setting Up Your Database (Neon.tech)

Let’s start with the foundation—the database. Unlike Render’s blind approach, Neon gives you full visibility.

Step 1.1: Create Your Neon Project

  1. Sign up at neon.tech
  2. Click “Create a project”
  3. Give it a name: fullstack-auth-db
  4. Select region closest to your users
  5. Click “Create project”

Neon automatically creates:

  • A PostgreSQL 17 database
  • A connection string
  • A built-in SQL editor and table browser

Step 1.2: Get Your Connection String

  1. In your Neon project, go to “Connection Details”
  2. Copy the pooled connection string (has -pooler in it)
    postgresql://user:[email protected]/dbname?sslmode=require
  3. Save this—you’ll need it for Render

Why pooled connection?

  • Serverless-friendly (connections don’t stay open)
  • Better for Render’s sleep/wake cycle
  • Included free (no extra cost)

Step 1.3: Test Your Database

Right in Neon’s UI, run:

SELECT version();

You should see PostgreSQL 16.x. This is huge—you can actually see your database works before deploying anything!

Part 2: Preparing Your Backend for Render

Step 2.1: The Dockerfile - Battle-Tested for Render

Your backend needs a Dockerfile that handles Prisma correctly. Here’s what actually works on Render:

backend/Dockerfile:

# Production Dockerfile for Render.com deployment
FROM node:20-alpine

WORKDIR /app

# Fix Prisma OpenSSL warning on Alpine Linux
RUN apk add --no-cache openssl

# Copy package files (Docker layer caching optimization)
COPY package*.json ./

# Install ALL dependencies (we need TypeScript to build)
RUN npm ci

# Copy source code
COPY . .

# Build TypeScript to JavaScript
RUN npm run build

# Create startup script that runs migrations then starts the app
RUN echo '#!/bin/sh' > /app/start.sh && \
    echo 'if [ -z "$DATABASE_URL" ]; then' >> /app/start.sh && \
    echo '  echo "Error: DATABASE_URL not set"' >> /app/start.sh && \
    echo '  exit 1' >> /app/start.sh && \
    echo 'fi' >> /app/start.sh && \
    echo 'echo "Generating Prisma Client..."' >> /app/start.sh && \
    echo 'npx prisma generate' >> /app/start.sh && \
    echo 'echo "Running database migrations..."' >> /app/start.sh && \
    echo 'npx prisma migrate deploy' >> /app/start.sh && \
    echo 'echo "Starting application..."' >> /app/start.sh && \
    echo 'node dist/index.js' >> /app/start.sh && \
    chmod +x /app/start.sh

# Render will set PORT env var, but we expose 4000 for local testing
EXPOSE 4000
CMD ["/app/start.sh"]

Critical details:

  • Prisma Client generated at runtime (DATABASE_URL available)
  • Migrations run automatically on every deploy
  • Validates DATABASE_URL before starting
  • Uses Alpine Linux (smaller = faster cold starts)
  • OpenSSL installed for Prisma on Alpine

Step 2.2: Configure Backend for Proxied Requests

Since we’re using Vercel proxy, your backend needs specific CORS and cookie settings.

backend/src/index.ts - CORS Configuration:

// Trust proxy (Render runs behind a proxy)
app.set('trust proxy', 1);

// CORS setup
const origins = (process.env.CORS_ORIGIN || '')
  .split(',')
  .map(s => s.trim())
  .filter(Boolean);

const corsOptions = {
  origin: (origin, callback) => {
    if (!origin) return callback(null, true); // Allow non-browser clients
    if (origins.length === 0 || origins.includes(origin)) {
      return callback(null, true);
    }
    return callback(new Error('Not allowed by CORS'));
  },
  credentials: true
};

app.use(helmet());
app.use(cors(corsOptions));

backend/src/controllers/authController.ts - Cookie Settings:

function setAccessCookie(res: Response, token: string) {
  res.cookie('access_token', token, {
    httpOnly: true,
    secure: IS_PROD,
    sameSite: 'lax', // KEY: Use 'lax' for Vercel proxy (not 'none')
    maxAge: 15 * 60 * 1000, // 15 minutes
    path: '/',
  });
}

function setRefreshCookie(res: Response, token: string) {
  res.cookie('refresh_token', token, {
    httpOnly: true,
    secure: IS_PROD,
    sameSite: 'lax', // KEY: Use 'lax' for Vercel proxy (not 'none')
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    path: '/',
  });
}

Why SameSite: 'lax' instead of 'none'?

Because of the Vercel proxy, requests appear same-origin to the browser:

  • Browser sees: your-app.vercel.app → your-app.vercel.app/api/login
  • Vercel proxies to: your-backend.onrender.com/api/login
  • Browser treats it as same-origin → 'lax' works perfectly

We tried SameSite: 'none' first (for cross-origin), but browsers were super strict and blocked cookies anyway. The proxy eliminates the need for cross-origin cookies entirely.

Part 3: Deploying Your Backend to Render

Step 3.1: Create Your Render Web Service

  1. Sign up/login at render.com
  2. Click “New +” → “Web Service”
  3. Connect your GitHub repository
  4. Configure the service:
    • Name: auth-backend (or whatever you want)
    • Region: Choose closest to your users
    • Branch: main
    • Root Directory: backend (important for monorepos!)
    • Runtime: Docker
    • Dockerfile Path: backend/Dockerfile
    • Docker Context: backend

Step 3.2: Configure Environment Variables

Click “Advanced” to add environment variables:

# Database connection (from Neon)
DATABASE_URL=postgresql://user:[email protected]/dbname?sslmode=require

# CORS (your Vercel domain - you'll update this after deploying frontend)
CORS_ORIGIN=https://your-app.vercel.app

# App environment
NODE_ENV=production

# JWT secrets (generate with: openssl rand -base64 32)
JWT_ACCESS_SECRET=your_super_strong_access_secret_here
JWT_REFRESH_SECRET=your_super_strong_refresh_secret_here  
JWT_EMAIL_SECRET=your_super_strong_email_secret_here
JWT_PASSWORD_RESET_SECRET=your_super_strong_password_reset_secret_here

# Skip email verification for demo (set to false in production)
SKIP_EMAIL_VERIFICATION=true

Critical Environment Variables:

  • DATABASE_URL: Your Neon pooled connection string from Step 1.2
  • CORS_ORIGIN: Your Vercel frontend URL (update after deploying frontend)
  • JWT_*_SECRET: Generate strong secrets with openssl rand -base64 32
  • SKIP_EMAIL_VERIFICATION: true for demo, false when you configure SMTP

Step 3.3: Configure Health Check

Render checks if your app is alive:

  • Health Check Path: /api/health

Make sure your backend has this endpoint:

// backend/src/routes/health.ts
app.get('/api/health', (req, res) => {
  res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});

Step 3.4: Deploy and Monitor

  1. Click “Create Web Service”

  2. Render will:

    • Pull your GitHub repo
    • Build the Docker image
    • Run your startup script (generate → migrate → start)
    • Assign you a URL: https://auth-backend-xxxx.onrender.com
  3. Watch the logs in real-time:

    ==> Generating Prisma Client...
    ✔ Generated Prisma Client
    ==> Running database migrations...
    Applying migration `20250930224306_init`
    ==> Starting application...
    [production] Server listening on port 10000
  4. Test your backend:

    curl https://auth-backend-xxxx.onrender.com/api/health
    # Should return: {"status":"ok","timestamp":"2025-10-01T..."}

Step 3.5: Verify Database Connection

Go to Neon’s SQL Editor and run:

SELECT * FROM "User";

After your first deploy with migrations, you should see the User table created. This is why Neon is worth it—you can actually verify your migrations worked!

Part 4: Preparing Your Frontend with Vercel Proxy

This is where the magic happens—making cross-origin requests work seamlessly.

Step 4.1: Create Vercel Proxy Configuration

The key to making cookies work is the Vercel proxy. Create frontend/vercel.json:

{
  "rewrites": [
    {
      "source": "/api/:path*",
      "destination": "https://auth-backend-xxxx.onrender.com/api/:path*"
    }
  ]
}

Replace auth-backend-xxxx.onrender.com with your actual Render backend URL from Step 3.4.

What this does:

  • Browser calls: https://your-app.vercel.app/api/login
  • Vercel intercepts and proxies to: https://auth-backend-xxxx.onrender.com/api/login
  • Browser never knows about the cross-origin request
  • Cookies work perfectly with SameSite=lax

Step 4.2: Configure Frontend API URL

Since we’re using the proxy, your frontend uses relative URLs. Update frontend/src/environments/environment.ts:

export const environment = {
  production: true,
  apiUrl: '/api'  // Relative URL - proxied by Vercel
};

Pro tip: Keep environment.development.ts pointing to http://localhost:4000/api for local development:

export const environment = {
  production: false,
  apiUrl: 'http://localhost:4000/api'  // Direct call in development
};

Step 4.3: Test Your Production Build Locally

Before deploying, verify your build works:

cd frontend
npm run build
npx http-server dist/frontend -p 8080

Open http://localhost:8080. It won’t connect to the backend yet (proxy only works on Vercel), but verify the build succeeds and the app loads.

Part 5: Deploying Your Frontend to Vercel

Step 5.1: Connect Vercel to Your Repository

  1. Sign up/login at vercel.com
  2. Click “Add New Project”
  3. Import your GitHub repository
  4. Configure the build settings:
    • Root Directory: frontend ← Critical for monorepos
    • Framework Preset: Angular (auto-detected)
    • Build Command: npm run build (default)
    • Output Directory: dist/frontend (default)
    • Install Command: npm install (default)

Step 5.2: Deploy!

  1. Click “Deploy”

  2. Vercel will:

    • Install dependencies in frontend/
    • Run npm run build
    • Read vercel.json and configure the proxy
    • Deploy to global CDN
    • Give you a URL: https://your-app.vercel.app
  3. First deploy takes 2-3 minutes. Watch the build logs.

Step 5.3: Update Backend CORS

Now that you have your Vercel URL, update Render:

  1. Go to your Render dashboard
  2. Click on your auth-backend service
  3. Go to “Environment”
  4. Update CORS_ORIGIN:
    CORS_ORIGIN=https://your-app.vercel.app
  5. Click “Save Changes” (this triggers a redeploy)

Step 5.4: Test the Full Stack

  1. Open your Vercel URL: https://your-app.vercel.app
  2. Open browser DevTools → Network tab
  3. Try to register a new user
  4. Watch the Network tab:
    POST https://your-app.vercel.app/api/auth/register
    Status: 201 Created
  5. Check DevTools → Application → Cookies:
    access_token: ey... (HttpOnly, Secure, SameSite=Lax)
    refresh_token: ey... (HttpOnly, Secure, SameSite=Lax)

If cookies are there and you can navigate to the dashboard—YOU DID IT! 🎉

Step 5.5: Verify in Neon

The beauty of Neon—go to SQL Editor and run:

SELECT id, email, "emailVerified", "createdAt" FROM "User";

You should see your newly registered user. This instant feedback is why we chose Neon over Render’s database.

Part 6: Troubleshooting - The Real Problems We Hit

Problem #1: Cookies Not Being Set

Symptom: Login succeeds (200 OK), but browser doesn’t store cookies. Subsequent /profile calls return 401 Unauthorized.

What we tried:

  1. ❌ SameSite=none + Secure=true (browsers still blocked)
  2. ❌ Setting domain attribute (made it worse)
  3. ✅ Vercel proxy + SameSite=lax (finally worked!)

The lesson: Cross-origin cookies are a nightmare. Even with all the right flags, modern browsers are ultra-strict. Proxying makes requests same-origin from the browser’s perspective, sidestepping the entire issue.

How to verify:

  1. Open DevTools → Network
  2. Find the login request
  3. Check Response Headers for set-cookie
  4. Check DevTools → Application → Cookies
  5. If set-cookie exists but cookies don’t appear, it’s a SameSite issue

Problem #2: Railway Database Randomly Failing

Symptom: Error: P1001: Can't reach database server appearing randomly every few hours. Backend works for a while, then suddenly can’t connect to database.

What we tried:

  1. ❌ Restarted database service (worked temporarily)
  2. ❌ Changed connection pooling settings (no improvement)
  3. ❌ Support tickets (no resolution after 2 weeks)
  4. ✅ Switched to Neon (instant stability)

The lesson: Free tier doesn’t mean unreliable should be acceptable. Database stability is non-negotiable.

Problem #3: Render’s Database Inspection Requires Payment

Symptom: Migrations run, but can’t verify if tables actually exist or inspect data without upgrading to $7/month paid tier.

What we tried:

  1. ❌ CLI connection (worked but clunky)
  2. ❌ pgAdmin (extra tool needed)
  3. ✅ Switched database to Neon (built-in table browser on free tier)

The lesson: Being able to see your database is essential for debugging. Don’t settle for blind deployment.

Problem #4: Prisma P1001 in Production

Symptom: Backend starts successfully but crashes on first database query: Error: P1001: Can't reach database server

Common causes:

  1. Wrong connection string: Check DATABASE_URL in Render environment
  2. Missing sslmode=require: Neon requires SSL connections
  3. Pooled vs Direct URL: Use the pooled connection from Neon (has -pooler in hostname)

Solution:

# Correct format for Neon (note -pooler and ?sslmode=require)
DATABASE_URL=postgresql://user:[email protected]/dbname?sslmode=require

Problem #5: Vercel Build Fails with Monorepo

Symptom: Vercel tries to build from repo root instead of frontend/ directory, can’t find package.json.

Solution:

  • Set Root Directory to frontend in Vercel project settings
  • Vercel will run commands relative to that directory

Problem #6: Migrations Don’t Run on Render

Symptom: App starts but database tables don’t exist.

Debug steps:

  1. Check Render logs for migration output
  2. Verify prisma migrate deploy runs in startup script
  3. Connect to Neon and manually check tables:
    SELECT tablename FROM pg_tables WHERE schemaname = 'public';

Solution: Our Dockerfile startup script runs migrations automatically. If they’re not running, check:

  • Startup script has execute permissions (chmod +x /app/start.sh)
  • Dockerfile CMD correctly calls the startup script
  • DATABASE_URL is accessible at runtime

Part 7: Post-Deploy Checklist

Smoke Tests

  • Frontend loads at https://your-app.vercel.app
  • Backend health check: curl https://auth-backend-xxxx.onrender.com/api/health
  • Register a new user via frontend
  • Verify cookies are set (DevTools → Application → Cookies)
  • Login with registered user
  • Access protected route (dashboard)
  • Logout and verify cookies are cleared
  • Check Neon database: SELECT * FROM "User"; shows registered users

Database Verification

  • Neon connection is using pooled URL (-pooler in hostname)
  • Tables exist: Run \dt in Neon SQL editor
  • Migrations applied: Check _prisma_migrations table
  • Sample query works: SELECT COUNT(*) FROM "User";

Production Hardening

  • Generated strong JWT secrets (openssl rand -base64 32)
  • Set SKIP_EMAIL_VERIFICATION=false and configure SMTP
  • Add rate limiting (already in code)
  • Set up error monitoring (Sentry recommended)
  • Configure custom domains on Vercel
  • Enable Neon’s branch feature for staging

Performance Optimization

  • Test backend cold start time on Render (should wake in <30s)
  • Verify Vercel CDN caching for static assets
  • Check Neon connection pooling is active
  • Add loading indicators for slow network (already done!)

The Victory Lap: What You Actually Accomplished

Congratulations! After battling through Railway database failures, Render’s limited free tier, and cross-origin cookie hell, you’ve successfully deployed:

  • ✅ Angular 20 frontend on Vercel’s edge network (100GB bandwidth/month free)
  • ✅ Express + Prisma backend on Render.com with Docker (750 hours/month free)
  • ✅ PostgreSQL database on Neon.tech with full SQL access (10GB storage free)
  • ✅ Vercel proxy solving cross-origin cookies (no more SameSite nightmares)
  • ✅ Automatic migrations on every Render deploy
  • ✅ Database visibility with Neon’s table browser (no blind debugging)

Most importantly: You learned why each piece works and can debug when things break.

What Made This Architecture Work

After three platform attempts, here’s what we learned:

1. Database Visibility is Non-Negotiable

Railway couldn’t stay up. Render’s free tier can’t inspect tables. Neon gives you full SQL access, table browser, and query editor—all free. When debugging production issues, seeing your data is everything.

2. Cross-Origin Cookies Are a Trap

Even with SameSite=none, Secure=true, and perfect CORS config, browsers still blocked cookies. The Vercel proxy bypasses the entire cross-origin security model by making requests appear same-origin. This is the “secret sauce” that makes the architecture work.

3. Docker Gives You Control

Render’s Docker support means your backend runs exactly the same locally and in production. No “works on my machine” mysteries. The startup script ensures Prisma Client generates and migrations run in the correct order, every time.

4. Free Tiers Are Generous (if chosen wisely)

  • Vercel: 100GB bandwidth, unlimited deployments
  • Render: 750 hours/month (25 days), 512MB RAM
  • Neon: 10GB storage, unlimited queries
  • Total cost: $0/month for side projects and demos

Next Steps: Level Up Your Deployment

Immediate Wins

  1. Custom Domain: Add your domain to Vercel (free SSL included)
  2. Staging Environment: Create a Neon branch for testing schema changes
  3. Email Service: Configure SMTP (SendGrid, Resend, or AWS SES)
  4. Error Monitoring: Add Sentry for backend and frontend error tracking

Advanced Improvements

  1. CI/CD Pipeline: GitHub Actions for automated tests + deploy
  2. Database Migrations: Set up GitHub workflow to apply migrations
  3. Backup Strategy: Neon has automatic backups, but export monthly snapshots
  4. API Documentation: Add Swagger/OpenAPI docs
  5. Rate Limiting: Configure per-endpoint limits (already in code, tune the values)

Lessons from the Trenches

What Worked

  • Testing locally first: Always npm run build and verify before deploying
  • Incremental deployment: Deploy backend → test → deploy frontend → test
  • Reading logs: Render and Vercel logs are surprisingly good
  • Neon’s SQL editor: Saved hours of debugging blind database issues

What Didn’t Work

  • Railway’s database: Two weeks of random failures, never resolved
  • Render’s free database: No table inspection = blind development
  • Cross-origin cookies with SameSite=none: Browsers too strict, gave up
  • Building Prisma Client at build time: DATABASE_URL not available

The Turning Point

The Vercel proxy was the breakthrough. After days fighting browser cookie policies, realizing we could make requests appear same-origin changed everything. Sometimes the best solution isn’t fixing the problem—it’s eliminating the problem entirely.

Resources & References

Official Documentation

Code Repository

Tools Used

  • Angular 20 - Frontend framework
  • Express - Backend framework
  • Prisma 5.22 - ORM with type safety
  • PostgreSQL 16 - Database (via Neon)
  • Docker - Backend containerization
  • TypeScript - Full-stack type safety

Final Thoughts

Deployment tutorials usually show the happy path. This one showed you the real path—including the failures, the pivots, and the debugging sessions that lasted hours.

The truth? Deployment is hard. Platforms fail. Cookies don’t work. Databases won’t connect. And that’s okay.

What matters is having a debugging process:

  1. Read the error message (actually read it, don’t skim)
  2. Check the logs (Render/Vercel/Neon all have good logging)
  3. Verify environment variables (80% of issues)
  4. Test each layer independently (database → backend → frontend)
  5. Google the exact error message (someone else hit this)

Remember: The architecture in this guide took three platform attempts to get right. Your first deploy probably won’t be perfect either. That’s expected. That’s learning.

Now go deploy something awesome! 🚀


Need Help?

If you run into issues:

  1. Check Part 6 (Troubleshooting) - we documented every problem we hit
  2. Review logs: Render logs show backend errors, Vercel logs show build issues, Neon shows query problems
  3. Open an issue on the GitHub repo
  4. Read the error: Seriously, read the entire error message. The solution is usually in there.

Found this guide helpful? Star the GitHub repo and share with others fighting deployment battles. Let’s help each other deploy better!