cacheSignal
Returns an AbortSignal that fires when the surrounding server render's cache lifetime ends.
const signal = cacheSignal()- Added in React 19.2.0
Return value
AbortSignal | nullAn AbortSignal if called during rendering. Otherwise cacheSignal() returns null. The signal aborts when the cache() lifetime is over: React has finished rendering successfully, the render was aborted, or the render failed.
Changelog
- PHP 19.2.0Introduced for React Server Components, to signal that the surrounding `cache()` lifetime has ended.
See also
cacheSignal hands you an AbortSignal tied to the lifetime of the current server render's cache. When React finishes rendering, or the render is aborted, or it fails, the signal aborts — and anything still in flight can stop.
When to reach for it
Two situations. First, cancellation: pass signal: cacheSignal() to fetch or a database driver so requests whose results can no longer be used are dropped rather than left to occupy a connection. On a page that aborts because the user navigated away, this is the difference between releasing the connection pool immediately and holding it until every query returns.
Second, noise suppression. Work that is aborted mid-flight throws, and those throws are not real failures. Guarding your error reporting with if (!cacheSignal()?.aborted) keeps cancellation out of the error dashboard.
Reading the null
Outside rendering, cacheSignal() returns null — deliberately, to make it clear the current scope is not cached forever. That is why the optional chain in cacheSignal()?.aborted matters: a bare .aborted throws in exactly the code paths (background jobs, module init) where you did not expect to be outside a render. In Client Components it currently always returns null, but React warns not to rely on that staying true.
Example
// app/data.js — Server Components only
import { cache, cacheSignal } from "react";
// Deduplicate the fetch, and abort it if the render goes away.
const dedupedFetch = cache(async url => {
const response = await fetch(url, { signal: cacheSignal() });
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
});
async function Weather() {
const data = await dedupedFetch("https://example.com/api/weather");
return (
<p>
{data.city}: {data.temperature}°C
</p>
);
}
export default async function WeatherPage() {
return (
<main>
<h1>Today</h1>
<Weather />
</main>
);
}When to use it
- Abort in-flight database queries and fetches when the user navigates away mid-render.
- Skip error logging for failures that are really just cancellations after the render finished.
- Tie a third-party client's cancellation token to the render that requested the work.
Frequently asked questions
Why does cacheSignal() return null?
Because it was called outside of rendering — React returns null to make clear that the current scope is not cached forever — or because it was called in a Client Component, where it currently always returns null. Always use optional chaining, as in cacheSignal()?.aborted.
When exactly does the signal abort?
When the cache() lifetime ends, which is any of three outcomes: React has successfully finished rendering, the render was aborted, or the render failed. In all three cases nothing further can consume the result, so in-flight work is safe to cancel.
Do I need cache to use cacheSignal?
No, but they are designed together: cacheSignal marks the boundary of the same per-request lifetime that cache uses. The typical pattern wraps a fetching function in cache and passes cacheSignal() into the request so deduplication and cancellation share one scope.
More examples
Keeping cancellations out of the error log
When the render completes normally and the query genuinely fails, the error reaches logError. When the user navigates away and React aborts the render, the same catch runs but cacheSignal().aborted is true and nothing is logged. The optional chain covers the case where getData is called outside a render, where cacheSignal() is null.
import { cacheSignal } from "react";
import { queryDatabase } from "./database";
import { logError } from "./telemetry";
async function getData(id) {
try {
return await queryDatabase(id);
} catch (error) {
// If the signal already aborted, this failure is just the render
// being torn down — not something an on-call engineer should see.
if (!cacheSignal()?.aborted) {
logError(error);
}
return null;
}
}
export default async function Report() {
const data = await getData("report_1");
return <p>{data ? data.title : "Report unavailable"}</p>;
}Passing the signal to a client that takes an AbortSignal
The first function is the common case: hand the signal straight to a client that speaks AbortSignal. The second shows the bridge for older clients — subscribe to the signal's abort event and call whatever cancellation method the client provides. Both are wrapped in cache so concurrent components asking the same question share one in-flight request and one cancellation.
import { cache, cacheSignal } from "react";
import { search } from "./search-client";
export const runSearch = cache(async query => {
const signal = cacheSignal();
// Most modern clients accept a standard AbortSignal.
const results = await search(query, { signal });
// For clients that only expose a cancel() method, subscribe instead.
return results;
});
export const runLegacySearch = cache(async query => {
const request = search.legacy(query);
const signal = cacheSignal();
signal?.addEventListener("abort", () => request.cancel(), { once: true });
return request.promise;
});
Discussion