I'm building a Next.js application and I'm trying to send custom error messages from the server to the client side when using Next JS new server-side actions. My server-side code is working fine, but I'm having trouble handling custom error types. When I try to catch these errors on the client side, I receive the following error: "Warning: Only plain objects can be passed to Client Components from Server Components. Error objects are not supported."
Here's my client-side code:
"use client"
async function handleDelete() {
try {
await deleteAddress(contactId, addressId)
} catch (e) {
console.log(e instanceof FetchError) // Is always false
}
}
And here's my server-side code:
"use server"
async function deleteAddress(contactId: number, addressId: number) {
const response = await fetch(process.env.BASE_API_URL + "/contacts/" + contactId + "/addresses/" + addressId, {
cache: "no-cache",
method: "DELETE"
});
if (!response.ok) {
throw new FetchError("There was a problem with your request", "Something went wrong", "destructive");
}
}
My goal is to catch the FetchError on the client side and handle it appropriately. What is the best way to send custom errors back to the client side in Next.js while avoiding the "Error objects are not supported" issue?
I'm building a Next.js application and I'm trying to send custom error messages from the server to the client side when using Next JS new server-side actions. My server-side code is working fine, but I'm having trouble handling custom error types. When I try to catch these errors on the client side, I receive the following error: "Warning: Only plain objects can be passed to Client Components from Server Components. Error objects are not supported."
Here's my client-side code:
"use client"
async function handleDelete() {
try {
await deleteAddress(contactId, addressId)
} catch (e) {
console.log(e instanceof FetchError) // Is always false
}
}
And here's my server-side code:
"use server"
async function deleteAddress(contactId: number, addressId: number) {
const response = await fetch(process.env.BASE_API_URL + "/contacts/" + contactId + "/addresses/" + addressId, {
cache: "no-cache",
method: "DELETE"
});
if (!response.ok) {
throw new FetchError("There was a problem with your request", "Something went wrong", "destructive");
}
}
My goal is to catch the FetchError on the client side and handle it appropriately. What is the best way to send custom errors back to the client side in Next.js while avoiding the "Error objects are not supported" issue?
Share Improve this question asked Oct 21, 2023 at 15:11 hantorenhantoren 1,2756 gold badges27 silver badges47 bronze badges 1- do yo have solution on this one? we have literally the same problem. – aRtoo Commented May 8, 2024 at 5:21
3 Answers
Reset to default 4You should serialize the error message into a plain object and then send it to the client.
On the server side (in your server ponent), create a custom error object and serialize it into a plain object with a message that you can send to the client.
// serverComponent.ts
export async function serverFunction() {
try {
// Your server-side code here
// If an error occurs, create a custom error object
throw new Error("Custom error message");
} catch (error) {
// Serialize the error into a plain object
return { error: { message: error.message } };
}
}
On the client side (in your client ponent), check for the error object and handle it accordingly.
// clientComponent.tsx
import { serverFunction } from './serverComponent';
export default function ClientComponent({ error }) {
if (error) {
// Handle the error here, such as displaying it to the user
return <div>Error: {error.message}</div>;
}
// Render the ponent content when there are no errors
return <div>Your ponent content here</div>;
}
export async function getServerSideProps() {
// Call the server ponent function and pass any error object
const result = await serverFunction();
return {
props: {
error: result.error, // Pass the error object to the client ponent
},
};
}
As I have learned it recently, Error Boundaries do not catch errors inside event handlers. Your handleDelete is triggered by an event I presume.
As Radmehr wrote, one way is to serialize the error into a new object (for example return { error: e.message })
inside the server action and return that object from the same catch block to the client.
It still won't trigger a client side catch, you need to throw a new Error with the message returned as response by the action.
Another solution I currently use for this scenario is the following (feels a bit hacky but works fine with the App router):
- on the client side, set up a state variable for error messages
- still on the client side, write a useEffect to watch for the changes of the error state
- inside the server action, throw a new Error (as you did should be fine)
- on the client side, wrap your server action call in a try-catch (also same as you did)
- now in the catch block, set the error instance's message property as the state
- as soon as the state changes, the useEffect runs and throws a new Error which can be caught by the nextjs error boundary :)
NOTE: you'll get an uncaught error warning in the browser but it's safe to ignore.
plete client side example:
"use client"
const [error, setError] = useState('');
useEffect(() => {
if (error) throw error;
}, [error]);
async function handleDelete() {
try {
await deleteAddress(contactId, addressId)
} catch (e) {
const e = error as Error;
setError(e.message);
}
}
class ErrorHandler extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
Error.captureStackTrace(this, this.constructor);
}
sendErrorResponse(res) {
return res.status(this.statusCode).json({
success: false,
error: this.message,
message: this.message,
});
}
}
module.exports = ErrorHandler;
const ErrorHandler = require("../utils/errorHandler");
//like
if (!response.ok) {
return new ErrorHandler('Something went wrong', 400).sendErrorResponse(res);
}