Skip to content
Docs

The Complete Guide to Vercel Services

Vercel Services let you deploy multiple frontends and backends in one project on a shared domain. Learn how to define services in vercel.json, route requests by path prefix, and connect services with generated environment variables.

9 min read
Last updated June 30, 2026

Vercel Services let you run multiple frontends and backends in a single Vercel project, built separately but served from one domain through a single public routing table. You can pair a Next.js frontend with a Python or Go backend in one repository, route public traffic to each with rewrite rules, and let them call each other privately, instead of splitting your monorepo into separate Vercel projects.

In this guide, you'll learn how Vercel builds and routes requests across the services in one project, how to define services in your vercel.json and expose them with top-level rewrites, and how services call each other with bindings. You'll also see how Services are billed, run your services locally, and troubleshoot the most common routing and configuration problems.

Vercel Services run several independently built applications inside one project. At build time, Vercel builds each service separately, using that service's own framework, dependencies, and build step. At request time, Vercel routes each incoming request to a service based on the rewrite rules in your vercel.json.

A single project can combine different frameworks and runtimes. For example, one project might contain:

  • A Next.js frontend at /
  • A Python FastAPI backend, for example at /api
  • A Go server, for example at /svc/go

Each service is internal by default and receives public traffic only when a top-level rewrite routes requests to it. All services share the same deployment, so they sit behind one public surface where Firewall, Deployment Protection, and redirects are configured once and apply across every service.

Use Vercel Services when you want to deploy more than one application from a single repository to a single domain. They fit two main cases:

  • A polyglot monorepo: A JavaScript frontend and a Python backend in the same repository that you want to deploy as one project.
  • Multiple backends: Several API services, each with its own requirements.

You don't need services if your project uses a single framework, such as a Next.js app with API routes. In that case, deploy the project as usual. If your monorepo contains separate applications that you'd rather mount under separate domains, deploy them as separate projects in a monorepo instead.

Vercel Services are available in Beta on all plans.

Before you set up services, make sure you have:

  • A Vercel project connected to a repository that contains more than one application, such as a frontend and a backend.
  • The ability to set the project's framework to Services in your project settings.
  • The Vercel CLI installed (npm i -g vercel).

Define your services in vercel.json, then expose them with top-level rewrites. Services are internal by default, so a service receives public traffic only when a rewrite routes requests to it.

  1. Set the project framework to Services. In your project's Build and Deployment settings, set the framework to Services. A project builds as services only when this setting is selected and a services key is present in vercel.json.
  2. Add a services block to vercel.json. Define each service with a root directory, and an entrypoint for backends that need one. Add top-level rewrites to route public traffic to each service.

    vercel.json
    {
    "services": {
    "my_frontend": {
    "root": "frontend/"
    },
    "my_backend": {
    "root": "backend/",
    "entrypoint": "main:app"
    }
    },
    "rewrites": [
    { "source": "/api/(.*)", "destination": { "service": "my_backend" } },
    { "source": "/(.*)", "destination": { "service": "my_frontend" } }
    ]
    }

    With these rewrites in place, any request under /api/ routes to my_backend, and every other path routes to my_frontend. Without the rewrites, neither service would receive public traffic.

  3. Deploy your project as you normally would. Vercel builds each service separately and serves them all from one deployment URL.

For the smallest possible setup, define a single service with a catch-all rewrite:

vercel.json
{
"services": { "api": { "root": "api/" } },
"rewrites": [{ "source": "/(.*)", "destination": { "service": "api" } }]
}

Each entry under services accepts the following fields:

FieldTypeDescription
rootstringPath to the service's directory in your repository.
entrypointstringEntry module for the service, for example main:app for a Python backend.
frameworkstringOptional. Framework slug, for example "nextjs", "fastapi", or "express". If you don't set it, Vercel detects the framework on every build.
runtimestringOptional. Set to "container" to build the service as a Docker image instead of using the detected runtime.
bindingsarrayOptional. Internal connections to other services. See How services call each other with bindings.

When vercel.json includes a services key, configuration splits into two groups. Public routing and URL behavior, such as rewrites, redirects, and headers, stay at the top level and control public traffic for the whole deployment. Build and runtime fields, such as functions, buildCommand, installCommand, devCommand, ignoreCommand, outputDirectory, and framework, aren't valid at the top level and must move into the relevant service, because each service owns its own build.

Each service follows the same limits as Vercel Functions, including memory and maximum duration. For the full set of options, see the service configuration reference.

Your vercel.json controls all public routing for the deployment. Vercel evaluates your top-level rewrites in order and routes each request to the first matching service, so the most specific rewrites should come before a catch-all rule. A service receives public traffic only when a top-level rewrite targets it, with the destination set to an object such as { "service": "my_backend" }.

With /api/(.*) routing to my_backend and everything else routing to my_frontend, requests route like this:

RequestHandled byService receives
GET /dashboardmy_frontend/dashboard
GET /api/usersmy_backend/api/users
POST /api/ordersmy_backend/api/orders

The service receives the original request path. GET /api/users reaches my_backend as /api/users, not /users, so mount your service routes under the same path your rewrite matches.

Routing into a service is final. Once a request is routed to a service, that service handles the rest of routing, and if nothing inside it matches, Vercel returns the service's own 404 or 405 response rather than falling back to your other top-level rewrites.

The destination object accepts two fields:

FieldRequiredDescription
serviceYesName of a service in the same deployment, from your services configuration.
pathNoSelects which route runs inside the service. It changes the route lookup, not the path your code sees.

A service can also define its own headers, redirects, rewrites, and routes, which run only after a top-level rewrite routes a request into it. To change the path your service code observes, add a request.path transform to the service's own routes.

