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