Page speed, redirects, Core Web Vitals
Fix timeouts on fetch: avoid hanging requests and slow APIs
Fetch requests without timeouts hang indefinitely on slow APIs, blocking renders and burning client connections. Set explicit timeouts and circuit-break failing services.
What's happening
fetch() in browsers and Node.js has no default timeout. A slow upstream service can hang a request for minutes before the OS-level TCP timeout kicks in (~2 minutes). During that window, the request occupies a connection slot, blocks any rendering that depends on it, and prevents Suspense boundaries from showing fallback UI. In serverless environments, the function holds an invocation slot until the platform's own timeout (usually 10-60 seconds).
The lab signal is a Performance trace showing a Pending request that never completes. The field signal is a uptime monitor reporting the page as up but slow, while users report timeouts in client-side error tracking (Sentry, LogRocket). RUM data via web-vitals or a custom resource-timing observer surfaces fetch durations that exceed reasonable thresholds.
The fix is two-part: set explicit timeouts on every fetch (AbortController in browser, fetch's signal option in Node) and add circuit breakers around services that are known to be flaky. Without these, one slow downstream becomes a sitewide outage.
Why it matters
Hanging fetches drag down LCP, INP, and TTFB depending on where they fire. SSR fetches block the initial HTML response. Client-side fetches in event handlers tank INP. Background fetches (analytics, prefetch) saturate the connection limit and starve foreground requests.
User-facing, hanging fetches present as a permanently-spinning loading state. Users wait, then bounce, often without realizing the page is broken. The bounce-rate spike on routes hitting a slow API is hard to attribute without explicit fetch timeouts and error tracking.
Common causes
- fetch() called without an AbortController signal or timeout option.
- Upstream API returning a 200 OK header then trickling data over minutes.
- Database query with no statement_timeout configured returning slowly under load.
- DNS resolution failing silently and falling back to OS-level retry timeouts.
- Third-party API rate-limited but accepting the connection anyway and hanging.
- Mobile network on flaky 3G dropping packets, leaving the TCP connection in limbo.
- Edge runtime functions calling Node.js APIs that hang (nodejs:net) without timeouts.
Detect this on your site
Run a quick scan with the Uptime Monitor. The tool surfaces this exact issue with the records and context needed to apply the fix below.
Open Uptime MonitorHow to fix it
- 1
Add a timeout to every fetch
Use AbortController in the browser or fetch's signal option with AbortSignal.timeout() (modern Node and browsers). Default to 5-10 seconds for user-facing requests, 30-60 seconds for background jobs. Never call fetch() without a signal.
- 2
Wrap server-side fetches with explicit timeouts
In Next.js Server Components and API routes, every external fetch needs a timeout. AbortSignal.timeout(5000) cancels after 5 seconds. Combine with try/catch to render fallback UI or return a 504 from the API route.
- 3
Set database query timeouts
PostgreSQL: SET statement_timeout = 5000 at the connection level, or per-query. MySQL: max_execution_time. Drizzle/Prisma both expose query timeouts. A slow query should fail fast, not hold the connection forever.
- 4
Add circuit breakers around flaky services
Implement (or use a library like opossum) circuit breakers that open after N consecutive failures and short-circuit subsequent calls for a cooldown period. Failed-fast responses are 100x faster than waiting for timeouts to kick in.
- 5
Use Suspense and streaming for slow data
Don't block the entire HTML response on a slow fetch. Wrap slow data fetches in }> in the App Router. The shell streams immediately; slow data fills in when ready or shows error UI on timeout.
- 6
Cache responses to mask slow upstreams
If the upstream is intermittently slow, cache successful responses with stale-while-revalidate. Users get the cached response instantly while you re-fetch in the background. Failed re-fetches don't interrupt service.
- 7
Monitor fetch latency and timeout rate
Track fetch duration p50/p95/p99 and timeout rate per endpoint. Alert when p99 exceeds 10x p50 or timeout rate exceeds 1%. Datadog, Sentry, and OpenTelemetry all support this with auto-instrumented fetch.
Example
// Modern: AbortSignal.timeout (Node 17.3+, all evergreen browsers)
async function safeFetch(url, options = {}) {
try {
const res = await fetch(url, {
...options,
signal: AbortSignal.timeout(5000), // 5s timeout
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
if (err.name === "TimeoutError") {
console.warn(`fetch ${url} timed out after 5s`);
return null;
}
throw err;
}
}
// Server Component with Suspense fallback
async function ProductList() {
const data = await safeFetch("https://api.example.com/products");
if (!data) return <Error message="Service unavailable" />;
return <List items={data} />;
}
export default function Page() {
return (
<Suspense fallback={<Skeleton />}>
<ProductList />
</Suspense>
);
}AbortSignal.timeout on every fetch plus Suspense for graceful degradation.
Frequently asked
5-10 seconds for user-facing requests on the critical path. 30-60 seconds for background jobs. Match the timeout to the user's tolerance — nobody waits 30 seconds for a search suggestion to appear.
Yes since Node 17.3. For older Node use AbortController with setTimeout to call abort(). Same pattern more verbose.
Suspense lets the framework show a fallback UI while a fetch is pending and an error boundary if it throws. Users see something within milliseconds even when the underlying fetch takes seconds.
Related fixes
Page speed, redirects, Core Web Vitals
Fix uptime flapping: stabilize intermittent monitoring failures
Page speed, redirects, Core Web Vitals
Fix TTFB slow: cut Time to First Byte under 800ms
Page speed, redirects, Core Web Vitals
Fix main thread blocked: cut Total Blocking Time and INP
Page speed, redirects, Core Web Vitals
Fix broken internal links draining crawl budget and perf