Moving Node.js API routes, Vercel Cron, a Neon or Vercel Postgres database and Blob uploads onto Encore in your own AWS account, while the frontend stays where it is.
Many Node.js backends on Vercel started as a few files in api/ or app/api/ next to a Next.js frontend: a POST /api/orders function that writes to Postgres from the Neon integration, a GET /api/invoices/[id] that hands out a Blob URL, and a vercel.json with a crons entry that hits /api/cron/expire every hour. It starts to pinch when the functions need background work that outlives a request, when connection counts from many short-lived function instances hit the database limit, or when the backend has to run in an AWS account the company controls.
This guide moves the backend part onto Encore in your own AWS account and leaves the frontend on Vercel. The API becomes a long-running service with its own database, schedule and bucket, tested locally with encore run, and the frontend switches to calling its URL. The generic Vercel to AWS migration guide covers the hosting side, including moving the frontend.
Encore combines an open source infrastructure SDK for TypeScript and Go with a platform that provisions what the code declares in your own AWS or GCP account. The functions become endpoints in a service that stays running, which holds a connection pool to Postgres and can publish work to a queue, and encore run starts the service with Postgres and a local bucket so the whole backend runs on a laptop without the Vercel CLI.

Install the CLI and create the Encore app in its own directory, such as backend/ next to the Next.js app:
The Postgres connection that each function opened from POSTGRES_URL becomes a database declared once per service. The existing schema goes into numbered SQL files in shop/migrations:
// shop/db.ts
import { SQLDatabase } from "encore.dev/storage/sqldb";
// Replaces the managed Postgres add-on: a local database under `encore run`,
// RDS when deployed to AWS.
export const db = new SQLDatabase("shop", { migrations: "./migrations" });
Each route handler becomes an endpoint with typed input and output. Validation errors and missing rows turn into 400 and 404 responses through APIError:
// shop/orders.ts
import { api, APIError } from "encore.dev/api";
import { db } from "./db";
export interface Order {
id: number;
email: string;
totalCents: number;
status: string;
}
export const createOrder = api(
{ expose: true, method: "POST", path: "/orders" },
async (p: { email: string; totalCents: number }): Promise<Order> => {
if (!p.email.includes("@") || p.totalCents < 0) {
throw APIError.invalidArgument("a valid email and a non-negative total are required");
}
const order = await db.queryRow<Order>`
INSERT INTO orders (email, total_cents) VALUES (${p.email}, ${p.totalCents})
RETURNING id, email, total_cents AS "totalCents", status`;
return order!;
},
);
export const getOrder = api(
{ expose: true, method: "GET", path: "/orders/:id" },
async ({ id }: { id: number }): Promise<Order> => {
const order = await db.queryRow<Order>`
SELECT id, email, total_cents AS "totalCents", status FROM orders WHERE id = ${id}`;
if (!order) throw APIError.notFound("order not found");
return order;
},
);
Without the /api prefix from Vercel, either add it to each path or rewrite it on the frontend side. Endpoints are documented in the local development dashboard, and encore gen client writes a typed TypeScript client the Next.js app can import, as the client generation docs describe.
A crons entry in vercel.json calls a route over HTTP, which means the route is public unless you check the CRON_SECRET header. An Encore cron job calls a private endpoint instead, which is not reachable from the internet:
// shop/jobs.ts
import { api } from "encore.dev/api";
import { CronJob } from "encore.dev/cron";
import { db } from "./db";
// Only touches rows that are still pending, so a repeated run is harmless.
export const expirePending = api(
{ expose: false, method: "POST", path: "/internal/expire-pending" },
async (): Promise<{ cancelled: number }> => {
const rows = await db.queryAll`
UPDATE orders SET status = 'cancelled'
WHERE status = 'pending' AND created_at < now() - interval '24 hours'
RETURNING id`;
return { cancelled: rows.length };
},
);
// Replaces the scheduler entry on the old platform.
const _ = new CronJob("expire-pending", {
title: "Cancel orders left pending for a day",
every: "1h",
endpoint: expirePending,
});
Cron jobs do not run under encore run, so call the endpoint directly while developing. Once deployed, remove the crons entry from vercel.json in the same release.
Files the app stored in Vercel Blob move to an object storage bucket. The frontend asks the API for a short-lived signed URL and downloads from S3 directly:
// shop/uploads.ts
import { api } from "encore.dev/api";
import { Bucket } from "encore.dev/storage/objects";
// Replaces files written to a mounted volume or a hosted blob store:
// a local bucket under `encore run`, S3 when deployed to AWS.
export const invoices = new Bucket("invoices");
export const uploadInvoice = api(
{ expose: true, method: "POST", path: "/invoices/:id" },
async ({ id, contents }: { id: string; contents: string }): Promise<{ key: string }> => {
const key = `invoice-${id}.txt`;
await invoices.upload(key, Buffer.from(contents), { contentType: "text/plain" });
return { key };
},
);
export const invoiceLink = api(
{ expose: true, method: "GET", path: "/invoices/:id/link" },
async ({ id }: { id: string }): Promise<{ url: string }> => {
const { url } = await invoices.signedDownloadUrl(`invoice-${id}.txt`, { ttl: 300 });
return { url };
},
);
For larger files, signedUploadUrl lets the browser upload straight to the bucket as well. Running it all locally:
Add the production frontend's domain to allow_origins_with_credentials under the global_cors key in encore.app if the browser sends cookies, as the CORS docs describe, and set the API URL as an environment variable in the Vercel project. Unauthenticated requests from any origin are allowed by default.
After you connect your AWS account and push, Encore provisions the RDS database, the S3 bucket, the schedule and the Fargate service in your own account, and provisions an IAM role for the service. Each pull request can get a preview environment, which pairs with Vercel's preview deployments of the frontend.

A coding agent can carry a feature across both halves in one branch: the endpoint and its table in backend/, the page that calls it in the Next.js app, with encore run and next dev side by side to check the result.
Without Encore, the closest match to Vercel functions on AWS is Lambda:
s3:GetObject and s3:PutObject, and generate presigned URLs with the AWS SDK.crons entries with EventBridge Scheduler schedules that invoke the functions.An ECS service on Fargate avoids the connection problem at the cost of a load balancer, a task definition and an image pipeline.
Postgres created through Vercel now runs on Neon, which accepts standard pg_dump connections. Pause writes on the frontend, dump in custom format and restore the data into the Encore database:
Use the direct (non-pooling) connection string for the dump, since pg_dump does not work well through a transaction-mode pooler, and check the pg_dump docs if you need to exclude tables. Copy Blob files to the bucket with a one-off script that lists them and uploads each one with the same key.
Vercel alternatives covers frontend and full-stack platforms, and How to deploy Node.js to AWS compares every AWS compute option.
Move the API routes into a long-running backend and leave the frontend where it is. With Encore, each function in api/ becomes an endpoint declared with api(), Vercel Cron entries in vercel.json become CronJob declarations, Blob uploads move to a Bucket and the Postgres connection becomes an SQLDatabase. encore run starts all of it locally, and on deploy Encore provisions Fargate, RDS and S3 in your AWS account. The frontend then calls the new API URL.
Yes. Deploy the backend to your AWS account, set its URL as an environment variable in the Vercel project, and allow the frontend's origin in the backend's CORS settings. If the browser sends cookies, the origin has to be listed explicitly rather than allowed with a wildcard.
EventBridge Scheduler triggering a Lambda function or an ECS task on the same cron expressions vercel.json used. Vercel Cron schedules are in UTC, and so are EventBridge Scheduler schedules unless you set a time zone.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.