Migrate from Vercel
Every Vercel feature has a bext equivalent, either built-in or via the plugin system. This guide walks through the feature mapping, configuration translation, and DNS cutover process.
Feature Mapping
| Vercel Feature | bext Equivalent | License Tier |
|---|---|---|
| Edge Functions | bext plugins (QuickJS / WASM) | Community |
| ISR | bext ISR cache ([cache.isr]) |
Community |
| Image Optimization | bext image ([image]) |
Community |
Middleware (middleware.ts) |
bext plugins or native middleware | Community |
| Serverless Functions | bext SSR routes / API routes | Community |
| Analytics | Prometheus metrics + Grafana | Community |
| Speed Insights | OpenTelemetry integration | Pro |
| Environment Variables | bext.config.toml [env] section |
Community |
| Preview Deployments | bext deploy with branch-based routing |
Community |
| Cron Jobs | bext task scheduler ([[tasks]]) |
Community |
| Firewall Rules | bext WAF ([waf]) |
Pro |
| DDoS Protection | bext rate limiting + WAF | Community/Pro |
| Custom Domains | bext Auto-TLS ([tls]) |
Community |
| Monorepo support | bext multi-app mode | Community |
vercel.json to bext.config.toml
Rewrites
vercel.json:
{
"rewrites": [
{ "source": "/blog/:slug", "destination": "/posts/:slug" },
{ "source": "/api/:path*", "destination": "https://api.backend.com/:path*" }
]
}
bext.config.toml:
[[route_rules]]
pattern = "/blog/:slug"
rewrite = "/posts/:slug"
[[route_rules]]
pattern = "/api/**"
upstream = "https://api.backend.com"
strip_prefix = "/api"
Redirects
vercel.json:
{
"redirects": [
{ "source": "/old", "destination": "/new", "permanent": true }
]
}
bext.config.toml:
[[route_rules]]
pattern = "/old"
action = "redirect"
redirect = "/new"
redirect_status = 301
Headers
vercel.json:
{
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" }
]
}
]
}
bext.config.toml:
[[route_rules]]
pattern = "/api/**"
headers = { "access-control-allow-origin" = "*" }
Environment Variables
Vercel Dashboard:
DATABASE_URL = postgres://...
NEXT_PUBLIC_API_URL = https://api.example.com
bext.config.toml:
[env]
DATABASE_URL = "postgres://..."
NEXT_PUBLIC_API_URL = "https://api.example.com"
Or use a .env file (loaded automatically) or OS environment variables. Precedence: OS env > bext.config.toml [env] > .env file.
Cron Jobs
vercel.json:
{
"crons": [
{ "path": "/api/cleanup", "schedule": "0 3 * * *" }
]
}
bext.config.toml:
[[tasks]]
name = "cleanup"
schedule = "0 3 * * *"
handler = "http::GET /api/cleanup"
enabled = true
Edge Functions to bext Plugins
Vercel Edge Functions run at the CDN edge. In bext, the equivalent is a QuickJS or WASM plugin that runs inline with the request:
Vercel Edge Function (app/api/geo/route.ts):
export const runtime = 'edge';
export function GET(request: Request) {
const country = request.headers.get('x-vercel-ip-country');
return new Response(JSON.stringify({ country }));
}
bext QuickJS plugin (plugins/geo.js):
export default {
name: "geo",
hooks: {
onRequest(ctx) {
if (ctx.path === "/api/geo") {
const country = ctx.headers["x-real-country"] || "unknown";
return { status: 200, body: JSON.stringify({ country }) };
}
}
}
};
Register the plugin:
[plugins]
directory = "plugins"
Preview Deployments
Vercel creates a unique URL for each git push. With bext, use branch-based multi-app routing:
# Deploy a preview for a feature branch
bext-server deploy ./my-app --app preview-feature-xyz
Configure the preview to be accessible on a subdomain:
# platform.toml
[[apps]]
name = "preview-feature-xyz"
hostname = "preview-feature-xyz.app.example.com"
source = "/var/bext/apps/preview-feature-xyz"
Combine with wildcard DNS (*.app.example.com) and Auto-TLS for automatic preview URLs.
DNS Cutover Checklist
When you are ready to switch production traffic from Vercel to bext:
1. Prepare the bext server
# Verify config is valid
bext-server check
# Build and start
bext-server build
bext-server run
2. Test with a hosts file override
# /etc/hosts (your machine only)
<bext-server-ip> app.example.com
Visit https://app.example.com and verify all pages, API routes, and assets load correctly.
3. Lower DNS TTL
24 hours before the cutover, lower the DNS TTL to 60 seconds so the switch propagates quickly:
app.example.com. 60 IN A <bext-server-ip>
4. Update DNS records
Remove the Vercel CNAME and add an A record pointing to your bext server:
# Before (Vercel)
app.example.com. CNAME cname.vercel-dns.com.
# After (bext)
app.example.com. 60 IN A 203.0.113.10
If using bext's Auto-TLS, the certificate is issued automatically on first request after DNS propagates.
5. Verify
# Confirm DNS points to bext
dig +short app.example.com
# Confirm TLS is working
curl -I https://app.example.com
# Check health
curl https://app.example.com/__bext/health
6. Restore DNS TTL
After 24 hours of stable operation, raise the TTL back to a normal value (3600 or higher).
7. Remove Vercel project
Once you have confirmed everything works, delete the Vercel project to stop billing.
What You Gain
Moving from Vercel to bext gives you:
- No per-request pricing -- flat infrastructure cost regardless of traffic
- No cold starts -- bext keeps render workers warm, SSR is always fast
- Full control -- logs, metrics, caching behavior, and network config are yours
- No vendor lock-in -- bext is open source; your deployment is portable
- Redis L2 cache -- cache survives deploys and is shared across instances
- Plugin system -- extend the server with WASM, QuickJS, or nsjail sandboxed code