Migrating from Vercel to Hop3¶
Thinking about moving a Next.js app off Vercel? Maybe you want predictable flat-rate costs instead of per-invocation and bandwidth billing, no cold starts on a low-traffic route, or data that lives on infrastructure you control. Hop3 runs your full-stack Next.js app as a single always-on Node process on your own server. This guide helps you migrate an existing Vercel project to Hop3 — and is clear about where the two platforms differ.
Is Hop3 a Good Fit?¶
Read this section first. Vercel and Hop3 have different shapes, and the migration is smooth only when your app fits.
Hop3 is a single always-on server. It runs your Next.js app as one long-lived Node process (next start, or the standalone server), behind a reverse proxy, on a box you provision. That is the ideal target for a server-rendered / full-stack Next.js app: SSR pages, API routes, and server actions all served by that one process.
It is not a fit for an app built around edge functions or heavy serverless fan-out:
- No global edge / CDN. Hop3 is a single origin server in one region. Vercel's global edge network has no Hop3 equivalent — you serve from one place.
- No per-function autoscaling. Each route does not scale independently. You scale the whole process, manually, with
hop3 ps scale. - No managed preview deployments. There are no per-PR review apps. You run a separate staging app instead.
- A purely static site works, but loses the CDN. Hop3 has a static-site builder, so a fully static export deploys fine — you just serve it from your one origin instead of Vercel's CDN.
If your app leans on the edge runtime, on serverless functions scaling to zero and back, or on a worldwide CDN as a core feature, Hop3 is the wrong tool — stay where you are. If your Next.js app is really a web server that happens to be deployed serverless, read on.
What's Similar¶
The day-to-day workflow will feel familiar:
| Vercel | Hop3 |
|---|---|
git push (Git-connected deploy) |
git push hop3 main |
| Project environment variables | [env] in hop3.toml / hop3 env set |
| Vercel Postgres / KV / Blob | hop3 addon (postgres / redis / s3) |
| Custom domains | hop3 domain add + ACME |
| Build & output settings | [build] in hop3.toml |
Your Next.js source doesn't change. You point a Git remote at your server and push, and Hop3 builds and runs it.
One thing to internalize up front: on Hop3 the app you're targeting is always the --app flag, never a positional argument. So a command reads hop3 env set FOO=bar --app myapp.
What's Different¶
| Vercel | Hop3 |
|---|---|
| Managed platform | Self-hosted |
| Serverless / edge functions | One always-on Node process |
| Global edge network + CDN | Single origin server, one region |
| Per-function autoscaling | Manual scaling (hop3 ps scale) |
| Preview deployments per PR | Not supported — use a staging app |
| Vercel Postgres / KV / Blob | PostgreSQL, MySQL, Redis, S3/MinIO — and growing |
Hop3 is simpler in operation: one server, one process, one bill. You give up the edge and the magic; you get always-on behavior, flat cost, and control.
How the Concepts Map¶
The migration is mostly a translation of Vercel primitives to Hop3 ones. Here's the concrete mapping:
| Vercel primitive | On Hop3 |
|---|---|
| The Next.js app | Node toolchain runs next build, then next start (or the standalone server) as the long-lived web process |
| API routes / server actions | Served by that same Node process — not split into separate serverless functions |
| Edge functions | No equivalent — must run in the Node process (or stay on Vercel) |
| Environment variables | [env] in hop3.toml (note: NEXT_PUBLIC_* are build-time / public) |
| Vercel Postgres | hop3 addon create postgres … |
| Vercel KV | hop3 addon create redis … |
| Vercel Blob | hop3 addon create s3 … |
| Vercel Cron | [run.workers] or system cron |
| Custom domain | hop3 domain add + a real certificate once ACME is configured |
The key shift is the second row: on Vercel your API routes become independent serverless functions; on Hop3 they're handled by the one Node server that also renders your pages. For a full-stack app that's usually exactly what you want — no cold starts, shared in-memory state, one place to reason about.
We recommend Next.js standalone output mode for the Hop3 target. It produces a minimal, self-contained production server (.next/standalone/server.js) that's ideal for running as one long-lived process. Hop3 has a full Next.js tutorial covering this end to end.
Step 1: Set Up Your Hop3 Server¶
Provision a server (Ubuntu 24.04 LTS recommended; Debian, Fedora, and Rocky/Alma also work) and install Hop3:
Install the CLI on your local machine:
Connect the CLI to your server. On a first install this also creates the admin account and stores your API token:
Step 2: Export Your Vercel Configuration¶
Pull your environment variables from Vercel using their CLI:
Review the file. A few things to keep in mind as you translate it:
NEXT_PUBLIC_*variables are build-time and public — they get inlined into the client bundle at build. Keep them, but never put a secret behind aNEXT_PUBLIC_name.- Drop the connection strings Vercel managed for you (
POSTGRES_URL,KV_URL,BLOB_READ_WRITE_TOKEN, …) — Hop3 addons set their own (DATABASE_URL,REDIS_URL,S3_*). - Drop anything Vercel injected automatically (
VERCEL,VERCEL_URL,VERCEL_ENV, …). The port is auto-set by Hop3 as$PORT.
Step 3: Add hop3.toml¶
This is where you translate Vercel's project settings — build command, output, env vars, runtime — into one file. A representative full-stack Next.js app (standalone output) looks like this:
[metadata]
id = "your-app"
[build]
toolchain = "node"
before-build = "npm ci && npm run build"
[run]
start = "node .next/standalone/server.js"
before-run = "cp -r .next/static .next/standalone/.next/ && cp -r public .next/standalone/ 2>/dev/null || true"
[env]
NODE_ENV = "production"
# Public, build-time — inlined into the client bundle:
NEXT_PUBLIC_API_URL = "https://your-app.example.com"
# Server-only secrets:
SECRET_KEY = "your-secret-key"
[healthcheck]
path = "/"
A couple of notes on the translation:
next buildruns inbefore-build; the standalone server runs as thewebprocess via[run].start. Thebefore-runline copies the static assets next to the standalone server (Next.js standalone output doesn't bundle them by default).- A
Procfileis also supported if you prefer it for the process model — theweb:,prebuild:, andprerun:lines map onto the same fields. - If you'd rather pin the toolchain and dependency stack precisely, Hop3 also offers a Docker builder (
[build] builder = "docker") and a Nix builder.
Step 4: Migrate Your Data¶
This is the part that takes the most care, because Vercel's managed storage maps onto three different Hop3 addons.
The four addon types Hop3 offers are PostgreSQL, MySQL, Redis, and S3/MinIO (and growing). That covers the common Vercel storage triad:
| Vercel | Hop3 addon |
|---|---|
| Vercel Postgres | postgres |
| Vercel KV | redis |
| Vercel Blob | s3 |
Postgres (Vercel Postgres → Hop3 postgres)¶
Vercel Postgres is Postgres under the hood, so a standard dump-and-restore works. Dump from your Vercel database using the connection string from the dashboard:
Create the addon and attach it to your app:
Get the connection details:
Import the dump:
# Copy dump to server
scp latest.dump root@your-server.com:/tmp/
# SSH to server and import
ssh root@your-server.com
sudo -u postgres pg_restore --verbose --no-owner \
-d your_app_db /tmp/latest.dump
If your schema needs a Postgres extension (pgvector, pg_stat_statements, …), enable it through the addon command — no manual psql needed:
Extensions are allow-listed, so only the ones Hop3 ships support for will install.
Once attached, the addon injects DATABASE_URL into your app's environment — point your code at that instead of POSTGRES_URL.
KV (Vercel KV → Hop3 redis)¶
Vercel KV is Redis-compatible. Create a Redis addon and attach it:
It injects REDIS_URL. Most KV usage is cache, so starting fresh is usually fine — if you need to carry data over, export it from your KV instance and load it into the addon.
Blob (Vercel Blob → Hop3 s3)¶
Vercel Blob maps to Hop3's S3/MinIO addon:
It injects S3_* connection variables. Copy your existing blobs into the new bucket (e.g. with rclone or the aws s3 CLI pointed at both endpoints), then update your code to read the S3_* vars.
Keeping an external managed database¶
You don't have to move the data at all. If you'd rather keep an existing managed Postgres (Neon, RDS, your current Vercel Postgres), just point [env] at its URL and skip the addon:
Step 5: Deploy to Hop3¶
Add Hop3 as a remote:
Deploy:
Hop3 detects the Node toolchain, runs your before-build (next build), and starts the standalone server as the web process.
Step 6: Point Your Domain¶
Add the hostname to your app, then update DNS:
Once Let's Encrypt is configured on the server, Hop3 requests a certificate for the domain at deploy time. Out of the box, a freshly installed server serves a self-signed certificate until you point it at an ACME provider.
Common Migration Issues¶
Edge functions¶
If your app uses the edge runtime (middleware on the edge, export const runtime = 'edge'), those paths have no Hop3 equivalent — Hop3 runs one Node process, not an edge network. Move that logic into the Node runtime, or keep it on Vercel. There's no way to fake the edge on a single origin server, and we won't pretend otherwise.
API routes and server actions¶
These need no change in your code — but understand what moved. On Vercel each route may run as its own serverless function; on Hop3 they all run inside the one Node process that also renders your pages. Long-running work that you relied on a separate function (and its own timeout) to handle should move to a worker (see below) rather than blocking a request.
NEXT_PUBLIC_ variables¶
NEXT_PUBLIC_* values are read at build time and inlined into the client bundle. They live in [env] like any other variable, but changing one requires a rebuild (git push hop3 main again) to take effect — setting it with hop3 env set and restarting is not enough, because the value was baked into the bundle at build.
Scheduled jobs (Vercel Cron)¶
Vercel Cron hits an endpoint on a schedule. On Hop3 you have two options.
A long-running worker process, declared alongside your web process:
Or system cron, if you just want to hit a route on a schedule the way Vercel Cron did:
ssh root@your-server.com
crontab -e
# Add: 0 * * * * curl -fsS https://your-app.example.com/api/cron/cleanup
Preview deployments¶
Hop3 has no per-PR preview deployments. The workaround is a dedicated staging app:
- Deploy a second app, e.g.
your-app-staging, from your staging branch. - Or use branch-based names:
your-app-feature-x.
Each is a real, separate Hop3 app — there is no automatic per-PR lifecycle.
Scaling¶
There is no per-function or per-route autoscaling, and no automatic scaling at all. You scale the whole process manually:
For a single-server full-stack app this is usually a deliberate, infrequent operation, not something the platform does for you under load.
Command Mapping¶
| Vercel CLI | Hop3 Command |
|---|---|
vercel / vercel deploy |
git push hop3 main or hop3 deploy |
vercel ls / vercel projects ls |
hop3 app list |
vercel inspect |
hop3 app status --app X |
vercel env ls |
hop3 env show --app X |
vercel env add |
hop3 env set K=V --app X |
vercel logs |
hop3 app logs --app X |
vercel logs |
hop3 app logs --app X |
| (Vercel storage dashboard) | hop3 addon create / hop3 addon attach |
vercel domains add |
hop3 domain add <host> --app X |
| (one-off task) | hop3 app run --app X |
A note on importing config: Hop3's only config importer is
hop3 app migrate procfile <dir>, which turns aProcfileinto ahop3.toml. There is no importer forvercel.json— the build command, output mode, env, and cron settings are translated by hand, exactly as shown in Step 3. That's a small one-time cost, and the result is a single file you can read and version.
Cost and Lock-In¶
Vercel's pricing is usage-based: function invocations, bandwidth, and managed-storage tiers. That's friendly at zero traffic and can spike unpredictably under load or with heavy bandwidth. Hop3 is a flat VPS bill — you pay for the box whether it serves one request or a million.
| Vercel | Hop3 (self-hosted) | |
|---|---|---|
| Compute | per-invocation + bandwidth | flat VPS (~$10–30/month) |
| Postgres / KV / Blob | metered managed tiers | included on your server (addons) |
| Cold starts | possible on idle routes | none (always-on process) |
| Bill predictability | varies with traffic | fixed |
Beyond cost, the angle is control: always-on behavior with no cold starts, a predictable monthly number, and data residency on infrastructure you own. The trade-off is real and worth restating — you run the server, you have one region, and you scale by hand. For a Next.js app that doesn't need the edge, that's a fair trade.
Rollback Plan¶
Keep your Vercel project running during the migration:
- Deploy to Hop3 on a test hostname (e.g.
your-app-new.example.com). - Verify everything works — SSR pages, API routes, database, cron.
- Switch DNS to point at your Hop3 server.
- Keep the Vercel project live for 1–2 weeks.
- Tear down the Vercel project once you're confident.
Because DNS is the only switch, rollback is just pointing the record back at Vercel.
Getting Help¶
Coming from a different platform? The concepts are similar. Check the Getting Started guide for a fresh start.