next.js icon indicating copy to clipboard operation
next.js copied to clipboard

Dynamic page not updating on subsequent navigations using Link component

Open Livog opened this issue 1 year ago • 10 comments

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

  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

Livog avatar May 05 '23 12:05 Livog

@francoromanol This looks like the issue you were seeing

mikebuilds avatar May 05 '23 13:05 mikebuilds

@mikebuilds yes, this and yours. Thanks for the mention :)

fprl avatar May 05 '23 14:05 fprl

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

pawelhoros avatar May 05 '23 14:05 pawelhoros

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)

arthurcohen avatar May 10 '23 22:05 arthurcohen

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

ellemedit avatar Jul 07 '23 02:07 ellemedit

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.

osdiab avatar Aug 09 '23 07:08 osdiab

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

kelvinthh avatar Sep 27 '23 15:09 kelvinthh

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.

CombeeMike avatar Nov 27 '23 23:11 CombeeMike

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.

maulerjan avatar Jan 30 '24 23:01 maulerjan

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

sLatzko avatar Feb 17 '24 00:02 sLatzko

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.

redbmk avatar Mar 08 '24 21:03 redbmk

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.

darioscattolini avatar Mar 14 '24 14:03 darioscattolini

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

rnnyrk avatar Mar 16 '24 08:03 rnnyrk

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 avatar Mar 24 '24 16:03 codeams

@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.

rnnyrk avatar Mar 25 '24 08:03 rnnyrk

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. Screenshot 2024-05-12 044845

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?

hossein-sarreshtedari avatar May 12 '24 01:05 hossein-sarreshtedari

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. Screenshot 2024-05-12 044845

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?

Emiltayeb avatar Jun 21 '24 13:06 Emiltayeb

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

Gwened avatar Jul 23 '24 18:07 Gwened