Vercel Observability tutorial: debug a slow or failing Function
By Flavio Copes
Debug a slow or failing Vercel Function with structured runtime logs, route metrics, CLI filters, request traces, and a verified fix.
Vercel 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:
npx create-next-app@latest observability-demo
cd observability-demo
Create app/api/report/route.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:
npm run dev
Then call each mode:
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:
npx vercel
The command prints a Preview URL. Save it in a shell variable:
PREVIEW_URL="https://your-preview-url.vercel.app"
Trigger the three requests against the deployment:
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
errorIdis 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. 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:
npx vercel logs \
--environment preview \
--status-code 500 \
--since 30m \
--expand
Search the log messages for our event:
npx vercel logs \
--environment preview \
--query "report.failed" \
--since 30m \
--expand
Once you have the platform Request ID, isolate that request:
npx vercel logs --request-id req_xxxxx --expand
For scripts, ask for JSON instead:
npx vercel logs \
--environment preview \
--status-code 500 \
--since 30m \
--json
You can also stream new logs while reproducing a problem:
npx vercel logs --follow
The current options and examples are listed in the vercel logs reference.
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.
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:
- Open Tracing.
- Select Start Tracing Session.
- Visit
/api/report?mode=slow. - 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 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:
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:
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:
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:
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
500response appears for/api/report. report.rejectedexplains the expected validation failure.- The Function duration for the
slowmode 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:
- Reproduce the problem on the correct deployment.
- Filter Runtime Logs by environment, route, status, and time.
- Open one request and record its Request ID.
- Read structured application logs and inspect outgoing requests.
- Use route metrics to see whether the request is isolated or part of a larger pattern.
- Trace one representative request when timing is unclear.
- Deploy a focused fix.
- 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. Observability becomes much more useful once you understand the difference between projects, deployments, Preview, and Production.
Related posts about services: