redirect() from 'next/navigation' doesn't scroll to top of page navigated to
Unanswered
Toyger posted this in #help-forum
ToygerOP
I have a form which is triggering a server action on submit, the on submit handler is as follows:
What I've done:
The server action then submits the form and does a few other server side operations before re-validating the "/event" route and redirecting to it:
What I'm trying to do:
The "/event" route displays events with RSCs and I'd like for the latest event that was just created to be present when the user is redirected to that page after submitting the event. Initially I used the client side router but found on production builds that the new event would not be present without refreshing, I assume this is because the client side redirect occurs before the server is able to invalidate the cache? Either way, calling redirect in the server action successfully displays the latest event however the browser will maintain it's scroll position and not scroll to the top of the page when navigated.
What I've tried:
- Tried using the client side router, cache was still stale.
- Checked Docs
- Checked Github issues
- Checked other forum posts, found a similar one from 2 years ago that was unanswered.
-Both redirect types
What I've done:
const [submitState, submitAction, isSubmitPending] = useActionState(
submitEvent,
null,
);
const onSubmit = () => {
startTransition(() => {
submitAction();
if (submitState?.error) {
toast("We had trouble submitting your event, please try again.");
}
});
};The server action then submits the form and does a few other server side operations before re-validating the "/event" route and redirecting to it:
export async function submitEvent(): Promise<ActionResponse> {
const draftResponse = await getFormWizardDraftData();
if (!draftResponse.success) return { error: draftResponse.error };
if (!draftResponse.data) return { error: "No draft data found to submit" };
const submitResponse = await submitNewEventData(draftResponse.data.form_data);
if (!submitResponse.success) return { error: submitResponse.error };
await markFormWizardDraftCompleteByDraftId(draftResponse.data.id);
revalidatePath("/event");
return redirect("/event");
}What I'm trying to do:
The "/event" route displays events with RSCs and I'd like for the latest event that was just created to be present when the user is redirected to that page after submitting the event. Initially I used the client side router but found on production builds that the new event would not be present without refreshing, I assume this is because the client side redirect occurs before the server is able to invalidate the cache? Either way, calling redirect in the server action successfully displays the latest event however the browser will maintain it's scroll position and not scroll to the top of the page when navigated.
What I've tried:
- Tried using the client side router, cache was still stale.
- Checked Docs
- Checked Github issues
- Checked other forum posts, found a similar one from 2 years ago that was unanswered.
-Both redirect types
56 Replies
@Toyger I have a form which is triggering a server action on submit, the on submit handler is as follows:
**What I've done:**
ts
const [submitState, submitAction, isSubmitPending] = useActionState(
submitEvent,
null,
);
const onSubmit = () => {
startTransition(() => {
submitAction();
if (submitState?.error) {
toast("We had trouble submitting your event, please try again.");
}
});
};
The server action then submits the form and does a few other server side operations before re-validating the "/event" route and redirecting to it:
ts
export async function submitEvent(): Promise<ActionResponse> {
const draftResponse = await getFormWizardDraftData();
if (!draftResponse.success) return { error: draftResponse.error };
if (!draftResponse.data) return { error: "No draft data found to submit" };
const submitResponse = await submitNewEventData(draftResponse.data.form_data);
if (!submitResponse.success) return { error: submitResponse.error };
await markFormWizardDraftCompleteByDraftId(draftResponse.data.id);
revalidatePath("/event");
return redirect("/event");
}
**What I'm trying to do:**
The "/event" route displays events with RSCs and I'd like for the latest event that was just created to be present when the user is redirected to that page after submitting the event. Initially I used the client side router but found on production builds that the new event would not be present without refreshing, I assume this is because the client side redirect occurs before the server is able to invalidate the cache? Either way, calling redirect in the server action successfully displays the latest event however the browser will maintain it's scroll position and not scroll to the top of the page when navigated.
**What I've tried:**
- Tried using the client side router, cache was still stale.
- Checked Docs
- Checked Github issues
- Checked other forum posts, found a similar one from 2 years ago that was unanswered.
-Both redirect types
"I assume this is because the client side redirect occurs before the server is able to invalidate the cache?"
this is weird. calling revalidatePath in server action should refresh the data. can you make a minimal reproducible repository?
this is weird. calling revalidatePath in server action should refresh the data. can you make a minimal reproducible repository?
i have been using revalidatePath in server action and router.push and have no problem with stale data
what URL are you on when you clicked the button?
ToygerOP
Will see if I can find time to create a repo today, I've added the client side router back and the data on "/event" is still stale. The navigation is from "event/new/review", it's a multi step form.
Updated client side code:
Updated server side code:
This is how data is called to the "event/" route:
Updated client side code:
const onSubmit = () => {
startTransition(() => {
submitAction();
if (submitState?.error) {
toast("We had trouble submitting your event, please try again.");
} else {
router.push("/event")
}
});
};Updated server side code:
export async function submitEvent(): Promise<ActionResponse> {
const draftResponse = await getFormWizardDraftData();
if (!draftResponse.success) return { error: draftResponse.error };
if (!draftResponse.data) return { error: "No draft data found to submit" };
const submitResponse = await submitNewEventData(draftResponse.data.form_data);
if (!submitResponse.success) return { error: submitResponse.error };
await markFormWizardDraftCompleteByDraftId(draftResponse.data.id);
revalidatePath("/event");
return null;
}This is how data is called to the "event/" route:
const EventPage = async () => {
const eventDashDataResult = await getEventDashData();
if (!eventDashDataResult.success) {
// return error display
}
const eventDashData = eventDashDataResult.data;
return (
// render cards
)
}tsgetEventDashData() calls data from our service layer which calls the data from postgres via drizzleNote the data is only stale on production builds, not the development server. My assumption is that re-validate would invalidate this info but I'm now realizing that may be incorrect as this method of data fetching has no "cache tag" in anyway?
ToygerOP
I'm fairly certain this is the cause of the issue, 'revalidatePath()' is ineffective when fetching data this way, in the past I successfully used 'router.refresh()' to invalidate server component data and assumed this would work the same but I don't think that's the case.
What url are you on when clicking the server action?
ToygerOP
The user would be on "event/new/review"
Clicks submit > calls action > on action success > gets sent to "event/" with the client side router
particularly on
/eventdo you use
fetch under getEventDashData ? what is your method of data fetching?ToygerOP
getEventDashData is not using fetch, that's what I mentioned earlier as the likely cause of my issue.
I'm using the pattern defined in 'learn nextjs' where you pull the data from the database in the app directly.
ToygerOP
/event is dynamic in the build if that's what you're asking
does problem exists in local
next build? or is it only in cloud/deployment production build?ToygerOP
Exists on the deployed build (which is on AWS with Open Next) && next build but not present on npm run dev
On the development server, I mean
If I refresh the page then the new event is present but on router.push() (client) or redirect() (server) it isn't present
does the problem persists on local
npm run build ?ToygerOP
Yes it does
@Toyger Yes it does
can i just see the build log for
/event ?i can't reproduce the bug
or the behavior you're describing
ToygerOP
I've still got to try forcing the page to dynamic with const dynamic
We've got nextauth checking headers in our service layer
its hard for me to help if you keep changing your codes
if a route is dynamic then data should be fresh if you dont have any caching. i.e refreshing the page will have a different result each time
given /event is dynamic, then it should return fresh data regardless if you used revalidatePath or not
revalidatePath only refresh two things:
- page if static route.
- any cached data inside dynamic route page.
iff you dont use static page,
or iff you dont use cache strategy,
it should return fresh data
- page if static route.
- any cached data inside dynamic route page.
iff you dont use static page,
or iff you dont use cache strategy,
it should return fresh data
maybe it is an issue of OpenNext?
furthermore
i also tested using
redirect in server action being called in client components, with really long paragraphs and can't replicate the behavior you're experiencingim using next 15.5.6
if anything, you'd need extra effort to make it so that
redirect() doesn't scrollalso tried with
return redirect()ToygerOP
Thanks for this by the way, I see what you mean. Very odd that I'm having this issue with stale data. I assume you're testing this all on built versions npm run start?
i can't run
npm run start if i havent build it locallyespecially with how quick build times are in recent updates, it isn't really hard to test it out locally in a minimal setup
ToygerOP
I'll see if I can create a reproduction with some aspects of our stack and see if I have any different results. I'll let you know
ToygerOP
Here's a reproduction, will post the repo in a sec
ToygerOP
Hmm, it seems the posts aren't being cached in this version. I'm a bit confused 😅 going to push and link the repo now
I tried to mimic the layers and fetching/posting patterns as closely as I could with our app. The only real difference is we're using nextauth and a postgres db, interestingly I don't get the headers warning when using better auth so maybe it is somehow a nextauth issue 😅 . This example has the scroll issue but doesn't seem to provide stale data on redirection. A bit confused about that
ToygerOP
Even with 'force-dynamic' enabled on our events app the new event isn't shown, we've definitely got a bigger problem in that regard
ToygerOP
By the way, besides for the scroll issue, the new event not being present is likely simply a race condition. The user is redirected to /event and reads for events before the new event is finished being submitted.
ToygerOP
Yes, I resolved the issue of the new event not being displayed by manually waiting for the result of the server action instead of using useActionState. Only using the client side redirect after the result from the server action is returned by using an
async startTransition. The scroll issue still persists however as shown in the reproduction repo