# Vercel Observability tutorial: debug a slow or failing Function

> Debug a slow or failing Vercel Function with structured runtime logs, route metrics, CLI filters, request traces, and a verified fix.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-08-07 | Topics: [Services](https://flaviocopes.com/tags/services/) | Canonical: https://flaviocopes.com/vercel-observability/

[Vercel Observability](https://vercel.com/docs/observability) helps you move from "the app is slow" or "the API returned 500" to the request, route, Function, and deployment responsible for it.

In this tutorial we'll deploy a Next.js Route Handler with two deliberate problems. One request will fail. Another will be slow.

We'll find both problems using Runtime Logs, the Vercel CLI, route metrics, and Session Tracing. Then we'll deploy a fix and verify that it worked.

## Create a route with two problems

Create a Next.js app:

```bash
npx create-next-app@latest observability-demo
cd observability-demo
```

Create `app/api/report/route.ts`:

```ts
function wait(milliseconds: number) {
  return new Promise((resolve) => {
    setTimeout(resolve, milliseconds)
  })
}

export async function GET(request: Request) {
  const startedAt = Date.now()
  const errorId = crypto.randomUUID()
  const mode = new URL(request.url).searchParams.get('mode')

  console.log(JSON.stringify({
    event: 'report.started',
    errorId,
    mode,
  }))

  try {
    if (mode === 'fail') {
      throw new Error('The billing profile is missing')
    }

    if (mode === 'slow') {
      await wait(1500)
    }

    console.log(JSON.stringify({
      event: 'report.completed',
      errorId,
      mode,
      durationMs: Date.now() - startedAt,
    }))

    return Response.json({
      status: 'ready',
      errorId,
    })
  } catch (error) {
    console.error(JSON.stringify({
      event: 'report.failed',
      errorId,
      mode,
      message: (error as Error).message,
      durationMs: Date.now() - startedAt,
    }))

    return Response.json(
      {
        error: 'Could not create the report',
        errorId,
      },
      { status: 500 },
    )
  }
}
```

The `fail` mode throws an error. The `slow` mode waits 1.5 seconds.

The route also writes structured JSON logs. Each log has an event name and an `errorId` that connects the server log to the response received by a user.

In a real app, do not log passwords, access tokens, full request bodies, or personal data. Log the small amount of context needed to reproduce and diagnose the problem.

Test the route locally:

```bash
npm run dev
```

Then call each mode:

```bash
curl "http://localhost:3000/api/report"
curl "http://localhost:3000/api/report?mode=slow"
curl "http://localhost:3000/api/report?mode=fail"
```

The last request returns status `500`. The slow request succeeds after about 1.5 seconds.

## Deploy a Preview

Deploy the app:

```bash
npx vercel
```

The command prints a Preview URL. Save it in a shell variable:

```bash
PREVIEW_URL="https://your-preview-url.vercel.app"
```

Trigger the three requests against the deployment:

```bash
curl "$PREVIEW_URL/api/report"
curl "$PREVIEW_URL/api/report?mode=slow"
curl "$PREVIEW_URL/api/report?mode=fail"
```

Debugging a Preview first keeps this exercise away from production traffic. The same tools work with production deployments.

## Find the 500 in Runtime Logs

Open the project in the Vercel dashboard and choose **Logs**.

Set these filters:

- Environment: **Preview**
- Status Code: **500**
- Request Path: `/api/report`

Open the matching request.

The details panel shows the request method, path, status, region, deployment, Function duration, memory usage, outgoing requests, and log messages. It also gives the request a Vercel Request ID.

Under log messages, find `report.failed`. Its `errorId` should match the value returned by the API.

This distinction is useful:

- The Vercel Request ID identifies one platform request.
- Our `errorId` is an application-level reference we can safely show to the person who experienced the error.

Build logs and Runtime Logs are different. Build logs explain why a deployment could not be built. Runtime Logs explain what happened when someone called a deployed Function.

[Runtime Logs are available on every Vercel plan](https://vercel.com/docs/logs/runtime). Vercel currently keeps them for one hour on Hobby, one day on Pro, and longer on plans or add-ons with extended retention.

## Search the same logs from the CLI

The dashboard is useful when exploring. The CLI is faster when you already know what you want to filter.

Show recent Preview requests that returned 500:

```bash
npx vercel logs \
  --environment preview \
  --status-code 500 \
  --since 30m \
  --expand
```

Search the log messages for our event:

```bash
npx vercel logs \
  --environment preview \
  --query "report.failed" \
  --since 30m \
  --expand
```

Once you have the platform Request ID, isolate that request:

```bash
npx vercel logs --request-id req_xxxxx --expand
```

For scripts, ask for JSON instead:

```bash
npx vercel logs \
  --environment preview \
  --status-code 500 \
  --since 30m \
  --json
```

You can also stream new logs while reproducing a problem:

```bash
npx vercel logs --follow
```

The current options and examples are listed in the [`vercel logs` reference](https://vercel.com/docs/cli/logs).

## Find the slow route in Observability

Open **Observability** in the project dashboard.

Choose a time range that includes the requests you just sent, then open the **Vercel Functions** view.

Sort the routes by duration. Open `/api/report`.

The route view connects several signals:

- Invocation count tells you how often the Function ran.
- Error rate shows whether the route is failing.
- Duration helps you find slow Functions.
- Runtime Logs take you from the route-level pattern to individual requests.
- External API data can reveal a slow service called by the Function.

Our example is intentionally small, so the route may only have a few data points. In a real incident, start with a time window around the report, zoom into the spike, and compare the failing route with its usual behavior.

The main Observability dashboard is available on all plans. Some detailed latency breakdowns, longer retention, and advanced metrics require [Observability Plus](https://vercel.com/docs/observability).

## Use Query when you need to group traffic

The Observability dashboard gives you prepared views. Query lets you ask a narrower question across the telemetry.

For example, create a query for:

- Vercel Function invocations
- Status code equal to `500`
- Grouped by route
- Limited to the affected project and time range

This answers: "Which Function produced the most 500 responses during this incident?"

Change the filter to the `/api/report` route and group by deployment. You can now see whether the failures began with one release.

Query is available on Pro and Enterprise plans. It is not required for the basic workflow in this article: Hobby users can still use the Logs and Observability views to find the same request.

## Trace one slow request

A metric tells you that a route is slow. A trace shows how one request spent its time.

Session Tracing works on deployed Preview and Production environments. It is available on all plans, but requires the Vercel Toolbar.

The toolbar is enabled by default on Preview deployments. If it is missing, open the project settings, choose **General**, find **Vercel Toolbar**, and enable it for Preview.

Open the Preview deployment in your browser. In the Vercel Toolbar:

1. Open **Tracing**.
2. Select **Start Tracing Session**.
3. Visit `/api/report?mode=slow`.
4. Open **Tracing** again and choose **View Session Traces**.

The dashboard opens Logs filtered to that session. Select the slow request to inspect its spans.

Vercel automatically shows the request moving through its infrastructure and into the Function. Framework, middleware, cache, and Function spans make it easier to see where the time went. Instrumented application code can add more detailed spans when you need them.

For one page load, choose **Run Page Trace** instead of tracing a complete session.

[Session Tracing](https://vercel.com/docs/tracing/session-tracing) is limited to deployed environments. It does not run against `localhost`.

## Fix the Function

Our example has two separate defects.

First, an expected missing billing profile should not crash the Function. Return a useful client error:

```ts
if (mode === 'fail') {
  console.warn(JSON.stringify({
    event: 'report.rejected',
    errorId,
    reason: 'missing_billing_profile',
  }))

  return Response.json(
    {
      error: 'Add a billing profile before creating a report',
      errorId,
    },
    { status: 422 },
  )
}
```

Second, remove the artificial delay:

```ts
if (mode === 'slow') {
  // Run the real report work here without the artificial wait.
}
```

When a real Function is slow, do not immediately increase its timeout. Open the request details first.

Look for:

- Slow outgoing database or API requests
- Several independent requests running one after another
- Repeated work that could be cached
- Large responses
- CPU-heavy work
- A cold start that dominates a short request

Independent operations can often run concurrently:

```ts
const [customer, invoices] = await Promise.all([
  getCustomer(),
  getInvoices(),
])
```

Measure before and after the change. Parallel work is only safe when the operations do not depend on each other.

## Deploy and verify the fix

Create another Preview deployment:

```bash
npx vercel
```

Call the two previously problematic URLs on the new deployment.

The former failure should return `422`, not `500`. The former slow request should complete without the 1.5-second wait.

In Logs, filter by the new deployment and confirm:

- No new `500` response appears for `/api/report`.
- `report.rejected` explains the expected validation failure.
- The Function duration for the `slow` mode has dropped.

Then return to Observability and compare the two deployments over the same type of traffic.

This last verification matters. A code change that looks correct is not enough. The deployed telemetry should show that the error disappeared and the duration improved.

## A practical incident workflow

When a Vercel Function fails or becomes slow, use this order:

1. Reproduce the problem on the correct deployment.
2. Filter Runtime Logs by environment, route, status, and time.
3. Open one request and record its Request ID.
4. Read structured application logs and inspect outgoing requests.
5. Use route metrics to see whether the request is isolated or part of a larger pattern.
6. Trace one representative request when timing is unclear.
7. Deploy a focused fix.
8. Repeat the same requests and compare the new telemetry.

This moves debugging from guesses to a specific request and a measurable result.

If you are new to the platform, start with [how to deploy a site to Vercel](https://flaviocopes.com/vercel/). Observability becomes much more useful once you understand the difference between projects, deployments, Preview, and Production.
