Next.js Discord

Discord Forum

NextJS server action running twice leading to unexpected behavior

Answered
Rohu posted this in #help-forum
Open in Discord
RohuOP
I have successfully reproduced this bug on a skeleton repo:
- https://github.com/dunstorm/nextjs-example-bug

I am trying to have a dynamic param [id] page with 2 lazy loaded components which use separate server action.

The issue is nextjs server action is running twice, after /results/loading redirects using router.push to /results/[id] we call the server action multiple times, this is a very big issue since it's causing redis to increment the value by 2 instead of 1

 GET /results/loading 200 in 19ms
 POST /results/loading 200 in 1015ms
getSearchResults mieepjoh3ce
 GET /results/mieepjoh3ce 200 in 19ms
getSearchResults mieepjoh3ce
 GET /results/mieepjoh3ce 200 in 19ms


I have tried
- Disabling reactStrictMode
Answered by LuisLl
Take a look at the latest change on my repo, only the "Generate Random Search" button is a client component, the rest are Server Components fetching data on the server, not server actions.
A component can't suspend when awaiting a Server Action to complete as the result of a mutation (clicking the button), so in this case I wrapped the click handler in startTransition to have access to the loading state and display a fallback.
View full answer

33 Replies

try putting the function calling redis.. inside a react cache wrapper
@Rohu
RohuOP
Yes it's inside the cache wrapper but as we see here, getSearchResults is called twice regardless
The only solution seems like is to change to client component since I have written all of the things in nextjs now
But I would like to solve this if possible, should I raise it in github?
If you raise an issue in GitHub first have in mind that you are using Server Actions in a way they are not intended to be used. You're using them to fetch data, when it should be for mutations.
Before you raise it to GitHub try moving the data fetching logic to either a Server Component or a Route Handler. See if this solves the issue, since these are the two recommended ways for fetching data.
The issue here is that your page is rendering twice, therefore your <SearchResults> component is also rendering twice, making the call twice, in different render passes so cache won't help here.
RohuOP
Yes, that's the issue exactly!
But why is page rendering twice, I don't understand - removing suspense doesn't work either
When you call a Server Action you make a POST request to the same URL you're currently on, and Server Actions in a single round trip receive the request and respond with new React Server Component payload, not sure if this is the default bahavior or this only happens when you revalidatePath or revalidateTag inside the Server Action. Anyway they'r enot meant for fetching data
RohuOP
I was intially thinking it's because of streaming but doesn't seem to be the case, even when we remove everything it still renders twice
Even when we remove server actions from the results page, it still renders twice
Even when we switch to client component it still renders twice, seems like a bug? 👀
although using the server action in the client component useEffect works - i.e. called once
I think I found a workaround, using loading.tsx in /search/loading (for loading state) and for createSearchQuery we use redirect inside it and directly call the function from /search/loading and this stops us from rendering the next page twice
Do you want me to push my code to a repo?
RohuOP
Yes, please! Thanks a lot for your time!
@LuisLl Do you want me to push my code to a repo?
RohuOP
or I can share the repo with you, given the username
Your page needs to be a Server Component, and it needs to suspend by awaiting the async function. This way loading.tsx will render in place
Btw actions.ts became queries.ts, no longer a Server Actions file, I removed the "use server" at the top, since Server Actions aren't supposed to be use for Data Fetching.

So, keep your pages Server Components, mark them as async and leverage them to fetch data in there, instead of turning your page into a client component and make use of useEffects.
Another thing you could do is instead of having a /loading/page.tsx that only calls the server action, make the root page.tsx responsible of it and redirect. But experiment with it, find better solutions and patterns👍
RohuOP
Really appreciate your time!

Still not the ideal solution since the loading page doesn't even appear when the createSearchQuery is too quick and there is a delay in between the loading and the result page shown - which is displayed as a blank page

Guess I will have to convert the result page to client component 😢 or atleast the subparts
It worked fine on my end, but it definitely not the ideal solution, I have to go to bed but if you haven’t solved it by tomorrow I can continue helping.

What I would do is:
- /app/page.tsx would be a Server Component and the button that starts the search would be a client component calling the Server Action. Or just wrap the button in a <form> and pass the server action to the action prop

- /app/(pages)/loading/page.tsx and /app/(pages)/loading/loading.tsx will no longer exist

- /app/(pages)/results/[id] would have a loading.tsx
RohuOP
Yes, I have tried the above - the thing I am trying to achieve here is to remove the delay created by createSearchQuery

So when I press the button, it's a blocking operation and it doesn't feel as smooth as the skeleton loading page to the user so that's why the intermediatery page

THe loading.tsx works for solving the twice render problem but NextJS is pretty weird, it sometimes doesn't show the loading.tsx and if it does, it stops showing that once createSearchQuery is complete and shows just a blank page while redirecting
The blank page problem doesn't occur if we are using client component and calling createSearchQuery in useEffect which I think makes sense but then the twice render problem

I guess the only solution is to make everything a client component - it doesn't matter for this use case because results page won't be scraped by search engines
I will try to reproduce the blank page issue in the repo! Thanks for everything you have done so far!
@Rohu The blank page problem doesn't occur if we are using client component and calling `createSearchQuery` in `useEffect` which I think makes sense but then the twice render problem I guess the only solution is to make everything a client component - it doesn't matter for this use case because results page won't be scraped by search engines
Take a look at the latest change on my repo, only the "Generate Random Search" button is a client component, the rest are Server Components fetching data on the server, not server actions.
A component can't suspend when awaiting a Server Action to complete as the result of a mutation (clicking the button), so in this case I wrapped the click handler in startTransition to have access to the loading state and display a fallback.
Answer