next.js
next.js copied to clipboard
Dynamic page not updating on subsequent navigations using Link component
Verify canary release
- [X] I verified that the issue exists in the latest Next.js canary release
Provide environment information
Next.js version: next@canary
Node.js version: 18.13
Operating System: Mac OS 13.3.1 (22E261)
Browser: Chrome 112
Which area(s) of Next.js are affected? (leave empty if unsure)
Routing (next/router, next/navigation, next/link)
Link to the code that reproduces this issue
https://github.com/Livog/next-link-app-dir-bug
To Reproduce
- Hard navigate to: http://localhost:3000/dynamic
- Update the input and click submit (this will save the data to the body.json in root)
- Click on Home
- Click on Dynamic
- You should now see the updated value
- Update the value again to something else
- Click on "Home" again
- Click "Dynamic", now the value is not updated while the last value you input is actually saved to the body.json
Describe the Bug
The value on the dynamic page does not update on subsequent navigation to the page using the Next.js Link component. The expected behaviour is that the value should always be up to date on every Link click. This issue persists even when setting
export const dynamic = 'force-dynamic'
export const revalidate = 0
This can cause errors in situations where users need to see updated data, such as when updating user information on an /account page. It is important to note that in the provided example, one needs to navigate back and forth more than once to reproduce the bug. However, in another project where Prisma, a database, and NextAuth are used, and user content is fetched directly from the database, the issue manifests after just one save and navigation back to the account page.
Additionally, there is no network activity in the Chrome DevTools Network tab on the third request, indicating that there is no network request being made when navigating to a force-dynamic page or layout. This behaviour is inconsistent with the expected behaviour.
A workaround for this issue is to remove the Link component and use a regular anchor (<a>
) tag instead. However, this approach removes any loading animations that might be present when using the Link component.
Expected Behavior
The expected behaviour is for the dynamic pages to always display the most recent, updated value on every navigation using the Link component. This should occur regardless of how many times the user navigates to the dynamic page. Additionally, there should be network activity when navigating to a force-dynamic page or layout, ensuring the most up-to-date data is fetched and displayed.
Which browser are you using? (if relevant)
Chrome 112
How are you deploying your application? (if relevant)
next start, next dev
@francoromanol This looks like the issue you were seeing
@mikebuilds yes, this and yours. Thanks for the mention :)
Same with router.push() and navigation between updated pages.
Same problem here, but with programmatic routing. In a CRUD flow, when changing data and routing back to the list of data, I get the old content. My current workaround is:
router.refresh()
router.replace(url)
Is this intended spec in Next.js? Looks so critical in production application as it causes staleness easily. still happen in 13.4.8
My expectation based on the docs is that for a dynamic route like this, it should revalidate after 30s. But indeed, in our app during development, it is still serving the target page by the route cache instead of refetching new data. This makes making applications that depend on fresh data pretty difficult.
Can confirm this STILL happens in 13.5 after you deployed your project onto Vercel. No matter how specific I have been, force-dynamic, no store, revalidate 0 etc. it just won't
I'm suffering from the same weird behavior after updating an app from Next v13.4.19
to 14.0.3
.
https://github.com/vercel/next.js/assets/30658772/b26dcced-382a-4f53-a512-6b226145d123
The video shows the following:
- IDs in DB:
..., 583, 584, 585, 586, 587
, routeedit/587
- Delete selected id
587
, thenrouter.refresh(); router.replace('/');
🔥 ID587
is still shown - Click on
<Link href="/edit/586">
✅ ID587
not shown anymore - Delete selected id
586
, thenrouter.refresh(); router.replace('/');
🔥 IDs587
&586
are shown - Click on
<Link href="/edit/585">
✅ IDs587
&586
not shown anymore - Delete selected id
585
, thenrouter.refresh(); router.replace('/');
🔥 IDs587
,586
&585
are shown - Reload page
✅ IDs
587
,586
&585
not shown anymore
I have no idea why that is happening. The code in play is pretty simple and looks basically like this (a bit simplified):
app/page.tsx
export const dynamic = 'force-dynamic';
const Home: NextPage = async () => {
// Also tried without trpc -> Same behavior, so trpc is not caching anything here
const measurements = await api.measurements.getAll.query();
return <Measurements measurements={measurements} selectedId={-1} />;
};
app/edit/[id]/page.tsx
export const dynamic = 'force-dynamic';
const Edit: NextPage<EditProps> = async ({ params: { id } }) => {
const measurements = await api.measurements.getAll.query();
const editIdNo = parseInt(id);
return <Measurements measurements={measurements} selectedId={editIdNo} />;
};
app/_components/measurements.tsx
'use client';
export const Measurements: React.FC<MeasurementsProps> = ({ measurements, selectedId }) => {
const deleteMeasurement = api.measurements.delete.useMutation();
const router = useRouter();
async function onDelete(): Promise<void> {
await deleteMeasurement.mutateAsync(selectedId);
router.refresh();
router.replace('/');
}
return (
<>
<div>
<div>Id: {selectedId}</div>
<button onClick={onDelete}>Delete</button>
</div>
<div className="flex flex-col">
{measurements?.map(x => {
return <Link key={x.id} href={`/edit/${x.id}`}>{x.id}</Link>;
})}
</div>
</>
);
};
There's really not much to this & I'd be really interested why router.replace('/');
(or push
) to a page with export const dynamic = 'force-dynamic';
shows stale data.
Is this a bug or am I misunderstanding something fundamentally here?
Again: The same code worked as expected in Next v13.
Can we get a response from code owners please? This is a serious problem. There is currently no way for a page/route to guarantee fresh data on navigation.
For me, export const dynamic = 'force-dynamic'
was the solution by applying it to the page. I suspect for quite a few, the router cache could also lead to stale data.
https://nextjs.org/docs/app/building-your-application/caching#router-cache
I'm on 14.1.0 and can't get this to work. It seems like sometimes it work but its hit or miss.
export const dynamic = 'force-dynamic'
seemed to work for the first link, but then any links after that don't work for some reason.
If I instead do router.refresh(); router.push(href)
it doesn't seem to work at all, even with force-dynamic
.
The route will render - if I add logs to the server component they do render, but the UI doesn't update for some reason.
If I use a form with a server action that does a redirect, then it does seem to work properly, so it seems to just be an issue with client-side navigation requests.
Same issue here, I use "force-dynamic"
in pages and routes, AND "no-store" in fetch requests, AND prefetch={false}
in Link component and still don't get fresh data when navigating back to a page after a mutation. I'm using Next.js 14.1.3.
I think I'm running into the same issue. I would expect that export const dynamic = 'force-dynamic'
would force a data refresh on every page visit, regardless if it is via Link
or router.push
. Running the latest "next": "14.1.3",
.
I have the following components:
app/clients/page.tsx
import { getClients } from '@server/handlers/clients';
export const dynamic = 'force-dynamic';
async function ClientsPage({ searchParams }: ClientsPageProps) {
const clients = await getClients({ page: Number(searchParams.page || '1') });
return (
<Suspense fallback="Loading...">
<ClientsTable clients={clients} />
</Suspense>
);
}
app/clients/[clientId]/page.tsx
import { getClientsById } from '@server/handlers/clients';
export const dynamic = 'force-dynamic';
async function ClientDetailPage({ params: { clientId } }: ClientDetailPageProps) {
const client = await getClientsById({ id: clientId });
return (<>... render detail</>);
}
app/clients/[clientId]/edit/page.tsx
'use client';
export function EditClientForm({ client }: EditClientFormProps) {
const client = await getClientsById({ id: clientId });
return (
{client && <EditClientForm client={client} />}
);
}
import { getClientsById } from '@server/handlers/clients';
export const dynamic = 'force-dynamic';
async function ClientDetailPage({ params: { clientId } }: ClientDetailPageProps) {
const client = await getClientsById({ id: clientId });
return (<>... render detail</>);
}
modules/EditClientForm.tsx
'use client';
export function EditClientForm({ client }: EditClientFormProps) {
async function onEditClient(values: z.infer<typeof EditClientSchema>) {
await onUpdateClient({ id: client.id, name: values.name });
router.refresh();
router.push(`/admin/clients/${params.clientId}`);
}
return (<form onSubmit={onEditClient}>...render form</form>);
}
'use server';
export async function getClients({ page = 1, limit = 15, status }: i.GetClients) {
... fetching the clients with Drizzle
}
export async function getClientsById({ id }: i.GetClientById) {
... fetching the client detail with Drizzle
}
I've tried to simplify these code examples, but the code is also available at https://github.com/rnnyrk/rnnyrk-kitchen-sink/tree/main/apps/web/src/app/admin/(layout)/clients
As you can see the I'll use the new 'use server'
functions and try to force every page to be dynamic and fetch the data on every visit. Although as seen on the video below the data is not fresh on visiting the pages.
Do I misunderstand the implementation or is this the same bug as described by @Livog? The routing between most pages indeed happens with the Link
component. Do we have to replace everything with router.push
? Also adding the router.refresh()
the client component where the data is mutated doesn't seem to have the expected result.
https://github.com/vercel/next.js/assets/4519142/8bbc3422-31c4-4b70-bf72-2e7183afe691
I was running into the same problem. I was able to fix it by swapping router.push
and router.refresh
so that the refresh is called last.
Example:
router.push('/expenses')
router.refresh()
It seems calling refresh first has an effect on the route that is performing the redirect, rather than the page you're redirecting to.
@codeams Thanks for sharing. Not sure why i didn't try this myself, but this does seem to work indeed and results in fresh data! Would be nice is this specific order of calling router.refresh
is mentioned somewhere.
The problem was solved By reading the Next document from version 14.2.0 onwards https://nextjs.org/docs/app/api-reference/next-config-js/staleTimes
By reading this section, we will find that Next routes have a default cache of 30 seconds, even if it is SSR, to eliminate it, you can set your Next config as below.
Of course, it is written in the document that this method is temporary and may change in the future. Is it wrong to use it?
The problem was solved By reading the Next document from version 14.2.0 onwards https://nextjs.org/docs/app/api-reference/next-config-js/staleTimes
By reading this section, we will find that Next routes have a default cache of 30 seconds, even if it is SSR, to eliminate it, you can set your Next config as below.
Of course, it is written in the document that this method is temporary and may change in the future. Is it wrong to use it?
But seems like this will make any page force being dnyamic? Isnt there a way to make single page stateTime to 0?
The problem was solved By reading the Next document from version 14.2.0 onwards https://nextjs.org/docs/app/api-reference/next-config-js/staleTimes
By reading this section, we will find that Next routes have a default cache of 30 seconds, even if it is SSR,
Thank you, this default implicit 30 seconds stale time was making it very difficult to debug.
Navigating back and forth to a dynamic page holding a Server Component was not updating the content even when one of them was making a database mutation.
But be careful, settings staleTimes.dynamic to 0 only fixes it for navigation using Link. It doesn't affect navigation based on Back/Forward browser buttons. Calling router.refresh() on applying the database mutation fixed both cases in my situation (on Next 14.2.3). I also tried revalidatePath(...) with no luck.
Something I couldn't understand: why would anyone want a "force-dynamic" page to remain stale for 30 seconds, by default? 🤯 Well, it seems the default value is now 0 in Next.js 15. See https://nextjs.org/blog/next-15-rc#caching-updates