How to Migrate from Vercel to Self-Hosted: Complete Guide
How to Migrate from Vercel to Self-Hosted: Complete Guide
January 17, 2026 (1mo ago)
Written by Temps Team
Last updated January 17, 2026 (1mo ago)
Moving your Next.js application from Vercel to a self-hosted platform saves money and gives you infrastructure control. This guide covers the exact steps to migrate, what breaks during migration, and how to fix it.
Why Teams Leave Vercel
Three patterns trigger most Vercel departures:
- Cost scaling — Bills that start at $20 grow to $500+ as teams and traffic increase
- Vendor lock-in — Features like Edge Middleware and ISR create Vercel-specific dependencies
- Data control — Compliance requirements demanding data stays on owned infrastructure
If any of these sound familiar, migration makes sense.
Before You Migrate: Compatibility Check
What Works Everywhere
These Next.js features transfer to any platform without changes:
- Static pages (SSG)
- Server-side rendering (SSR)
- API routes
- Image optimization (with configuration)
- Middleware (basic redirect/rewrite logic)
- Incremental Static Regeneration (ISR)
What Needs Adjustment
These Vercel-specific features require alternatives:
| Vercel Feature | Alternative |
|---|---|
| Edge Middleware | Standard Node.js middleware |
| Edge Runtime | Node.js runtime |
| Vercel Analytics | Temps built-in / Plausible / Fathom |
| Vercel KV | Redis (Upstash, self-hosted) |
| Vercel Postgres | Any PostgreSQL (Neon, Supabase, self-hosted) |
| Vercel Blob | S3-compatible storage (MinIO, Cloudflare R2) |
@vercel/og | Standard ImageResponse from Next.js |
What Doesn't Transfer
- Vercel's specific edge locations and CDN configuration
- Vercel-proprietary analytics dashboards
- Speed Insights (use Core Web Vitals monitoring instead)
Migration Path 1: Vercel → Temps
Temps provides the closest experience to Vercel with self-hosted infrastructure. Most apps migrate without code changes.
Step 1: Export Environment Variables
From Vercel dashboard:
- Go to Project Settings → Environment Variables
- Export all variables or note them manually
# Or use Vercel CLI
vercel env pull .env.local
Step 2: Set Up Temps
# Install Temps on your server
curl -fsSL https://temps.sh/deploy.sh | bash
# Login to your Temps instance
bunx @temps-sdk/cli login
Step 3: Import Your Project
- Open Temps dashboard (default:
http://your-server:3000) - Click "New Project" → "Import from GitHub"
- Select your repository
- Add environment variables from Step 1
- Deploy
Step 4: Update DNS
Replace Vercel's DNS records with Temps-provided values:
# Remove
CNAME @ cname.vercel-dns.com
CNAME www cname.vercel-dns.com
# Add
CNAME @ your-temps-domain.temps.sh
CNAME www your-temps-domain.temps.sh
Step 5: Verify
- Site loads correctly
- All pages render (SSR, SSG, API routes)
- Forms and authentication work
- Images load properly
- Analytics capturing data
Migration time: 15-30 minutes
Migration Path 2: Vercel → Docker + VPS
For teams wanting maximum control, Docker deployment offers complete infrastructure ownership.
Step 1: Enable Standalone Output
Add to next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
};
module.exports = nextConfig;
Step 2: Create Dockerfile
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci
# Build application
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Copy built application
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
Step 3: Set Up VPS
On DigitalOcean, Hetzner, or your preferred provider:
# Install Docker
curl -fsSL https://get.docker.com | sh
# Clone your repo
git clone https://github.com/your/repo.git
cd repo
# Build image
docker build -t my-nextjs-app .
# Run container
docker run -d \
--name my-app \
-p 3000:3000 \
--env-file .env.production \
my-nextjs-app
Step 4: Set Up Reverse Proxy (nginx)
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Step 5: SSL with Let's Encrypt
# Install certbot
apt install certbot python3-certbot-nginx
# Get certificate
certbot --nginx -d yourdomain.com
Migration time: 1-2 hours
Code Changes You May Need
Removing Vercel Analytics
Before:
// app/layout.tsx
import { Analytics } from "@vercel/analytics/react";
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
</body>
</html>
);
}
After (with Temps or Plausible):
// app/layout.tsx
// Temps: Analytics included automatically, no code needed
// Or with Plausible:
import Script from "next/script";
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Script
defer
data-domain="yourdomain.com"
src="https://plausible.io/js/script.js"
/>
</body>
</html>
);
}
Replacing Edge Runtime
Before (Vercel Edge):
// app/api/hello/route.ts
export const runtime = "edge";
export async function GET() {
return Response.json({ message: "Hello" });
}
After (Node.js):
// app/api/hello/route.ts
// Remove the runtime declaration - uses Node.js by default
export async function GET() {
return Response.json({ message: "Hello" });
}
Replacing Vercel KV
Before:
import { kv } from "@vercel/kv";
export async function GET() {
const value = await kv.get("key");
return Response.json({ value });
}
After (with Upstash or self-hosted Redis):
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: process.env.REDIS_URL,
token: process.env.REDIS_TOKEN,
});
export async function GET() {
const value = await redis.get("key");
return Response.json({ value });
}
Replacing Vercel Postgres
Before:
import { sql } from "@vercel/postgres";
export async function GET() {
const { rows } = await sql`SELECT * FROM users`;
return Response.json({ users: rows });
}
After (with any PostgreSQL):
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
export async function GET() {
const { rows } = await pool.query("SELECT * FROM users");
return Response.json({ users: rows });
}
Image Optimization
Next.js image optimization works on all platforms. You may need to configure allowed domains:
// next.config.js
const nextConfig = {
output: "standalone",
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**.yourdomain.com",
},
],
},
};
module.exports = nextConfig;
Post-Migration Checklist
Functionality
- All pages load correctly (SSR, SSG, static)
- API routes respond properly
- Authentication flows work
- Forms submit successfully
- File uploads function
- Images display and optimize
- Middleware redirects/rewrites work
Performance
- TTFB (Time to First Byte) acceptable
- Core Web Vitals passing
- No significant cold starts
- Caching working correctly
Operations
- SSL certificate valid and auto-renewing
- Environment variables configured
- Logging capturing errors
- Monitoring/alerting set up
- Backup strategy in place
DNS
- Domain pointing to new infrastructure
- Old Vercel DNS records removed
- SSL certificate covers all subdomains
- Redirect from www to apex (or vice versa)
Common Migration Issues
Issue: Build fails with "Module not found"
Cause: Vercel auto-installs some dependencies that aren't in your package.json
Fix: Add missing dependencies explicitly:
npm install sharp # For image optimization
Issue: API routes return 404
Cause: Incorrect build output or routing configuration
Fix: Ensure output: 'standalone' is set and rebuild
Issue: Images not loading
Cause: Image optimization requires Sharp package
Fix:
npm install sharp
Issue: Environment variables not found
Cause: Variables not set in new environment
Fix: Verify all env vars are configured. Use .env.production for Docker or dashboard for Temps.
Issue: Middleware not executing
Cause: Edge runtime not supported
Fix: Remove runtime: 'edge' if present. Standard middleware works on all platforms.
Cost Savings After Migration
Typical Before/After
| Metric | Vercel | Self-Hosted |
|---|---|---|
| 5-person team | $100/mo | $0 |
| 500GB bandwidth | $160/mo | $0 |
| Sentry (errors) | $29/mo | Included* |
| Analytics | $19/mo | Included* |
| Monthly total | $308 | $40-50 |
*With Temps. Docker requires separate tools.
Annual Savings
| Team Size | Vercel Annual | Self-Hosted Annual | Savings |
|---|---|---|---|
| 3 devs | $1,500+ | $400-600 | $900+ |
| 10 devs | $3,600+ | $600-900 | $2,700+ |
| 25 devs | $8,000+ | $900-1,200 | $6,800+ |
When to Stay on Vercel
Migration isn't always the right choice. Stay on Vercel if:
- Zero DevOps tolerance — Your team has no one to manage infrastructure
- Heavy Edge usage — You rely on Edge Middleware and global edge functions
- Enterprise features — You need Vercel's SOC2, HIPAA, or enterprise support
- Convenience over cost — Time is more valuable than the savings
Next Steps
Ready to Migrate?
- Audit your app — Check for Vercel-specific features using this guide
- Choose your platform — Temps for easiest migration, Docker for maximum control
- Test in staging — Deploy to a test environment first
- Schedule migration — Plan DNS cutover during low-traffic period
- Monitor closely — Watch for issues in the first 48 hours
Get Help
This guide covers Next.js 14+ with App Router. Pages Router apps follow similar patterns. Always test thoroughly before production migration.