Skip to content
Error Handling

Error Handling

If an error is thrown inside fetcher, it will be returned as error by the hook.

const fetcher = url => fetch(url).then(r => r.json())
// ...
const { data, error } = useSWR('/api/user', fetcher)

The error object will be defined if the fetch promise is rejected.

Status Code and Error Object

Sometimes we want an API to return an error object alongside the status code. Both of them are useful for the client.

We can customize our fetcher to return more information. If the status code is not 2xx, we consider it an error even if it can be parsed as JSON:

const fetcher = async url => {
  const res = await fetch(url)
  // If the status code is not in the range 200-299,
  // we still try to parse and throw it.
  if (!res.ok) {
    const error = new Error('An error occurred while fetching the data.')
    // Attach extra info to the error object. = await res.json()
    error.status = res.status
    throw error
  return res.json()
// ...
const { data, error } = useSWR('/api/user', fetcher)
// === {
//   message: "You are not authorized to access this resource.",
//   documentation_url: "..."
// }
// error.status === 403

Note that data and error can exist at the same time. So the UI can display the existing data, while knowing the upcoming request has failed.

Here we have an example.

Error Retry

SWR uses the exponential backoff algorithm (opens in a new tab) to retry the request on error. The algorithm allows the app to recover from errors quickly, but not waste resources retrying too often.

You can also override this behavior via the onErrorRetry option:

useSWR('/api/user', fetcher, {
  onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
    // Never retry on 404.
    if (error.status === 404) return
    // Never retry for a specific key.
    if (key === '/api/user') return
    // Only retry up to 10 times.
    if (retryCount >= 10) return
    // Retry after 5 seconds.
    setTimeout(() => revalidate({ retryCount }), 5000)

This callback gives you the flexibility to retry based on various conditions. You can also disable it by setting shouldRetryOnError: false.

It's also possible to provide it via the Global Configuration context.

Global Error Report

You can always get the error object inside the component reactively. But in case you want to handle the error globally, to notify the UI to show a toast (opens in a new tab) or a snackbar (opens in a new tab), or report it somewhere such as Sentry (opens in a new tab), there's an onError event:

<SWRConfig value={{
  onError: (error, key) => {
    if (error.status !== 403 && error.status !== 404) {
      // We can send the error to Sentry,
      // or show a notification UI.
  <MyApp />