vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.83k stars 26.96k forks source link

Next13 caching fetched data even with the 'revalidate = 0' route segment #51788

Closed EvandrooViegas closed 6 months ago

EvandrooViegas commented 1 year ago

Verify canary release

Provide environment information

Operating System:
      Platform: win32
      Arch: x64
      Version: Windows 10 Home
    Binaries:
      Node: 18.12.1
      npm: N/A
      Yarn: N/A
      pnpm: N/A
    Relevant Packages:
      next: 13.4.8-canary.2
      eslint-config-next: 13.4.6
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.1.3
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true), Data fetching (gS(S)P, getInitialProps)

Link to the code that reproduces this issue or a replay of the bug

https://github.com/EvandrooViegas/outsystem-angola

To Reproduce

  1. clone my repo
  2. run npm install in the root of the cloned repo
  3. run npm run dev

Describe the Bug

Next13 seems to be caching the data coming from supabase even with the revalidate = 0 route segment.

Expected Behavior

Get the freshest data in every page request

Which browser are you using? (if relevant)

chrome

How are you deploying your application? (if relevant)

No response

jovanhartono commented 11 months ago

crazy that it's still an issue...

salman0ansari commented 10 months ago

this is so annoying

nisan1337 commented 10 months ago

This makes app router unusable anything other than static pages. There is always a hydration error in dynamic pages that force root to client rendering which is basically plain react.

ChoaibMouhrach commented 10 months ago

Using "router.refresh" worked for me but I don't like how my progress bar stoped working too

samcx commented 10 months ago

Hi everyone, will be taking a look at this!

