Calling notFound() in server action
Answered
Chinese Alligator posted this in #help-forum
Chinese AlligatorOP
Hi!
How do you handle 404 from server actions?
Given the following code:
I've expected that when I call such action,
But it does not. I just get error with message
Also, this error message is not typed, so is it reliable to use?
Would appreciate your experience on that.
How do you handle 404 from server actions?
Given the following code:
async function serverAction() {
"use server";
notFound();
}I've expected that when I call such action,
not-found.tsx page will be automatically shown.But it does not. I just get error with message
NEXT_NOT_FOUND, so I need manually handle it.Also, this error message is not typed, so is it reliable to use?
Would appreciate your experience on that.
Answered by Chinese Alligator
I've digged more and found another workaround, inspired by this SO discussion: https://stackoverflow.com/questions/67889383/react-error-boundaries-with-useeffect-and-async-function-what-im-missing
By React docs, errors thrown in async code are not handled by error boundaries. That's why Next can't show appropriate
By React docs, errors thrown in async code are not handled by error boundaries. That's why Next can't show appropriate
not-found.txs (and error.tsx as well). Following the suggestion from SO, I've made it working by re-throwing the error during render:// buttons.tsx
"use client";
import { useState } from "react";
export default function Button({ action }) {
const [error, setError] = useState<Error | null>(null);
// Re-throw error to be caught by error.tsx / not-found.tsx
if (error) throw error;
return (
<button onClick={() => action().catch(setError)}>
Button with server action
</button>
);
}8 Replies
@Chinese Alligator Hi!
How do you handle 404 from server actions?
Given the following code:
ts
async function serverAction() {
"use server";
notFound();
}
I've expected that when I call such action, `not-found.tsx` page will be automatically shown.
But it does not. I just get error with message `NEXT_NOT_FOUND`, so I need manually handle it.
Also, this error message is not typed, so is it reliable to use?
Would appreciate your experience on that.
try to return your notfound function. Like
Else return some json that says, it wasn't found. Another thing you can do is do thrown an error and your frontend catch the error and handle it.
In both ways the redirect to the notfound page will be handled by the client
async function serverAction() {
"use server";
return notFound();
}Else return some json that says, it wasn't found. Another thing you can do is do thrown an error and your frontend catch the error and handle it.
In both ways the redirect to the notfound page will be handled by the client
if you do wrap
try/catch around serverAction, you need to rethrow the special NOT_FOUND errorimport { notFound } from "next/navigation";
import { isNotFoundError } from "next/dist/client/components/not-found";
export default function Page() {
return (
<form
action={async () => {
"use server";
try {
notFound();
} catch (e) {
// This will be made easier in Next.js 15 with the unstable_rethrow API
if (isNotFoundError(e)) throw e;
}
}}
>
<button>Click me</button>
</form>
);
}@joulev i can't reproduce the issue. are you sure you don't wrap something around `serverAction`?
Chinese AlligatorOP
I've tried your example and confirm it shows correct
- if server action is passed as form
- if server action is passed to any other handler (e.g.
Modified example to demo it:
not-found.tsx. After investigation, I discovered the following:- if server action is passed as form
action , notFound is handled automatically on client- if server action is passed to any other handler (e.g.
onClick or useEffect), notFound is NOT handled automatically Modified example to demo it:
// page.tsx
export default function Page() {
return (
<Button
action={async () => {
"use server";
notFound();
}}
/>
);
}
// button.tsx
"use client";
export default function Button({ action }) {
return <button onClick={() => action()}>Click me</button>;
}Chinese AlligatorOP
Created an issue in Next.js repo https://github.com/vercel/next.js/issues/69480
@Chinese Alligator Created an issue in Next.js repo https://github.com/vercel/next.js/issues/69480
good catch, yeah i think this is a bug.
worth to mention though that
worth to mention though that
redirect still works, despite it working in more or less the same mechanism as notFound. so you can perhaps use redirect("/404") as a workaround at the moment.Chinese AlligatorOP
I've digged more and found another workaround, inspired by this SO discussion: https://stackoverflow.com/questions/67889383/react-error-boundaries-with-useeffect-and-async-function-what-im-missing
By React docs, errors thrown in async code are not handled by error boundaries. That's why Next can't show appropriate
By React docs, errors thrown in async code are not handled by error boundaries. That's why Next can't show appropriate
not-found.txs (and error.tsx as well). Following the suggestion from SO, I've made it working by re-throwing the error during render:// buttons.tsx
"use client";
import { useState } from "react";
export default function Button({ action }) {
const [error, setError] = useState<Error | null>(null);
// Re-throw error to be caught by error.tsx / not-found.tsx
if (error) throw error;
return (
<button onClick={() => action().catch(setError)}>
Button with server action
</button>
);
}Answer
Chinese AlligatorOP
I want to add, that everything was in React docs:
https://react.dev/reference/rsc/use-server#calling-a-server-action-outside-of-form
Wrapping server action call into
https://react.dev/reference/rsc/use-server#calling-a-server-action-outside-of-form
When using a Server Action outside of a form, call the Server Action in a Transition, which allows you to display a loading indicator, show optimistic state updates, and handle unexpected errors. Forms will automatically wrap Server Actions in transitions.
Wrapping server action call into
startTransition allows to omit error handling at all. Here is the final code:// button.tsx
"use client";
import { useTransition } from "react";
export default function Button({ action }) {
const [isPending, startTransition] = useTransition();
const onClick = () => {
startTransition(async () => {
await action();
});
};
return <button onClick={onClick}>Button with server action</button>;
}