Skip to content
Docs

How to ship an Express app on Vercel

Deploy an Express app to Vercel with zero configuration. Configure response streaming, middleware, cron jobs, the Bun runtime, and observability.

Ben SabicContent Engineer

Express is a fast, unopinionated, minimalist web framework for Node.js. It gives you a thin layer over Node's HTTP server, a familiar middleware model, straightforward routing, and a large ecosystem of community middleware for tasks like authentication, body parsing, and logging.

On Vercel, you can deploy an Express app with zero configuration: your app runs as a single Vercel Function on Fluid compute, and you get response streaming, preview deployments, and observability without extra setup.

This guide walks you through deploying an Express app to Vercel from a template, the Vercel CLI, or a Git repository, then configuring features such as streaming, middleware, cron jobs, the Bun runtime, and observability.

Copy link to headingPrerequisites

Before you begin, make sure you have:

  • A Vercel account
  • Node.js 20+ and a package manager (e.g., npm)
  • An existing Express project, or a new one created from an Express template
  • A Git repository on GitHub, GitLab, or Bitbucket (if you want Git-based deployments)
  • Vercel CLI installed (npm i -g vercel)

Copy link to headingHow it works

When you deploy an Express app, Vercel detects the framework and builds it for the Vercel runtime. Your Express app handles requests through a single Vercel Function, which runs on Fluid compute by default. Your app scales up and down with traffic, and you pay only for the compute it uses, not for idle time.

Because Vercel ships zero-configuration detection for Express, you don't set a build command or output directory. Vercel reads your project, finds the file that exports your Express app (or starts it with a port listener), and applies the correct build settings.

Copy link to headingDeploy your Express app

You can ship an Express app to Vercel in three ways. Choose the one that fits where your code lives today.

Copy link to headingOption 1: Deploy from a template

The fastest way to ship an Express app is to start from a template. Browse the Express templates gallery, pick a starter, and deploy it. Vercel clones the template to your Git provider, creates a project, and deploys it with zero configuration.

Templates to start from include:

Copy link to headingOption 2: Start a new project with the Vercel CLI

To scaffold a new Express project locally, use the Vercel CLI init command. It clones Vercel's Express example into a folder named express.

  1. Create the project:
    Terminal
    vercel init express
  2. Install dependencies:
    Terminal
    cd express
    npm install
  3. Develop locally at http://localhost:3000. Run it with the Vercel CLI so your app behaves the same way it does in production:
    Terminal
    vercel dev
  4. Deploy your app. The first run links the project, and because it's the project's first deployment, Vercel creates a production deployment:
    Terminal
    vercel
  5. After that, vercel creates preview deployments. Deploy later changes to production with:
    Terminal
    vercel --prod

Copy link to headingOption 3: Deploy an existing Express app

If you already have an Express app, deploy it from Git or from the command line.

From Git: Push your project to GitHub, GitLab, or Bitbucket, then import it at vercel.com/new. Vercel detects Express automatically and deploys it with zero configuration.

From the CLI: From your project's root directory, run vercel to deploy. The first deployment of a new project is a production deployment. After that, vercel creates preview deployments and vercel --prod deploys to production. To pull project settings and environment variables for local development, run:

Terminal
vercel link
vercel env pull

For Vercel to detect your app, export your Express instance as the default export from one of the recognized entry files, such as app.ts, index.ts, or server.ts at your project root or under src/:

src/index.ts
import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.json({ message: 'Hello from Express on Vercel' });
});
export default app;

If your package.json uses "type": "commonjs", export the app with module.exports = app instead. You can also start the app with a port listener (app.listen) rather than a default export, and Vercel detects either pattern.

Copy link to headingUse Vercel features with Express

After your app is deployed, you can layer Vercel features onto it. Some work automatically, and others take a few lines of configuration in vercel.json.

Copy link to headingYour app runs as a single Vercel Function

Vercel bundles your entire Express app into a single Vercel Function. Every incoming request goes to that function, and Express's router matches the path to your route handlers, middleware, and error handling.

This function uses Fluid compute by default, which runs multiple requests concurrently within a single instance to reduce cold starts and the cost of I/O-bound work such as API calls and database queries. You don't configure anything to get this behavior.

Because your entire app ships as a single bundle, it must fit within the 250 MB limit for Vercel Functions. Vercel removes unneeded files from the bundle to keep it small, but it doesn't bundle your application code with a tool like Webpack or Rollup.

Copy link to headingStream responses

