Skip to content

Request cancellation

fetchSite and fetchArticle both accept an optional AbortSignal as a third argument. When the signal fires, any in-flight HTTP requests are cancelled immediately.

fetchSite(author, siteSlug, signal?)
fetchArticle(author, articleSlug, signal?)

In server contexts, passing the request’s signal ensures the Scribe PDS fetch is cancelled if the user navigates away before your loader responds. Without it, the server continues fetching data after the response is no longer needed.

In client contexts, passing a signal from an AbortController lets you cancel the fetch on component unmount or when parameters change.

Framework adapters wire this up automatically

Section titled “Framework adapters wire this up automatically”

If you are using a framework adapter (@scribe-atp/react, @scribe-atp/angular, etc.), you do not need to manage signals manually:

  • React hooks (useSite, useArticle) — abort on unmount and on parameter change
  • Angular signals (injectSite, injectArticle) — abort when the host component is destroyed
  • Angular Observable (ScribeService) — abort when you unsubscribe or when async pipe’s component is destroyed
  • Vue composables — abort on unmount and on parameter change
  • Nuxt composables — handled by useAsyncData

Only read on if you are using @scribe-atp/core directly.


Pass request.signal directly from the incoming request:

// React Router v7
export async function loader({ request }: LoaderFunctionArgs) {
return fetchSite('alice.bsky.social', 'alice-bsky-social', request.signal);
}
// Next.js App Router
export async function GET(request: Request) {
return fetchArticle('alice.bsky.social', params.slug, request.signal);
}

Create an AbortController and abort when needed:

// Cancel on component unmount (vanilla JS)
const controller = new AbortController();
const site = await fetchSite('alice.bsky.social', 'alice-bsky-social', controller.signal);
// Later — component unmounts or user navigates away:
controller.abort();

In React you can do this inside useEffect:

useEffect(() => {
const controller = new AbortController();
fetchSite('alice.bsky.social', 'alice-bsky-social', controller.signal)
.then(setSite)
.catch((err) => {
if (err.name !== 'AbortError') throw err;
});
return () => controller.abort();
}, []);

When a signal fires, the rejected promise contains an AbortError. Always check for it and swallow it — a cancelled request is not an error:

try {
const site = await fetchSite(author, siteSlug, controller.signal);
} catch (err) {
if (err instanceof Error && err.name === 'AbortError') {
return; // normal — request was cancelled
}
throw err; // unexpected error — re-throw
}