Error Handling Question
Answered
Ichneumonid wasp posted this in #help-forum
Ichneumonid waspOP
Hello,
I am having trouble trying to figure out what the standard/best practice for error handling is. I have read the relevant documentation page but its not exactly what Im looking for.
My main questions are:
1) From server actions, do I always return something (an object) which can be a value or error and handle the error at the place where the server action was called by having a try/catch ?
2) Should I be throwing errors in server actions at all or bubble them up at where the action was called ? (So a component or smth)
3) Should all error handling be done with the error boundary ? So in error.tsx.
I am having trouble trying to figure out what the standard/best practice for error handling is. I have read the relevant documentation page but its not exactly what Im looking for.
My main questions are:
1) From server actions, do I always return something (an object) which can be a value or error and handle the error at the place where the server action was called by having a try/catch ?
2) Should I be throwing errors in server actions at all or bubble them up at where the action was called ? (So a component or smth)
3) Should all error handling be done with the error boundary ? So in error.tsx.
Answered by aardani
1) From server actions, do I always return something (an object) which can be a value or error and handle the error at the place where the server action was called by having a try/catch ?
Always return something that is serializable. Do not return error because Next.js front-end catches those error and obfuscate the error so you dont have any usefull error info at the front-end (for the sake of security). If the error is readable, return an error object instead. So yes, try-catching the invocation of server-side as an action is futile. Always try-catch inside the server action and convert it to readable object.
2) Should I be throwing errors in server actions at all or bubble them up at where the action was called ? (So a component or smth)
Same as question number one, can't bubble them at component because you lost info like error codes. You bubble them inside the server-action.
3) Should all error handling be done with the error boundary ? So in error.tsx.
Not necessarily but can be usefull for uncaught/general errors but only marginally better. This is because error caught on event handlers (onClick, onSubmit, etc) doesn't get caught by error boundary and throws silent errors. Error boundary is only useful if error occurred during client-side rendering.
If you're using typescript, most of the time the error is already contained within the types itself - i.e if you follow the types and its errors, you wouldn't need to worry too much about error handling on the rendering side. The most common error comes from data fetching and as you know it, server actions. So i do suggest creating a "Result" interface where all data fetching function returns to so you can have obvious/generic error codes (apart from custom error codes) that the front-end can easily handle if those error happened.
type Result<T> = {
data: T,
error: {
code: 'not found' | 'unauthenticated' | (string & {}),
// have default code but also allow custom codes
message: string,
// default message if i18n is not handled in front-end yet
}
}
export function createPostAction(): Result<Post> {
return {
data: ...,
error: { ... }
}
}21 Replies
1) From server actions, do I always return something (an object) which can be a value or error and handle the error at the place where the server action was called by having a try/catch ?
Always return something that is serializable. Do not return error because Next.js front-end catches those error and obfuscate the error so you dont have any usefull error info at the front-end (for the sake of security). If the error is readable, return an error object instead. So yes, try-catching the invocation of server-side as an action is futile. Always try-catch inside the server action and convert it to readable object.
2) Should I be throwing errors in server actions at all or bubble them up at where the action was called ? (So a component or smth)
Same as question number one, can't bubble them at component because you lost info like error codes. You bubble them inside the server-action.
3) Should all error handling be done with the error boundary ? So in error.tsx.
Not necessarily but can be usefull for uncaught/general errors but only marginally better. This is because error caught on event handlers (onClick, onSubmit, etc) doesn't get caught by error boundary and throws silent errors. Error boundary is only useful if error occurred during client-side rendering.
If you're using typescript, most of the time the error is already contained within the types itself - i.e if you follow the types and its errors, you wouldn't need to worry too much about error handling on the rendering side. The most common error comes from data fetching and as you know it, server actions. So i do suggest creating a "Result" interface where all data fetching function returns to so you can have obvious/generic error codes (apart from custom error codes) that the front-end can easily handle if those error happened.
type Result<T> = {
data: T,
error: {
code: 'not found' | 'unauthenticated' | (string & {}),
// have default code but also allow custom codes
message: string,
// default message if i18n is not handled in front-end yet
}
}
export function createPostAction(): Result<Post> {
return {
data: ...,
error: { ... }
}
}Answer
Ichneumonid waspOP
So to understand this better, I have a situation where I have a /login server-side page. That page has a LoginForm client component with the buttons for logging in. The buttons call relevant server actions to login the user with google, github, etc.
An example server action is this:
Im really confused here as in the good case the server action redirects, but when there is an error should I return a serializable object ? To me that seems weird as some times nothing is returned from the server action and some others there is. How should I handle this case?
An example server action is this:
export async function googleSignIn() {
const supabase = await createClient();
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/oauth?next=/`,
},
})
if (error) throw new AuthorizeTryError('Failed to Sign In with Google: ' + error);
redirect(data.url);
}Im really confused here as in the good case the server action redirects, but when there is an error should I return a serializable object ? To me that seems weird as some times nothing is returned from the server action and some others there is. How should I handle this case?
Yes
redirect() is a different case, redirect and all its counterpart is a special case because those are handled differently in the engine
Ichneumonid waspOP
so instead of throw error i should return ? Then how would it be handled in the client component it got called from?
export function LoginForm() {
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
const [email, setEmail] = useState<string>("")
async function handleGoogle() {
try {
await googleSignIn()
} catch (e: any) {
throw new Error(e.message ?? "Google sign-in failed")
}
}this is why you can technically "inject" data and pretend its a NEXT_REDIRECT error but thats a workaround you'd rather avoid
@Ichneumonid wasp so instead of throw error i should return ? Then how would it be handled in the client component it got called from?
javascript
export function LoginForm() {
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
const [email, setEmail] = useState<string>("")
async function handleGoogle() {
try {
await googleSignIn()
} catch (e: any) {
throw new Error(e.message ?? "Google sign-in failed")
}
}
Just like how you'd do it with fetch()
export function LoginForm() {
async function onSubmit() {
const res = await googleSignIn()
if (res.error) // do something
else // do something
}Ichneumonid waspOP
if res.error maybe do a toast else do nthing as in that case I would have already redirected
well then just remove the
elseIchneumonid waspOP
so does this apply to server actions in general ? So should all of my server actions regardless of what they do or if they redirect return an error in an object instead of throwing? Or is there a case where id throw in a server action ?
the only case where you'd throw in server action is if its a redirect(), not-found(), unauthorized(), other NEXT_REDIRECT related functions,
else its better to just return object
else its better to just return object
@aardani the only case where you'd throw in server action is if its a redirect(), not-found(), unauthorized(), other NEXT_REDIRECT related functions,
else its better to just return object
Ichneumonid waspOP
but these happen automatically correct?
what do you mean?
Ichneumonid waspOP
like I dont need to throw my self if its a redirect or a not found
nextjs does that automatically
yeah as long as you dont put redirect() in a try-catch :)))
Ichneumonid waspOP
yea that I get
awesome
Ichneumonid waspOP
Ok I think it cleared in my head a bit how to handle these
thank you!
your welcome!