It seems the original :repro: that provided is currently a 404 (not a :public-big: :repro: or doesn't exist anymore).

If anyone is able to provide a minimal :repro: , that will be great so we can confirm and fix this long-standing issue.

ChoaibMouhrach commented 10 months ago

@samcx How do we opt-out of client-side caching?

samcx commented 10 months ago

@ChoaibMouhrach I believe opting-out of client-side caching is related to the original issue above because the client-side cache is different from the cache used by fetch. The only way to currently do this is using router.refresh.

Can you clarify why you wish to opt-out of client-side cache?

ChoaibMouhrach commented 10 months ago

@samcx In my case, I have a sidebar with links. When I navigate between those links, I retrieve fresh data from the database. However, when I create a new item in the database and then navigate back to the page via a link component, I don't see the newly created item. This is an issue for me. The only solution I found is using a router.refresh, but I encountered some drawbacks, one of which is the loss of my page navigation progress bar.

samcx commented 10 months ago

@ChoaibMouhrach My apologies, I take back what I said earlier—revalidatePath is indeed another option for purging Client-side Router Cache.

In your use-case, you could use revalidatePath (https://nextjs.org/docs/app/api-reference/functions/revalidatePath) instead of router.refresh. Are you using a Server Action to retrieve fresh data from the database?

I believe this will not cause the loss of your page navigation progress bar.

ChoaibMouhrach commented 10 months ago

@samcx Yes I'm using server actions to get the data, I think I tried it before and it didn't work, I'll give it a shot then I'll let you know

ChoaibMouhrach commented 10 months ago

@samcx I just had the time to try it, unfortunately, it didn't work, I still get the old data.

samcx commented 10 months ago

@ChoaibMouhrach Thanks for testing!

That seems like possibly a separate issue with revalidatePath. Can you submit a new bug so we can take a look?

ChoaibMouhrach commented 10 months ago

@samcx, Thanks, I will

jack-szeto commented 9 months ago

hey it's been a while! I've been doing some testing and I think the issue isn't a problem anymore. Turns out everything works just fine when unstable_cache and revalidateTag are used correctly.

I've been experimenting and testing this in my own repo, nextjs-caching-with-prisma. If you've got some time, feel free to clone it and give it a shot.

To give you a quick rundown, here's what I've done. First, I put the caching and re-validating logic in the src/lib/post.ts file:

// ...
import { unstable_cache as cache, revalidateTag } from "next/cache";

export const getPosts = cache(
  async () => {
    return await prisma.post.findMany({
      // ...
    });
  },
  ["posts"],
  {
    tags: ["posts"],
  }
);

export const createPost = async (data: Prisma.PostCreateInput) => {
  const newPost = await prisma.post.create({
    data,
  });
  revalidateTag("posts");
  return newPost;
};

// ...

Then, I separated the server action and form validation logic in the src/actions/post.action.ts file:

"use server"; // server actions have to have the "use server" directive

export const createPostAction = async (prevState: any, formData: FormData) => {
  // validation

  const newPost = await createPost({
    // ...
  });

  return {
    post: newPost,
  };
};

Lastly, you could use a form to call the server action, like this:

"use client";

export async function PostCreationForm() {
  const [state, formAction] = useFormState(createPostAction, {
    errors: {},
  });

  useEffect(() => {
    if (state.post) {
      redirect("/posts");
    }
  }, [state]);

  return (
    <form
      action={formAction}
    >
      {/* ... */}
    </form>
  )
}

p.s. If you don't want to use a form, you could use useTransition hook to call the server action:

const [isPending, startTransition] = useTransition();
// ...
<Button
  variant="destructive"
  onClick={() => {
    startTransition(async () => {
      await deletePostAction(null, post.id);
    });
  }}
  disabled={isPending}
>
  Delete
</Button>
samcx commented 9 months ago

@jack-szeto Thanks for sharing!

In the meantime, I'll be closing this issue. If we issue arises again, feel free to create a new bug report so we can take a closer look!

6bangs commented 9 months ago

This is definitely not fixed; I've reproduced it with a minimal case here: https://github.com/6bangs/nextjs-bug

This is just npx create-next-app@latest with the following changes:

Link tag on the main page:

          Link to bug:&nbsp;
          <Link href="/bug/3" prefetch={false}>click here</Link>

Dynamic route with optional catch-all segment at app/bug/[[...id]]/page.tsx:

export const dynamic = "force-dynamic";

type Props = {
  params: {
      id: string;
  }
}
const BugPage = async ({params: {id}}: Props) => {
  console.log(`Bug page executing for ID ${id}`);
  return (
    <p>Testing.</p>
  )
}

export default BugPage;

Repro steps:

  1. Clone the repo and run it
  2. Click the link to the bug in the upper-left
  3. In the console, you'll see Bug page executing for ID 3
  4. Click the browser back button
  5. Click the link again
  6. The page is not executed again so no message is logged to the console.

No combination of dynamic or revalidate resolves the issue. I've also tried using revalidatePath without success.

IMO this ticket should be reopened rather than creating a new one; there are ~30 others who have run into the issue and a lot of useful comments.

samcx commented 9 months ago

@6bangs Thanks for sharing a :repro:! I've reopened the issue.

Will be taking a look soon at the :repro:!

jack-szeto commented 9 months ago

@6bangs I've been looking at your example and I have a few questions. I understand how it might be confusing when a page doesn't rerender despite having dynamic or revalidate set.

However, in your example repo, I noticed that nothing changes when you revisit the page. Shouldn't it be cached in this case?

Also, I'm wondering if there are any specific cases where you'd want a dynamic page that doesn't make use of the unstable_cache and tag functions? I'm just trying to see if there are any scenarios that I might not have considered.

6bangs commented 9 months ago

For my AI use case:

This is my situation, but the bug would be noticed for any asynchronous behavior that modifies the state associated with the segment.

samcx commented 9 months ago

@6bangs This is actually separate from the issue that was originally reported since there is no fetch() in your example—the export const dynamic = "force-dynamic"; will only affect fetch()` and will not affect the Router Cache. You'll see it log again after 30s. So your example is working as expected.

There will be an upcoming API (staletime) that will let you customize this, but we are still listening to feedback on this! We also need to improve the observability here, as it is not immediately obvious when the Router Cache was used.

6bangs commented 9 months ago

Yup, staletime would work for my use case. Any ETA on that? For now we're stuck with a hacky workaround (appending a cache-busting parameter to the URI).

run4w4y commented 9 months ago

Hello! I'm having a similar presumably server-side caching issue (I'm assuming so since the issue persists across different browsers and devices). I have a page for which I get the contents with fetch in a server component, Next version I'm using is 14.1.0, the route segment config for the whole layout is as follows:

export const dynamic = 'force-dynamic'
export const fetchCach = 'force-no-store'
export const revalidate = false

The issue I'm having is: when the content is updated on the backend (the one I'm fetching from in the RSC) the page does show the new content on hard navigation, but when the same page is opened using client-side navigation all I'm seeing is stale content. For now I just stick to using hard navigation for all of the links, but hopefully I could get some help with the issue

samcx commented 9 months ago

@6bangs No timetable for this yet but hopefully soon!

@run4w4y Can you file a new bug report so we can take a closer look?

run4w4y commented 9 months ago

@samcx Thank you for your response! I was thinking this might be related to the issue discussed above, I'm sorry if it is not. In that case I'll try to come up with a smaller repro tomorrow and open up another bug report

piotr-wieruszewski commented 9 months ago

I am having the same issue with next.js v 14.0.3. I am using a fetch GET request from a client side component and the response always has X-Nextjs-Cache: HIT even with hard refresh. It works fine when I use a dev build - this issue occurs in a production build only. The only thing which worked was router.refresh(). It seems that it is a router cache, but I thought that this cache will be used when using e.g. Link components, but not with a hard refresh of a page in browser. Is that correct behavior of a router cache?

mirano-galijasevic commented 8 months ago

@6bangs No timetable for this yet but hopefully soon!

@run4w4y Can you file a new bug report so we can take a closer look?

More than a month later, still no timetable for fixing this? This had been reported 8 month ago. Please let me know if you are going to fix this or not. I do not want to use 10 hacks to avoid this in different situations, just because somebody is dead set on doing things the wrong way. Just tell me if you are not going to fix this, so that I can move to some other framework, since you seem to be on the path to ruin this. Thank you.

samcx commented 8 months ago

@mirano-galijasevic As far as I'm aware, there were different issues being reported in this thread, and there was no :repro: given for the original issue post.

We can't reliably debug and fix and issue without a reproduction. Unfortunately, that's just the case here (according to the original post). There are other bugs and edge cases we're fixing constantly (ones that are older than 8 months or younger), so not saying we're not fixing this, and I'm fully aware of new notifications coming into this thread.

kevincobain2000 commented 8 months ago

Thanks for watching this issue @samcx yes there are different issues reported here while all of those can be fixed by fixing the revalidate to be transparent and work as it is said to work.

Which is

revalidate:0 0 means 0 - no cache.

And in support of @mirano-galijasevic - I feel your pain and just due to this issue, many times we had to choose a framework other than Next14.

piotr-wieruszewski commented 8 months ago

In my case the problem was that the generated route was static content: image In that route I only had a GET method defined and in this instance every fetch request to this route was cached regardless of my config (even with a cache boosting parameter). If I add there any dummy HTTP methods there like POST or PUT the build will generate this endpoint as dynamic and it works as expected. Or, I could use an edge runtime too.

samcx commented 8 months ago

@kevincobain2000 Not able to reproduce myself.

CleanShot 2024-02-29 at 13 23 55@2x

(/app/page.tsx)

export default async function Page() {
  const data = await getData();
  return (
    <div>
      <div>{JSON.stringify(data)}</div>
    </div>
  );
}

async function getData() {
  const res = await fetch("https://catfact.ninja/fact", {
    next: { revalidate: 0 },
  });

  if (!res.ok) {
    // This will activate the closest `error.js` Error Boundary
    throw new Error("Failed to fetch data");
  }

  return res.json();
}

(/next.config.mjs)

/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  reactStrictMode: true,
  logging: {
    fetches: {
      fullUrl: true,
    },
  },
}

export default nextConfig
kevincobain2000 commented 8 months ago

@samcx In your same example above you are refreshing the page and hitting the same url. That is why there is no cache hit.

If you do a soft navigation then it doesn't hit.

npx create-next-app -e https://github.com/nextui-org/next-app-template

page.tsx

async function getData() {
    const res = await fetch("https://catfact.ninja/fact", {
      next: { revalidate: 0 },
    });

    if (!res.ok) {
      // This will activate the closest `error.js` Error Boundary
      throw new Error("Failed to fetch data");
    }

    return res.json();
  }

export default async function Home() {
    const data = await getData();
    return (
        <section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
            <div>{JSON.stringify(data)}</div>
... 
and so on
 GET / 200 in 246ms
 │ GET https://catfact.ninja/fact 200 in 211ms (cache: SKIP)
 │  │  Cache missed reason: (revalidate: 0)
 ⚠ Unsupported metadata themeColor is configured in metadata export. Please move it to viewport export instead.
 ⚠ Unsupported metadata themeColor is configured in metadata export. Please move it to viewport export instead.
 GET /robots.txt?1709255277972 404 in 35ms
 GET /about?_rsc=acgkz 200 in 26ms
 ⚠ Unsupported metadata themeColor is configured in metadata export. Please move it to viewport export instead.
 GET /about?_rsc=13o5j 200 in 17ms
 GET /?_rsc=1mruw 200 in 15ms <--- 

Reproduce

  1. Open localhost:3000
  2. View the data
  3. Click About
  4. Click Home (You see same data and it doesn't make another API call)
samcx commented 8 months ago

@kevincobain2000 Just verified what you're seeing.

This is the same thing as mentioned above—the fetch isn't being cached, but there's a separate Router Cache that's caching in-memory. Sounds kind of unintuitive that that we're caching the fetch through the Router Cache that wasn't cached on the server, but that's because one is done on the Client versus the Server.

https://nextjs.org/docs/app/building-your-application/caching#overview

Also to clarify, this isn't something we just created for App Router—there also was a similar Router Cache in Pages Router. You just wouldn't come across the need to bypass/bust that cache (e.g., in the rare case you on-demand ISR, and want to bust that route's cache too on the client, not really necessary).

Besides the hacky solution above, I think what we need is the Staletime API which was mentioned above, and a more ergonomic way to possibly opt-out the Router Cache. :frog-eyes:

kevincobain2000 commented 8 months ago

@samcx Thanks for verifying.

Understand what you said, you have put it nicely.

Yes, there needs to be a simple setting that can turn this cache on, off, or for duration. The reason it has become a big issue, is that most users believe that revalidate was for this purpose. And not all users want a router cache, especially when navigation expects data change - trading apps, admin panels, or any other case.

BrentJohnsonNz commented 6 months ago

I've just chased down an error where SSR data fetching ceased for a specific simple URL. My error was caused by the router keeping an entry permanently in the router.js "Server Data Cache" (this.sdc/inflightCache). The error is caused by executing multiple rapid "router.push(" calls to pages with SSR. If the first router.push data fetch call is cancelled at exactly the right time (just before it's deleted from the inflight cache) the data gets stuck in the inflight request cache and will be served for all subsequent navigations to the SSR page.

The symptom for the error in this scenario will be a client-side console error "Loading initial props cancelled".

The "inflightCache" appears to only be active for production builds. In my case it only manifested when running end-to-end tests against staging that were navigating very rapidly between pages.

samcx commented 6 months ago

@kevincobain2000 Staletime API (experimental) is available now → https://nextjs.org/docs/app/api-reference/next-config-js/staleTimes.

In the meantime, I will be closing this issue for the time being—feel free to create a new bug report if this is an issue un-related to router cache.

kevincobain2000 commented 6 months ago

Thanks @samcx I ll check this out.

6bangs commented 6 months ago

@samcx, I see this:

The dynamic property is used when the prefetch prop on Link is left unspecified. The static property is used when the prefetch prop on Link is set to true, or when calling router.prefetch.

What about in my example where prefetch is false?

samcx commented 6 months ago

@6bangs good callout! I have created a :pr: to update our :docs:https://github.com/vercel/next.js/pull/65252.

6bangs commented 6 months ago

Great, thanks!

github-actions[bot] commented 5 months ago

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.