---
title: Full-stack previews on Vercel
description: Learn how to use full-stack previews for your Vercel projects. Deploy Next.js, FastAPI, and a containerized Go service together in one project, then review every cross-service change through one preview URL.
url: /kb/guide/full-stack-preview-deployments-on-vercel
canonical_url: "https://vercel.com/kb/guide/full-stack-preview-deployments-on-vercel"
published: 2026-08-14
last_updated: 2026-08-14
authors: Anshuman Bhardwaj
related:
  - /kb/guide/vercel-services
  - /docs/deployments/environments
  - /docs/environment-variables/manage-across-environments
  - /docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation
  - /docs/services/routing
  - /kb/guide/docker
  - /docs/project-configuration/vercel-json
  - /docs/functions/container-images
  - /docs/frameworks
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

A frontend in one Vercel project, a Python API in another, and a containerized Go service are each reasonable choices on their own. The friction appears when a change crosses their boundaries. Someone has to coordinate three deployments, update a service URL, align CORS rules, copy secrets into several places, and decide what “roll back” means when only two of the three releases are healthy.

[Vercel Services](https://vercel.com/kb/guide/vercel-services) puts it all into a single Vercel project. Each service is built independently with its own framework and dependencies, and a single project-level routing table decides what the outside world can reach. So, Vercel deploys, previews, and rolls back the entire system as a single unit.

This guide follows the [full-stack preview template](https://github.com/vercel-labs/full-stack-service-previews) from local development through preview review to production. The template's [architecture reference](https://github.com/vercel-labs/full-stack-service-previews/blob/main/ARCHITECTURE.md) documents its routing, deployment identity, isolation boundary, and platform limits.

## Map the service boundary

The example contains a small checkout flow with the following parts:

- **Next.js storefront:** The public interface collects a product, quantity, customer tier, and optional coupon. It displays the branch, commit, and runtime identities included in a successful quote, so a reviewer can see that both backends belong to the same preview.
  
- **FastAPI checkout service:** Python owns the public checkout contract and pricing policy. It validates the request, applies customer-tier and coupon discounts, asks the private reservation engine for a final stock decision, and returns one stable response to the frontend.
  
- **Go reservations service:** Go owns the concurrency-sensitive inventory boundary. It performs the final stock check, atomically creates a 15-minute mock hold, and returns only reservation facts from a `Dockerfile.vercel` container.
  

These choices are examples. Apply the same boundary by deciding which services must be public and which should only be reachable by another service.

| Service role    | Example            | How it is reached                 |
| --------------- | ------------------ | --------------------------------- |
| Public frontend | Next.js storefront | Catch-all public rewrite          |
| Public API      | FastAPI checkout   | `/api/v1/checkout/*` rewrite      |
| Private backend | Go reservations    | Service binding from FastAPI only |

The browser does not need direct access to reservations, so the reservations service receives no public route. FastAPI is the policy boundary and calls Go over a private service binding.

The template keeps its catalog and reservations in memory. A mutex makes the final check and decrement atomic inside one Go process, which is enough to demonstrate the contract and test concurrent requests locally.

## Link the services

The project declares three independently built services in `vercel.json`. Top-level rewrites decide what is public:

```json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "services": {
    "web": {
      "root": "apps/web/",
      "framework": "nextjs"
    },
    "checkout": {
      "root": "services/checkout/",
      "framework": "fastapi",
      "entrypoint": "main:app",
      "bindings": [
        {
          "type": "service",
          "service": "reservations",
          "format": "url",
          "env": "RESERVATIONS_SERVICE_URL"
        }
      ]
    },
    "reservations": {
      "root": "services/reservations/",
      "entrypoint": "Dockerfile.vercel"
    }
  },
  "rewrites": [
    {
      "source": "/api/v1/checkout/(.*)",
      "destination": {
        "service": "checkout"
      }
    },
    {
      "source": "/(.*)",
      "destination": {
        "service": "web"
      }
    }
  ]
}
```

The checkout service declares `bindings` to reach reservations. The rewrite exposes FastAPI through the deployment’s public URL. The binding grants the calling service access to a target that remains private. Vercel injects `RESERVATIONS_SERVICE_URL` as a deployment-aware absolute URL at runtime. In a preview, checkout therefore calls reservations from that same preview, without a fixed hostname, a preview URL copied into an environment variable, or a public route for the Go service.

The Go image in `services/reservations/Dockerfile.vercel` compiles a static binary in one stage and copies it into a small runtime image in the next. The server in `main.go` reads `PORT` and falls back to `8081`, which is the runtime contract for an HTTP container on Vercel: containers are expected to open an HTTP server, and the default port is 80 unless `PORT` is set in the environment variables.

## Run the services locally

Before getting started, ensure you have:

- Node.js 24.x and npm
  
- Python 3.12 or newer
  
- Vercel CLI 59.0.0 or newer
  
- Docker Desktop
  

From the repository root, install the dependencies and start the complete system:

```bash
npm install
vercel dev -L
```

On its first run, the command creates the Python environment for the checkout service, installs the dependencies in `services/checkout/requirements.txt`, and builds the Go container image. It then starts Next.js, FastAPI, and the Go container together. The `-L` (`--local`) option keeps the run fully local and does not require Vercel Cloud authentication.

Vercel also injects the service binding variable. Application code only reads the URL and makes an ordinary HTTP request:

```python
reservations_url = os.environ["RESERVATIONS_SERVICE_URL"]
response = await client.post(
    f"{reservations_url}/internal/reserve", json=payload
)
```

Open `http://localhost:3000` and reserve an item. One click travels through every piece of your configuration:

1. The browser posts to `/api/v1/checkout/quote`
   
2. The top-level rewrite sends that path to FastAPI
   
3. FastAPI calls `/internal/reserve` through the binding
   
4. Go performs the final stock check and returns a reservation ID
   

FastAPI then applies the customer-tier and coupon rules to produce a total, and Next.js renders it beside a countdown on the hold. That click exercises the project routing table, not three processes that happen to be running. A malformed rewrite or a missing binding fails in the same path that developers use every day.

Keep the development server running and verify those guarantees from a second terminal:

```bash
npm run smoke:services
```

The smoke command checks that the public page loads, a reservation passes from FastAPI to Go with the expected price, the hold lasts 900 seconds, both backend identities appear in the response, and `/internal/health` is not publicly reachable.

Test `/api/v1/checkout/quote` through `vercel dev` rather than calling the FastAPI port directly. A direct-port test can pass while the project-level rewrite or service binding is broken.

Local development gives a representative loop, but it is not a production simulator. Test all three services together in a Preview Deployment before shipping to production.

## Configure deployment environments

Vercel generates `RESERVATIONS_SERVICE_URL` from the binding, so it points to local reservations during development, preview reservations for a pull request, and production reservations after release. Do not create this variable yourself. A user-defined value with the same name takes precedence over the generated binding and can reconnect checkout to the wrong target.

What remains in Project Settings is real application configuration: database connections, feature flags, and third-party credentials. A practical release flow has three tiers:

1. **Branch previews** for isolated pull-request testing. By default, a connected Git repository creates Preview Deployments for pull requests and pushes to non-production branches.
   
2. **Staging** as a [Custom Environment](https://vercel.com/docs/deployments/environments#custom-environments) with branch tracking, for integration or release-candidate testing against stable staging resources.
   
3. **Production** for live data and credentials.
   

Custom Environments can track matching branches and carry their own variables and domains. Preview-scoped values are shared by default; use branch-specific values and separate backing resources when a preview needs isolated data.

For a production version of this template, the environment matrix might look like this:

| Variable                 | Preview                 | Staging          | Production          | Why                                                   |
| ------------------------ | ----------------------- | ---------------- | ------------------- | ----------------------------------------------------- |
| `INVENTORY_DATABASE_URL` | Seeded preview database | Staging database | Production database | Keeps test inventory away from live stock.            |
| `PAYMENT_API_KEY`        | Test key                | Test key         | Live key            | Prevents preview checkout from creating real charges. |
| `PAYMENT_WEBHOOK_SECRET` | Preview/test secret     | Staging secret   | Production secret   | Verifies callbacks within the correct environment.    |

Your equivalent is any value that changes business behavior or grants access, not a URL whose only job is to find another service in the same deployment.

Vercel also supports [branch-specific preview variables](https://vercel.com/docs/environment-variables/manage-across-environments). A branch named `inventory-reconciliation`, for example, can point `INVENTORY_DATABASE_URL` at a specially seeded dataset, while other previews continue using the default preview database.

All three services are part of one Vercel project, so this configuration is managed at the project environment level. There is no separate checkout secret store and reservations secret store to synchronize. The services receive the values appropriate to the deployment environment in which they were built.

## Review a change as one system

Vercel Services makes a change across three runtimes reviewable as a single unit. Consider this scenario: a PR to add a 15-minute stock reservation across the stack. The change crosses every service:

- The Go service must make the final availability decision, atomically reserve the quantity, and return an ID and expiry.
  
- FastAPI must translate insufficient stock into its public API, apply pricing policy, and carry the reservation metadata through the checkout response.
  
- Next.js must show that the stock is held and run the countdown.
  

Unit tests in each directory can pass while the deployed system is still wrong. Python may expect `reserved_until`, Go may emit a differently named field, or the UI may parse the expiry in the wrong timezone. Concurrent requests can also reveal a stock race that a single happy-path request misses.

One pull request containing all three changes. Vercel builds each service independently and creates one preview deployment for the commit. The Git integration reports one deployment URL. Behind it are the matching Next.js, FastAPI, and Go builds from that commit.

The Services graph in the Deployments panel shows the `web`, `checkout`, and `reservations` services for that deployment, and the Logs UI can filter events by service. Git deployments automatically receive branch and commit metadata. Direct CLI deployments display `CLI deployment` when Git metadata is unavailable.

A reviewer opens the preview, selects an item and quantity, and clicks **Reserve for 15 minutes**. The browser remains on one origin. Its `/api/v1/checkout/quote` request is rewritten to the preview’s FastAPI service, and FastAPI’s binding resolves to the preview’s Go container. There is no temporary CORS allowlist and no branch-specific backend URL to paste into the frontend.

With three Vercel projects, the same review needs three deployments and a temporary way to connect them. The frontend preview must learn the FastAPI preview URL; FastAPI must learn the Go preview URL; public cross-origin calls may require CORS changes; and each project makes its own rollback decision. Vercel Services makes the commit the preview boundary, rather than a collection of URLs you have to manually maintain.

### Debug a Vercel Services project

Say the checkout returns a 502 even though both backend test suites pass. Start with the request in the Vercel dashboard and narrow the logs to the Go reservations service and `/internal/reserve`. Go completed the reservation with HTTP 201. Following the same request into checkout reveals a response-contract validation error: Go emitted `expires_at`, while FastAPI's typed boundary requires `reserved_until`. The Go test was updated with the renamed field, while FastAPI's mocked successful response still contained the old one, so both isolated suites passed. The preview exercised the deployed contract between them. Logs, traces, and metrics for the container live beside those for the other functions; the binding call does not disappear into a separately operated platform.

Aligning the field name and pushing another commit produces a new commit deployment. The reviewer reloads the same branch URL and repeats the flow. The second result proves that Next.js, FastAPI, and Go now agree on the reservation contract.

Bindings also preserve the intended public boundary. The reservations service remains unreachable from the public internet during review. The reviewer exercises it via the same checkout boundary used in production, so the preview does not require a public route for the Go service.

An equivalent change might add a product across the storefront selector, FastAPI catalog and pricing policy, and Go inventory map. The preview keeps the three implementations together while reviewers test the new contract.

## Test the deployed preview

Once the pull-request deployment is ready, end-to-end and contract tests need only one base URL. Tests can load the Next.js page and call the FastAPI path through the same public routing table. FastAPI-to-Go behavior is exercised against the real deployed services, so the suite catches serialization, routing, and runtime differences that mocks cannot.

Run the repository smoke command manually against a preview with `BASE_URL=https://your-preview.vercel.app npm run smoke:services`. Protected previews need [Protection Bypass for Automation](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation); provide its value as `VERCEL_AUTOMATION_BYPASS_SECRET`, which the smoke script sends in the `x-vercel-protection-bypass` header.

## Promote or roll back safely

After the preview passes, merge through the normal production branch flow or promote an existing deployment with:

```bash
vercel promote <deployment-id-or-url>
```

Promoting a preview deployment to production triggers a complete rebuild with production environment variables. If production has a problem, `vercel rollback` can reassign the project’s domains to a previous production deployment. Because the three services belong to that deployment, promotion and rollback move the frontend, Python API, Go container, routing table, and bindings as a single unit.

## Next steps

- Learn how [Vercel Services routes traffic and connects services](https://vercel.com/docs/services/routing).
  
- Learn how to [run Docker on Vercel](https://vercel.com/kb/guide/docker).
  
- Review the [`vercel.json`](https://vercel.com/docs/project-configuration/vercel-json) [configuration reference](https://vercel.com/docs/project-configuration/vercel-json).
  
- Read the [container images documentation](https://vercel.com/docs/functions/container-images).
  
- Explore the [frameworks supported on Vercel](https://vercel.com/docs/frameworks).