Vercel Functions stream responses by default on Node.js, so you can send data to the client as you produce it instead of waiting for the full response. Express builds on Node's response object, so you stream by writing chunks with res.write() and finishing with res.end():

src/index.ts
import express from 'express';
const app = express();
app.get('/stream', async (req, res) => {
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
req.on('close', () => {
console.log('Client disconnected');
});
for (const chunk of ['Hello', ' ', 'from', ' ', 'Express']) {
res.write(chunk);
await new Promise((resolve) => setTimeout(resolve, 200));
}
res.end();
});
export default app;

Streaming pairs well with Fluid compute: while your function waits between chunks, the same instance can serve other requests. To stream AI model output, AI SDK handles the response formatting for you.

Copy link to headingCombine Express middleware with Vercel Routing Middleware

Express and Vercel each have a middleware layer, and they solve different problems. Express middleware runs inside your app's router, after the request reaches your function. Use it for app-level concerns such as body parsing, logging, CORS, and authentication:

src/index.ts
import express from 'express';
import cors from 'cors';
const app = express();
app.use(express.json());
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});
app.use('/posts', cors());
app.post('/posts', (req, res) => {
res.json({ ok: true });
});
export default app;

Here, express.json() is built in, while cors comes from the cors package you install separately. Vercel Routing Middleware runs at the edge, before the request reaches your Express app. Use it for rewrites, redirects, and header changes that should happen before any function runs. The two layers work together, with Routing Middleware shaping the request at the edge and Express middleware handling it inside your app.

Copy link to headingServe static assets from the CDN

To serve static files such as images, fonts, or a favicon, place them in the public/** directory. Vercel serves them through its CDN using default headers, which you can override in vercel.json. Express's own express.static() helper is ignored on Vercel, so rely on the public directory instead.

Copy link to headingRun scheduled tasks with cron jobs

Vercel Cron Jobs trigger a route on a schedule by sending an HTTP GET request to it. Define a route in your Express app for the task, then register the schedule in vercel.json.

Define the route:

src/index.ts
import express from 'express';
const app = express();
app.get('/api/cron/cleanup', (req, res) => {
if (req.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) {
return res.status(401).send('Unauthorized');
}
// Run your scheduled work here
res.json({ ok: true });
});
export default app;

Register the schedule:

vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"crons": [{ "path": "/api/cron/cleanup", "schedule": "0 0 * * *" }]
}

Vercel runs cron jobs only on production deployments. To stop anyone else from calling the route, set a CRON_SECRET environment variable in your project settings. Vercel sends it as a Bearer token in the Authorization header on every cron invocation, and your handler compares it before running the task.

Copy link to headingUse the Bun runtime

Express runs your function on Node.js by default. To run it on Bun instead, set bunVersion in vercel.json:

vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"bunVersion": "1.x"
}

Vercel detects the setting, runs your app on Bun in both vercel dev and production, and keeps it on Fluid compute. The Bun runtime is in public beta and supports most Node.js APIs. Set the major version only, and Vercel manages the minor and patch versions. For a ready-made starting point, deploy the Express on Bun template.

Copy link to headingMonitor performance with Observability

Vercel Observability tracks your deployed function automatically, with no setup. Open the Observability page in your project to see invocation counts, error rates, and duration for your Express app, along with the requests your function makes to external APIs. On Observability Plus, you also get longer retention and a latency breakdown by path.

Copy link to headingBest practices

Copy link to headingExport your app from a recognized entry point

Vercel finds your Express app by looking for a file at a fixed set of locations: app, index, or server (with a .js, .ts, or related extension) at your project root or under src/. The file must export your app as a default export or start it with a port listener. Put your app at one of these paths so Vercel detects and deploys it correctly:

src/index.ts
import express from 'express';
const app = express();
// Add your routes here
export default app;

Copy link to headingHandle errors explicitly

Add an error-handling middleware so a thrown error doesn't leave your function in an undefined state. Express catches errors and renders its own 500 response, which can stop Vercel from recycling the function and resetting it for the next request. An error handler at the end of your middleware chain keeps this predictable:

src/index.ts
import express from 'express';
const app = express();
// Routes here
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
});
export default app;

Copy link to headingDevelop with the Vercel CLI

Run vercel dev for local development instead of a standalone server. It serves your app the way production does, so the behavior you test locally matches what you deploy. This also lets you exercise features such as cron routes and the Bun runtime before shipping.

Copy link to headingResources and next steps

Related documentation

More Fluid compute guides