Using distributed tracing to debug timeout errors in Next.js

Next.js lets you write the UI, the API, and everything in between, so when a request stalls it can feel like you’re chasing a bug through a hall of mirrors. With distributed tracing in your workflow, those mirrors flatten into a single timeline that lists every route hop, database call, and third-party handshake in order. Now you can follow the delay from click to response without digging through log files or swapping tabs.
Tracing captures the sequence of requests and operations in your app, while distributed tracing extends this capability across services or APIs, even if they use different languages and platforms. This end-to-end visibility helps you find performance issues and the root cause of errors by following the flow of requests and operations from the frontend to the backend.
In this post we’ll break down a stubborn timeout in a Vercel-deployed Next.js notes app that leans on Server Actions for moving data. With Sentry’s Trace View, each step shows up like breadcrumb arrows you can actually follow. This allows you to quickly and effectively debug the issue, getting you right back into building instead.
Debugging a Statement Timeout Error in a Next.js App With Server Actions
We created an example Next.js note-taking app using Vercel’s Next.js Supabase starter. The app has cookie-based authentication and uses Server Actions (async functions executed on the server) to read, edit, and create notes. Server Actions can be called in server or client components to handle form submissions and data mutations without needing to create an API.
The Next.js example note-taking app includes a statement timeout error that we’ll debug using Sentry’s distributed tracing.
Clone the Next.js Note-Taking App
Start by cloning the GitHub repository for the starter Next.js app, which uses Supabase to store user data. Follow the steps in the README to set up the app, add example notes to Supabase, and deploy it to Vercel.
Set Up and Configure Sentry in a Next.js App
If you already have a Sentry account, then you're good to go. If not, head over to the signup page and create an account to get started with the next steps.
Once you’ve signed up, follow the onboarding steps to create a new project for a Next.js app. Alternatively if you’re already a bit familiar with Sentry, create a new project and select Next.js as the platform.
Add the Sentry Next.js SDK to the note-taking app by running the Sentry wizard in the root of the project (remember to replace the --org
and --project
options with your own variables):
npx @sentry/wizard@latest -i nextjs --saas --org <your-org-name> --project <your-project-name>
This command installs the @sentry/nextjs
npm package and automatically configures Sentry for your app.
You’ll be asked the following questions in the CLI. Answer “yes” to each.
◇ Do you want to route Sentry requests in the browser through your Next.js server to avoid ad blockers?
│ Yes
◇ Do you want to enable Tracing to track the performance of your application?
│ Yes
◇ Do you want to enable Session Replay to get a video-like reproduction of errors during a user session?
│ Yes
◇ Do you want to create an example page ("/sentry-example-page") to test your Sentry setup?
│ Yes
◇ Are you using a CI/CD tool to build and deploy your application?
│ Yes
Add the code snippet provided by the setup wizard to the next.config.ts
file. Remember to replace yourNextConfig
with nextConfig
as defined in the file.
Replace the contents of the app/global-error.tsx
file with the code snippet provided by the setup wizard. Import the default Next.js error page component:
import NextError from "next/error";
Replace the placeholder comment with the default Next.js error page component:
- {/* Your Error component here... */}
+ <NextError statusCode={0} />
To validate the Sentry setup, first run your development server:
npm run dev
Then visit the /sentry-example-page
route in your browser. For most Next.js applications, this will be at localhost
.
Sentry example page route - with error generating button
Click the Throw Sample Error button and then open your project in the Sentry dashboard. You’ll see the two example errors as Sentry issues:
Example issues in Sentry dashboard
You can click on an issue to get more details about it:
Example issue details
Note that Sentry focuses on reporting unhandled errors, like the ones above. It doesn’t automatically detect errors caught in a try...catch block or similar error-handling mechanism, and it won’t mark them as handled unless you report them explicitly.
Sentry provides a captureException()
method to capture handled errors. It takes an Error object as an argument:
import * as Sentry from "@sentry/browser";
try {
aFunctionThatMightFail();
} catch (err) {
Sentry.captureException(err);
}
You can manually capture a message using the captureMessage()
method:
Sentry.captureMessage("Something went wrong");
Upload Source Maps to Sentry
Source maps enable readable stack traces in your errors, allowing you to see where an error originates in the original, unminified code.
The @sentry/nextjs
package automatically generates and uploads source maps. If you’re deploying with Vercel, use the Vercel integration to handle source map uploads during deployment.
To install the Sentry integration on Vercel, click the Connect Account button:
Sentry Vercel integration
In the popup, select the note-taking app project and click Connect Account.
Now connect your Sentry project to the Vercel project:
Connect Sentry project to Vercel project
You’ll notice additional environment variables in the Vercel project:
Environment variables added to Vercel project
Instrument Next.js Server Actions
In the Next.js app, wrap the getNotes function with the Sentry withServerActionInstrumentation
method in the actions.ts
file:
export async function getNotes() {
"use server";
try {
return await Sentry.withServerActionInstrumentation(
"getNotesServerAction",
{
recordResponse: true,
},
async () => {
// Get the client
const supabase = await createClient();
// Get the current user
const { data: userData } = await supabase.auth.getUser();
if (!userData.user) {
throw new Error("User not authenticated");
}
const { data: notes, error } = await supabase.rpc(
'slow_get_notes',
{ p_user_id: userData.user.id }
);
// If there's a database error, throw it
if (error) {
console.error('Error fetching notes:', error);
Sentry.captureException(error);
throw new Error("Failed to retrieve notes");
}
// Return the notes
return notes;
},
);
} catch (error) {
// Capture the error in Sentry with full details
console.error('Error in getNotes:', error);
Sentry.captureException(error);
// Create a custom error with a generic message
const clientError = new Error("Something went wrong while loading your notes. Please try again later.");
// Rethrow the error with generic message for the client
throw clientError;
}
}
Import Sentry at the top of the file along with the other imports:
import * as Sentry from "@sentry/nextjs";
The modified getNotes method enables Sentry to name and identify Server Actions, capture errors, and record performance.
Sentry doesn’t currently monitor Server Actions automatically. Automatic monitoring will be added once the Next.js Turbopack bundler is stable.
Commit these changes to your remote GitHub repository. Vercel will automatically deploy your application.
API Call Timeout Troubleshooting Using Distributed Tracing
Open your deployed application and log in. You’ll see that the notes fail to load:
Notes loading error
You’ll see a connection error or a message saying there was a problem rendering the server component.
Let’s investigate the “Failed to retrieve notes” error using Sentry. Open the issue in your Sentry dashboard to see details about the error:
Failed notes loading Sentry issue
The error name doesn’t tell us much. We can see that this error occurred across two environments: a Node environment and a Linux server. The Stack Trace shows the line of code where the error occurred:
Failed notes loading issue stack trace
The error was captured as an event using Sentry’s captureException
method.
Click the View Full Trace button in the Trace Preview section:
Notes loading error trace view
Tracing captures the timing and flow of requests and operations in an app. A trace is made up of transactions, which are events your application sends to Sentry. Each transaction contains spans, which are timed operations within the application flow. All transactions and spans in a trace share the same Trace ID.
The Trace View shows that the slowest transaction occurred while loading the notes page, when fetching notes using the getNotesServerAction
Server Action. The POST
request to Supabase was canceled due to a statement timeout.
The transaction has one span: a POST
request that took 8.29 seconds:
Slowest transaction - API POST request to Supabase
With distributed tracing, Sentry allows you to trace client-side and server-side operations.
Why Did the API Request Time Out and How Do I Fix It?
In the example note-taking app, notes are fetched using the slow_get_notes
Postgres function, which has a two-minute delay.
Server Actions run as Vercel functions. On the Hobby plan, these functions have a maximum execution time of 60 seconds, with a default timeout of 10 seconds.
As the function took longer than the permitted execution time, the request timed out. The same error can be caused by network connectivity issues.
Common ways to fix Server Action timeout errors include:
Increase the default maximum execution duration if a function is computationally expensive and is expected to take a long time. On Vercel, you can also enable fluid compute to increase the maximum execution limit for serverless functions.
Confirm that the function returns an HTTP response. Otherwise, Vercel will wait until the maximum duration has elapsed and then time out.
Ensure the function is not being called in an infinite loop – check the logic in any loops or recursive calls.
Check that any external API integrations are working and returning responses.
If your database queries are slow, query optimization can help. Start by indexing columns that are read often, such as those used for filtering, sorting, or joining tables. Read more about query optimization in the Supabase docs.
To take your Supabase debugging to the next level, use the Supabase + Sentry Integration to trace slow database queries and catch database errors.
Distributed Tracing Brings Clarity
Distributed tracing makes it easier to debug errors and performance issues in a Next.js app by tracking events and operations across the stack. It’s especially helpful for finding the source of timeout errors with vague error messages.
At Sentry, we used distributed tracing to quickly debug an intermittent Slack integration error affecting one of our customers. Read more about it in our blog post, Debugging a Slack Integration with Sentry’s Trace View.
To learn more about distributed tracing in Next.js, take a look at the following Sentry resources: