Blitz comes with a number of useful errors you can use throughout your application.
AuthenticationError
name
: "AuthenticationError"statusCode
: 401message
: "You must be logged in to access this"CSRFTokenMismatchError
name
: "CSRFTokenMismatchError"statusCode
: 401message
: "You must be logged in to access this"AuthorizationError
name
: "AuthorizationError"statusCode
: 403message
: "You are not authorized to access this"NotFoundError
name
: "NotFoundError"statusCode
: 404message
: "This could not be found"RedirectError
name
: "RedirectError"ErrorBoundary
component will automatically
handle this redirect for you.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.
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 } from "@blitzjs/next"
import { useQueryErrorResetBoundary } from "@blitzjs/rpc"
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.
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'
.
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)
}
}
}}
/>