🚀Announcing Flightcontrol - Optimized Deployment for Fullstack Blitz.js and Next.js 🚀
Back to Documentation Menu

Error Handing

Topics

Jump to a Topic

Built-In Errors

Blitz comes with a number of useful errors you can use throughout your application.

  • AuthenticationError
    • name: "AuthenticationError"
    • statusCode: 401
    • Default message: "You must be logged in to access this"
  • CSRFTokenMismatchError
    • name: "CSRFTokenMismatchError"
    • statusCode: 401
    • Default message: "You must be logged in to access this"
  • AuthorizationError
    • name: "AuthorizationError"
    • statusCode: 403
    • Default message: "You are not authorized to access this"
  • NotFoundError
    • name: "NotFoundError"
    • statusCode: 404
    • Default message: "This could not be found"
  • RedirectError
    • name: "RedirectError"
    • You can throw this error from a render function if you want to redirect the user while also preventing the user from seeing on the current render path. Our ErrorBoundary component will automatically handle this redirect for you.
    • Example: throw new RedirectError('/login')

To use, import from blitz and use like any JavaScript Error. If you're curious, you can see the source code for these.

import { AuthenticationError } from "blitz"

try {
  throw new AuthenticationError()
} catch (error) {
  if (error.name === "AuthenticationError") {
    // Handle this error appropriately, like show a login screen
    console.log(error.statusCode)
    console.log(error.message)
  }
}

You can throw these or any other errors from anywhere in your app, whether on the server or on the client.

Catching and Handling Errors on the Client

You handle errors on the client by using <ErrorBoundary>.

By default, new Blitz applications include a top-level ErrorBoundary and FallbackComponent in app/pages/_app.tsx.

It looks something like this:

// app/pages/_app.tsx
import {
  AppProps,
  ErrorBoundary,
  ErrorComponent,
  useQueryErrorResetBoundary,
} from "blitz"
import LoginForm from "app/auth/components/LoginForm"

export default function App({ Component, pageProps }: AppProps) {
  // This ensures the Blitz useQuery hooks will automatically refetch
  // data any time you reset the error boundary
  const { reset } = useQueryErrorResetBoundary()

  return (
    <ErrorBoundary FallbackComponent={RootErrorFallback} onReset={reset}>
      <Component {...pageProps} />
    </ErrorBoundary>
  )
}

function RootErrorFallback({ error, resetErrorBoundary }) {
  if (error.name === "AuthenticationError") {
    return <LoginForm onSuccess={resetErrorBoundary} />
  } else if (error.name === "AuthorizationError") {
    return (
      <ErrorComponent
        statusCode={error.statusCode}
        title="Sorry, you are not authorized to access this"
      />
    )
  } else {
    return (
      <ErrorComponent
        statusCode={error.statusCode || 400}
        title={error.message || error.name}
      />
    )
  }
}

That means all errors will at least be caught at the root level. However, you can also add <ErrorBoundary> anywhere else in your app for more localized error handling. To do this, declare a separate useQueryErrorResetBoundary in your component and pass it along to the local ErrorBoundary. If an error is caught by an <ErrorBoundary> somewhere down inside your app tree, then it will not reach the root ErrorBoundary unless you re-throw it.

Handling Server Errors on the Client

A really awesome feature of Blitz is that you can throw any error from a Blitz query or mutation and then use an ErrorBoundary on the frontend to catch and handle it.

For example, with the above _app.tsx, you can throw AuthenticationError inside a Blitz query and then a login screen will automatically show in the client because that root ErrorBoundary is rendering <LoginForm> if error.name === 'AuthenticationError'.

Custom Errors

For errors other than what Blitz provides, it's recommended to create custom Error classes. You can then add custom data attributes that help you handle the error.

Here's an example of how to create a custom error. It's a JavaScript class, so you can be as creative as you want.

import SuperJson from "superjson"

export class UsernameTakenError extends Error {
  name = "UsernameTakenError"
  constructor({ suggestedUserName }) {
    super()
    this.suggestedUserName = suggestedUserName
  }
}
// Register with SuperJson serializer so it's reconstructed on the client
SuperJson.registerClass(UsernameTakenError)
SuperJson.allowErrorProps("suggestedUserName")

throw new UsernameTakenError({ suggestedUserName: "second_best" })

Note that you must register it with SuperJson as shown above in order for instanceof to work on the client. And you must also tell SuperJson about any special error propertries you want to be serialized. By default custom error properties are omitted for security concerns.

Then on the client, you can use a FallbackComponent like above, or you can handle the error in your form submit handler like this.

<Form
  onSubmit={async (values) => {
    try {
      await setUsername(values.username)
    } catch (error) {
      if (error instanceof UsernameTakenError) {
        setSuggestedUsername(error.suggestedUserName)
      }
    }
  }}
/>

Error Pages

See the Error Pages documentation to learn about custom error pages and error fallback components.


Idea for improving this page? Edit it on GitHub.