Services call each other privately with bindings, not over the public internet. A binding lets server-side code in one service reach another service directly, without exposing the target to public traffic.

You declare a binding on the calling service. The caller names the target service and the environment variable that should receive the generated URL, and Vercel injects that URL at runtime:

vercel.json
{
"services": {
"orders": {
"root": "services/orders/",
"framework": "express",
"bindings": [
{
"type": "service",
"service": "inventory",
"format": "url",
"env": "INVENTORY_URL"
}
]
},
"inventory": {
"root": "services/inventory/",
"framework": "fastapi",
"entrypoint": "main:app"
}
},
"rewrites": [{ "source": "/api/(.*)", "destination": { "service": "orders" } }]
}

Server-side code in orders reads the injected variable to build request URLs:

await fetch(new URL('items/123', process.env.INVENTORY_URL));

A binding is the reachability grant. Because orders declares a binding to inventory, it can call inventory even though inventory has no public rewrite and stays unreachable from the internet. The binding fields are:

FieldRequiredDescription
typeYesMust be "service" for a service-to-service binding.
serviceYesTarget service name from services.
formatYesMust be "url". The generated value is an absolute URL base for the target service.
envYesEnvironment variable that receives the generated URL. Vercel injects this value, so you don't set it yourself.

The injected URL is deployment-aware, so a preview deployment's orders service reaches that same preview deployment's inventory service without referencing a fixed hostname. Internal calls also skip the public request pipeline, so Firewall, Deployment Protection, top-level middleware, and CDN request accounting don't apply. A binding grants internal access only and doesn't authenticate the call, so handle any application-level authorization in your service code. Bindings resolve at runtime only, not during builds or in middleware.

Vercel bills Services based on the compute resources each service runs on, the requests services make to each other, and the data services return.

  • Compute: Each service is billed the same as Vercel Functions, based on Active CPU, provisioned memory, and invocations. Fluid compute applies, so concurrent requests can share an instance.
  • Service requests: When one service calls another over a binding, each call counts as one service request. Requests that arrive from the public internet aren't service requests. Service requests are regionally priced, billed separately from CDN requests, and don't incur a separate Edge Request or Fast Data Transfer charge.
  • Data transfer: The bytes a service returns are billed as Fast Origin Transfer, the same as other origin traffic on Vercel.

Each service is also subject to the same function limits, such as memory and maximum duration. See Services pricing and limits for full details.

Run all of your services together on your machine with vercel dev:

Terminal
vercel dev

To run everything locally without authenticating with the Vercel Cloud, add the -L flag (short for --local):

Terminal
vercel dev -L

In both cases, Vercel automatically injects the generated binding environment variables, so calls between services work the same locally as in production.

A project builds as services only when two conditions are both true: the project's framework is set to Services, and vercel.json contains a services key. If either is missing, Vercel falls back to its default framework detection and ignores your services configuration. Confirm both, then redeploy.

Services are internal by default and receive public traffic only through a top-level rewrite. If a service never receives requests, add a rewrite whose destination targets it, for example { "source": "/api/(.*)", "destination": { "service": "my_backend" } }.

Vercel evaluates top-level rewrites in order and routes each request to the first match. If requests land on the wrong service, put your most specific rewrites before broader ones, and keep the catch-all /(.*) rule last.

A service receives the original request path, and Vercel doesn't strip the matched portion before forwarding it. GET /api/users reaches the service as /api/users, not /users, so mount your routes accordingly. To change which route runs or the path your code sees, use the path field on the rewrite destination or a request.path transform in the service's own routes.

A binding must be declared on the calling service, and its URL resolves at runtime only. If a call fails, confirm the caller declares a binding to the target, that you're reading the injected env variable, and that the call runs in a function rather than during a build or in middleware.

When services is present, top-level build and runtime keys such as functions, buildCommand, installCommand, devCommand, ignoreCommand, outputDirectory, and framework aren't valid. Move them into the relevant service so the configuration has a single owner.

Learn how to build on Services with these two step-by-step guides. Both create a real-time, full-stack app by pairing a frontend with a WebSocket backend within a single Vercel project that deploys to one domain.

Vercel Services let you deploy multiple frontends and backends within a single Vercel project. Each service is an independently built unit that shares one deployment and one public surface. You expose a service to public traffic with a top-level rewrite, and services call each other privately with bindings.

Vercel Services are available in Beta on all plans. Each service is billed like a Vercel Function, based on Active CPU, provisioned memory, and invocations, with Fluid compute applied. Calls between services over a binding are billed as service requests, and the bytes a service returns are billed as Fast Origin Transfer.

A single project can combine services across different frameworks and runtimes, including JavaScript frontends and Python or Go backends. You can pin a service's framework with the framework field using a slug such as "nextjs", "fastapi", or "express", or leave it unset and let Vercel detect the framework on each build. To build a service as a Docker image instead, set its runtime to "container".

Use services when you want to deploy multiple applications from a single repository to a single domain, such as a polyglot monorepo or several backends. If you'd rather mount separate applications under separate domains, deploy them as separate projects in a monorepo instead.

Public requests reach a service through top-level rewrites in your vercel.json. For private calls between services, declare a binding on the calling service. Vercel injects the target service's URL into an environment variable that your server-side code reads, and the call stays on the internal network without a public route.

Run vercel dev to start all of your services together on your machine, or vercel dev -L (the -L flag is short for --local) to run them without authenticating with the Vercel Cloud. Binding environment variables are injected automatically, so cross-service calls work the same locally as in production.

Was this helpful?

supported.