Giter Site home page Giter Site logo

Comments (10)

PaulWild avatar PaulWild commented on September 13, 2024 1

Thanks for the advice! We will have to have a think about how we might optimise things. My mind also went straight to a promise with a setTimeout so very glad you suggested that too! It may actually improve perceived speed.

I did go back to my v15 version and yeah I realised that it wasn't working as originally thought. I think the force-dynamic was overriding the cache directive.

from next.js.

ztanner avatar ztanner commented on September 13, 2024 1

Sounds good -- yeah, I think the handling in 15 is just masking the behavior because the dynamic rendering is forcing the fetch to hit the origin; React will see the pending data fetch and flush the HTML before even getting to the for loop. We're triggering the same behavior with the await setTimeout but with the added benefit of not actually opting out of the fetch caching behavior.

Going to close this for now as I don't believe this is actionable on our side, but I'm still happy to answer any questions or provide additional details about my previous suggestions. I've relayed this issue to the React team in case we can get a fix there that's backportable to v14.

from next.js.

Harshkt214 avatar Harshkt214 commented on September 13, 2024

I can see this bug in the code you provided. However I tested it and fixed in the next15.0.0 canary release.

from next.js.

PaulWild avatar PaulWild commented on September 13, 2024

This is correct and I did mention that in the issue itself - however given that next15 and react19 are both in RC only and don't have stable releases I was hoping that back fixing this would be considered.

from next.js.

ztanner avatar ztanner commented on September 13, 2024

Hi @PaulWild -- I believe this was fixed (albeit indirectly) by #64145. We made it so that force-dynamic also sets a default on any fetches on the page to not be cached.

Previously export const dynamic = "force-dynamic" was not influencing fetch cache behavior. So in this case, while the page was marked as dynamic, the fetch was cached. And in v14, fetches that are cached are currently buffered rather than streamed (this was also addressed in v15 via #68447)

If you update src/app/page.tsx to also have:

export const fetchCache = "force-no-store";

Then it'll behave as it does in Next 15 (ie, it won't cache any fetches, which is the intended behavior behind force-dynamic).

However if you want to preserve fetch caching semantics while keeping force-dynamic in place, then I'm working on a backport here:

from next.js.

ztanner avatar ztanner commented on September 13, 2024

Hi @PaulWild, upon further investigation with the team, if your reproduction is reflective of what you're doing in your app, it's because it's doing work that's blocking the main thread causing the React server render to not be able to flush the initial HTML (which contains the loading state).

The reason it works on the first request is because the request isn't cached, so React sees the pending fetch, suspends, and then immediately flush the loading state to the client. On the second request, the fetch is cached, so the fetch immediately responds (so no need to suspend) but then the render is blocked by the loop you've created here. React will be stuck computing the work in this for loop before it can get a chance to flush.

Is there any way to move this work into an async task? If not, your best bet might be to disable fetch caching here as React will see the pending fetch and skip the main thread blocking work until after the HTML is flushed.

from next.js.

PaulWild avatar PaulWild commented on September 13, 2024

Hi @ztanner thanks for taking a look at this for us. Let me give a little more context that might explain some of the reproduction:

We are an ecommerce site. The problem area/components are in our header which we load in the layout.tsx. My thinking around adding to the export const dynamic = "force-dynamic" was to indicate that our header is opted out of static generation because we read from the headers. In retrospect maybe a detail that wasn't required.

In terms of how realistic the example is, it's not 100% as I think my example implies that we are doing some expensive calculation which isn't true. One example of what we are doing is loading our category structure to populate our aisles menu. The components are more akin to

const problemComponent = async () => {
    const someChangeableButCachableData = await fetch(data);
    const slightyProcessedData = minimalTransformation(someChangeableButCachableData)
    
    return ( 
     <FairlyComplexJsx data={slightyProcessedData} />
    )
}

An async task would make sense for an expensive calculation, but this feels more like a standard react component, maybe with a bigger structure than usual.. The difference might not be massive but it is impactful as it is currently effecting our web vital score. It can take a time to first byte from a reasonable 1-200ms to plus 1 seconds.

On the second request, the fetch is cached, so the fetch immediately responds but then the render is blocked by the loop you've created here. React will be stuck computing the work in this for loop before it can get a chance to flush.

This to me is the slightly surprising behaviour as I would have expected the look up to the data-cache to be fast but still involve a pending task and that initial flush.

from next.js.

PaulWild avatar PaulWild commented on September 13, 2024

That was unfortunate timing, having the metion of the backport fix being crossed out as I was replying. My hopes were a little higher before seeing that :D

from next.js.

ztanner avatar ztanner commented on September 13, 2024

Hey @PaulWild -- I crossed out the backport primarily because the backport didn't fix the issue. I tried it against your repro and it still behaved the same way. Sorry for the false hope there!

This to me is the slightly surprising behaviour as I would have expected the look up to the data-cache to be fast but still involve a pending task and that initial flush.

Indeed, though this is a current limitation of React. I think you're looking for a future Suspense capability which is Suspense for CPU bound trees. At the moment Suspense is primarily optimized for IO (things like data fetches). There's an experimental property but it's only in Fiber (React's client renderer) and not Fizz (the streaming server renderer). Until then there's not much I can think of to do without tricking React into flushing the server HTML earlier. For example, if you add a await new Promise((resolve) => setTimeout(resolve, 500)); before your fetch, that'll give it enough time to flush the initial HTML while streaming the computationally expensive work.

from next.js.

ztanner avatar ztanner commented on September 13, 2024

Also just to add another option... you might be able to leverage ISR to pre-generate the page at build time. And then revalidate the page when you know the data is changed (or on an interval) to signal when the page should be updated. That'll ensure the page is always a cache HIT without needing to perform any work when the user requests the page as the work will have already been performed by the time the request comes in.

from next.js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.