slew docs/start/migrate-from-vercel

Migrate from Vercel

The workflow you have on Vercel — connect a repo, push, get a live URL, preview deploys per PR — is the workflow slew is built around. A migration is four things: connect the git repo, copy the env vars, translate the config, point DNS. The importer does the first three in one command, and nothing on Vercel changes until you flip DNS yourself, so there is no moment where the site is down and no way back.

The short version

cd your-project
slew login
slew import vercel --dry-run   # compat report + plan, creates nothing
slew import vercel             # do it

The importer reads vercel.json and package.json, and — when a Vercel token is set (--token or VERCEL_TOKEN, in a directory linked with vercel link) — also pulls the project's build settings, production env vars, connected repo and domains from the Vercel API. It is strictly read-only towards Vercel. It then:

  1. Prints a compatibility report. Hard incompatibilities stop the import before anything is created; everything else is stated plainly and carried on.
  2. Creates the slew project and links the directory.
  3. Copies production env vars (scope both, so builds see them too). Sensitive vars — which Vercel's API never returns decrypted — are listed by name to set by hand.
  4. Recreates crons and translates redirects into slew's redirect rules.
  5. Connects the GitHub repo with your Vercel build settings — every push now builds and deploys, PRs get preview URLs.
  6. Prints the DNS cutover checklist below, filled in with your actual domains.

No Vercel token? The importer still does everything it can from the local files, and env vars are two commands:

vercel env pull --environment=production .env.production
slew env import .env.production --scope both

slew env import reads any dotenv file and skips the VERCEL_* keys Vercel injects.

What maps to what

On Vercel On slew
git push → deploy Same — git push-to-deploy via the GitHub App
Preview deployments per PR Same — expiring preview URLs, commented on the PR
Preview branch domain Branch domains: slew domains add staging.example.com --branch staging
Environment variables slew env — encrypted at rest, runtime/build/both scopes
vercel.json crons slew cron — 5-field cron, UTC, up to 5 per project
vercel.json redirects slew.json redirects — translated by the importer
ISR / revalidate Works — platform cache + on-demand revalidation endpoint
Image optimization Included on every plan, served from the CDN edge
Analytics Included, cookieless, every plan
Instant rollback slew rollback <deployment-id> — re-points, no rebuild
Serverless / edge functions Different model: your app runs as one real Node server — see below

What's genuinely different

Being straight about this is the point — a migration that fails on first deploy is worse than one that tells you up front:

  • One runtime: Node 22, as a long-running server. No Python, Go, Ruby or Rust function runtimes, no edge runtime. The importer refuses to continue if vercel.json declares one.
  • No serverless decomposition. Your Next.js app runs in standalone mode as the server the framework was written to be. That kills cold-start arithmetic and makes WebSockets and in-process state work — and it means per-function config (functions, regions) has nothing to configure.
  • Redirects and same-site rewrites translate; headers don't. vercel.json redirects carry over with their exact status codes, and rewrites whose destination is a path on your own site become slew rewrites. Rewrites that proxy to another origin, and headers, are the exceptions: for Next.js they cost nothing — rewrites() and headers() in next.config.js work on slew because the app is a real server — but static sites have no platform-level headers yet.
  • One region. Rendering happens in Germany behind a CDN; static assets and ISR pages are cached at the edge. If you need rendering in dozens of PoPs, slew is the wrong tool — better to read that here than find out in production.
  • middleware runs in your server, in the EU, not on an edge fleet. Same behavior, one region.
  • cleanUrls is the default — extensionless paths already resolve to their .html file. Just drop the key.

Per-framework notes

Next.js — add one line, if it isn't there already:

// next.config.js
module.exports = { output: 'standalone' }

The builder detects the standalone build and deploys it as a server; SSR, API routes, middleware, server actions, ISR and next/image all work. Details on the Next.js page.

Astro — static builds deploy as-is; SSR needs @astrojs/node (recipe). SvelteKit — swap @sveltejs/adapter-vercel for @sveltejs/adapter-node (SSR) or @sveltejs/adapter-static (recipe). Remix — works via the Remix recipe. Static sites (Vite, Astro, plain HTML) — nothing to change.

Lockfiles: the builder installs server-app production dependencies with npm ci, which needs a committed package-lock.json. On pnpm, yarn or bun, generate one alongside: npm i --package-lock-only. The importer warns when this applies to you.

Cutting over DNS

The one step the importer can't do for you. Nothing about your Vercel setup has changed yet — the site keeps serving from Vercel until DNS moves, and moving it back is the same edit in reverse.

  1. Check the deploy at https://<project>.slew.cloud. slew git log prints the build log; click through the site, verify env-dependent pages work.
  2. Lower the TTL on your domain's DNS record to 300 seconds, and wait out the old TTL. This makes the actual switch near-instant — and makes a rollback near-instant too.
  3. slew domains add example.com — prints the CNAME target to point the record at (apex domains: use your provider's ALIAS/ANAME, or the A record the command prints).
  4. Flip the record, then run slew domains add example.com again: TLS is provisioned once DNS resolves to the CDN, usually within a minute. slew domains cert example.com confirms the certificate. Expect a brief window on the very first switch where early resolvers reach slew before the certificate is issued — the 300s TTL keeps that window to about a minute; re-adding the domain retries issuance immediately.
  5. Remove the domain from the Vercel project, so Vercel's edge stops answering for it, and put the TTL back up.

Repeat 3–5 per domain. A www + apex pair counts as two records.

After the switch

  • Watch it: slew logs -f, slew stats, and the console's Traffic tab.
  • Preview deploys, branch domains and crons are already running — nothing else to enable.
  • Keep the Vercel project around, paused, for a week if it makes you comfortable. Rollback is: point DNS back. When you're sure, delete it.

Coming from Netlify instead? Same idea, own page: Migrate from Netlify.

Stuck anywhere? Email support — a first migration is exactly the kind of thing worth doing together over a screen share.