Introduction
Displaying error messages on the client side is critical to providing a seamless user experience. As a server function becomes more complex, the possibility of having multiple different error messages increases. Decoupling error types from their visual representation ensures that the client can define and display error messages in a consistent way. This is especially important when considering internalization, as error messages may need to be displayed in different languages using some form of client-side translation hook. In addition, separating concerns ensures a cleaner and more maintainable codebase.
In this post, we will explore how to handle different error messages in a client application using Next.js server actions. However, this is applicable to any client-server architecture. We will define client-side error messages and display them based on server-side errors, without comparing untyped strings and ensuring that the error type is decoupled from its visual representation.
The Problem
Problem with server-side error handling
As suggested in the Next.js Server Actions documentation, the server-side code can throw a new error that will be caught by the nearest error boundary on the client. This is the most common way to handle errors in server actions.
In this example, the error message itself is defined by the server as an untyped string. This is a common approach and works well for simple applications. However, as described above, we want to define custom typed errors on the client side.
Example server action with serializable errors
Returning serializable error objects from the server is an alternative approach to gain more control over the structure of the response. Look again at the nextjs example for server-side validation and error handling.
In this approach, the server still defines the error message. Let's explore a solution where the client defines the error message.
Solution
Defining error types
First, we use Typescript to define a type for the error to allow only certain errors to be thrown on the server.
Defining error entities
An actual error in the client is now a single entity of the ErrorType
. You can later define whatever properties you need in your user interface to display the error.
Client side error handling
In our UI component, we invoke the server action by clicking a button. If an error occurs, we get back a serialized enum string and map it typesafe to the error entity. Notice how we can define error messages inside the client component and have access to all available client hooks such as translation hooks.
Conclusion
Everything is well typed, there is no possibility of misspellings or typos in error type strings. The error messages are decoupled from their visual representation, which makes it easy to change the UI without changing the error type.
If you only have one error message, you don't need this approach. You can just catch the error and display the appropriate error UI. However, if you end up comparing strings to find out what type of error is being thrown, this is my way to go.
Please let me know if you know of a better, cleaner, and more developer-friendly way to do this.