next.js: Dynamic page not updating on subsequent navigations using Link component

Verify canary release

  • 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

  1. Hard navigate to: http://localhost:3000/dynamic
  2. Update the input and click submit (this will save the data to the body.json in root)
  3. Click on Home
  4. Click on Dynamic
  5. You should now see the updated value
  6. Update the value again to something else
  7. Click on “Home” again
  8. 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

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 14
  • Comments: 15 (1 by maintainers)

Most upvoted comments

Same with router.push() and navigation between updated pages.

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.

Is this intended spec in Next.js? Looks so critical in production application as it causes staleness easily. still happen in 13.4.8

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)

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.

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.

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, route edit/587
  • Delete selected id 587, then router.refresh(); router.replace('/'); 🔥 ID 587 is still shown
  • Click on <Link href="/edit/586"> ✅ ID 587 not shown anymore
  • Delete selected id 586, then router.refresh(); router.replace('/'); 🔥 IDs 587 & 586 are shown
  • Click on <Link href="/edit/585"> ✅ IDs 587 & 586 not shown anymore
  • Delete selected id 585, then router.refresh(); router.replace('/'); 🔥 IDs 587, 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.