-
Notifications
You must be signed in to change notification settings - Fork 27.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[NEXT-1187] Link navigation with loading.tsx is not instant for dynamic pages #43548
Comments
That is how it works, in order to do the loading state, your browser has to first make the request to nextjs server, then nextjs will first return the loading state, and after resolves your page. The thing is that in need, it is even slower that in production, because nextjs do a bigger amount of work. You could try to run it in production with All of this is part of streaming rendering : https://beta.nextjs.org/docs/data-fetching/streaming-and-suspense#concepts |
that totally makes sense, that's why i've added throttling for testing on the client and delay on the api side. but isn't it a job of page prefetch feature to get the loading state before navigating to it? More than that Next.js docs say that loading.tsx should be instant compared to custom Suspense boundaries
I mean, I understand why it works the way you described for custom suspense boundaries, but it's not obvious from the docs that the loading.tsx should work the same way |
As i said, for api endpoints that take some time to fetch (for example 5s) it would always take the same amount of time to show you the loading UI, the time it would take you to show the loading state is the time your browser to connect to the server. So if your browser takes 1s to connect to the server, the loading ui would show after 1s and not after 5s, so instead of waiting 5s to show your page, you get a loading state sooner. This is better for when you reload the page for ex. And you're right, with prefetching it should be instant, since it has already the data... but it seems that when you have a loading state it prefetches twice ? 🤔 Enregistrement.de.l.ecran.2022-11-30.a.12.13.09.movBut prefetching isn't also a silver bullet. Take the case when your page hasn't finished prefetching the page, it would still take 1s before showing you the loading state and i think even if your page where static it would take the same amount of time : the time it takes for your browser to connect to your server and your server to serve you the static page. Showing a loading state on the client would definitely make it faster, but only if you don't issue a navigation : like you fetch on the client without navigating to another page i think... 🤔 |
Still happening in 13.1.3-canary.0 |
I think that the loading.tsx should be instant without any network calls. |
Absolutely agree with all problems raised in this issue, this is the only thing stopping our project from migrating to App Router - navigation creates just horrible experience with sometimes up to 300ms wait time before even the loading streams in (I know this can be solved by moving the server closer e.g. on edge, but that's a different story). Other side of the problem is that there's no way to display any other loading state on the UI while loading hasn't streamed in yet - you are forced into this UX. I truly don't understand the reason behind this: if With |
@tonypizzicato , I've resolved same issue with group route. |
for me when I added a root loading.tsx file it was picked immediately after I started the navigation to another page while the loading.tsx file inside the other page's folder wasn't picked and I was getting the same behavior as @tonypizzicato before adding the root loading.tsx file. |
it's worth mentioning the above behavior was happening with next@13.2.4 but when I upgraded to 13.3 it was no longer happening |
@mhesham32 , did you use client component in 13.3? |
Yes the navigation is triggered from a client component to a server page |
We are also experiencing this issue, and it is still present on v13.3. The concept of "loading" for the end user means anything that is delaying their request, whether it's due to server processing or network latency. There should be no distinction to the end user on technical merit. This is purely a UX issue. End users need immediate feedback, whether that's the data they've requested or feedback to say their request is being handled. Currently, this implementation fails to do this. This is a core part of the age-old business arguement for retaining impatient users that we work ever so hard to get, that cannot be overlooked. The fact that the on-request loading feedback needs loading on-request makes this implementation feel like one step forward and two steps back. We are very grateful for NextJS and we do appreciate that the AppDir is still in beta, but please make sure this UX issue is fixed before release. |
It seems that to make loading instant you have to define it on the parent folder of the dynamic route like they do in App dir playground repository. You can also use route groups like in @devdreamsolution comment : #43548 (comment) |
It works with #43548 (comment) solution. I will postpone the use of app folder until will become stable. |
Glad to have found this issue for a partial fix. Hope the behavior changes for manual Suspense so that the fallback is shipped to the client and run at the start of the transition. That's the UX users are going to expect. I'm not sure what the behavior was pre app directory with getServersideProps. But in other isomorphic React projects I've worked on you could always show a loading state on the component level (not saying you couldn't in Next.js in a useEffect or getInitialProps, but my understanding is with GSSP it could only be done on the page level). |
@unleashit my understanding is that pre-app router the loading fallback could be controlled by the user with router events (https://nextjs.org/docs/api-reference/next/router#routerevents) there is even an example used for showing a loading indicator : https://github.com/vercel/next.js/blob/8050a6c8e056efb336f15f6fa5be789cd040574d/examples/with-loading/pages/_app.tsx |
Hey thanks for the reply but I'm pretty sure those router events were only for the full page (and not at the component level) as I was mentioning. I'm pretty sure Suspense fallbacks are intended as the solution, but I don't think it's implemented as well as it could be. It definitely an improvement over those router events, because you can control it on the "route segment" level with a child loading.tsx. That's better than it was, but better still would be to support the same behavior (fallback shown immediately on a client transition) in a manual Suspense fallback. |
@unleashit those events where for page changes (or URL changes I think ?), it is in a useEffect so you can use it even at component levels, it is basically the same as I would not say that Suspense is implemented badly,but what it seems is that the client router is not aware of the I also recently inspected the RSC payload on navigation, and have seem to find that a I will try this again and post an update with my findings later. |
Yeah I agree with most of that and was only using the router transition as a reference for how things were UX wise with the NextJS experience. Suspense is an upgrade over that because it has more granular support for loading content from the server, vs only the top/page level for getServerSideProps. But I think there definitely are some issues that people will agree on and will hopefully get ironed out. As someone mentioned above, as a "user", I would expect either the content or a loading transition to appear immediately after clicking a link, button, etc. Not after a delay, whether the delay is caused by the network or a data request somewhere, because the distinction doesn't really matter to them. The experience is different with a loading.js and a manual Suspense. In the docs, the former is basically an automatic suspense for a page, but the loading behavior isn't the same. As an example, you can go to: https://jasongallagher.org/portfolio/tatteasy and click next/prev buttons. You should see a loader (a nested loading.tsx) just between the header and footer (* at least in Firefox, see below). But then the behavior isn't the same with manual . I have one on the home page part way down the page. You can test by clicking on "work" in navbar. If you refresh with throttling on in dev tools, notice that the checkerboard of images never shows a spinner. The site is statically exported, but I've also tried this in SSR mode (and these are server components that query data) and the behavior is the same: almost never a spinner, even with throttling. In some of my testing while running in SSR mode, I did sometimes see a blip of a fallback in some places, but not in the way I would expect. IMO for a good UX, the fallback state should last for the full time that the HTML markup can't be shown in the browser regardless of why. I assume a static/SSG site doesn't show a suspense fallback because the RSC output is statically available. Yet the spinner works (albeit inconsistently) in Next's implementation of loading.js. It would be much nicer if the fallback was displayed consistently no matter the environment.
|
@dumbravaandrei22 maybe try to put a dummy Layout at |
@unleashit Normally the You could try to add an artificial delay to your fetches of for ex 1 to 2 seconds, to see it better. |
Yeah, that's exactly what I suspect the intention is but I don't think it's good UX. A better version of the Fallback would start running on client as soon as the dom is available, and/or as soon as a route transition begins if inside a layout. Then ending either as soon as content starts to paint, or when its finished (probably best). We can disagree on this, but there's no reason why it can't be implemented this way... and no reason I can think of that it wouldn't be a better user experience than a loader that appears before and/or after a pause. It already works like that in loading.tsx for the most part (seemingly with bugs), which is why you see the transition between pages on site that's static. I get that it appears to work more nicely in situations where the server component has more work do (nice work on your site). But in other situations where the reason for the delay is network, browser parsing, etc, especially a page that already has content... it shouldn't matter what is causing the delay when it comes to fallback behavior. With client components we can use useEffect and have that control. But right now with server components you only have the behavior the Suspense fallbackoffers. Interesting what you originally pointed out about the prefetched text files on my site. Just noticed it. I think they're just the static RSC output which for some reason on a static exported site have a txt extension (normally I think they're rsc). But I could be wrong! |
For link navigations manual For
I have an exemple without prefetching which shows the loading spinner only after a delay then show them instantly on subsequent navigations I also have an with prefetch=true that shows them instantly
I think the logic of the react team is that Suspense gives you the power to show the loading spinner whenever you like, by default it does not do exactly that, but you can take over this by using a transition, i've done something like that here : https://parallel-routes-modal-next.vercel.app/boards_search/oWNLDSH2nQTVrDcWS3zg7o You can see here that the suspense boundary show up instantly because i suscribed to the However this example is very hacky as i have to opt out of the link component entirely : https://github.com/Fredkiss3/parallel-routes-modal-next/blob/c9443997ebc8ccaadc03c0a68a2e8ab17ed1f260/src/app/boards_search/%5Bbid%5D/card-list.tsx#L41 I think the canonical solution here is the loading.tsx file, you just have to make sure to place them in the right place and add prefetching. |
I think it comes down to justifying behaviors because of what may seem to make sense "technically". But with user experience, you do your best to achieve behavior based on what the user would expect and prefer. If the cost is high, you sometimes have to compromise of course. I'm not going to have one of the first tickets to Mars for example. My UX is probably going to be going camping instead. But since I don't think this is quite rocket science and Sever Components are still new, it seems like a good time to focus on what the UX of it should be. I think the acceptance of this is a Next thing... coming from the fact that getServersideProps was accepted as the norm even though it was always bad UX. There were always better ways to handle client route changes via lazy loading such as this, which allowed finer control of the experience that what GSSP offered. RSCs are an upgrade over both methods, but the fallback situation is still less than ideal. It may work fine in some situations, but in others it doesn't. There's no reason why React and Next can't fix it if they want. Just show the fallback as soon as the route transition begins and stop showing it when the rsc is ready to hydrate. I'm not an expert on Server Components, haven't watched any two hour videos and don't currently have time, but I just can't image what the technical barrier to that could be. |
You are totally right about the expectations of user experiences. I don't know if it is "fixable" by the Next or React team, it doesn't seem like it for I'm totally in the same length as you about what we can do to make UX better. My suggestions (with informations I have today) : 1 - use In a live video with Ben Holmes (the whiteboard guy who works at Astro) & Dan Abramov, they made a little app with a Search list, they did the same as my second suggestion : https://github.com/bholmesdev/simple-rsc/tree/main/app |
I don't see why it isn't fixable. For each suspense, add a HOC to the client bundle that loads the fallback immediately on mount. Then end it once it has children. Seems like loader.js is already doing that (although not perfectly), and probably not far off from a normal suspense. I would think this is probably something for Next rather than React to do. Of course I'm being an arm chair directory who hasn't taken the time to look at the code, but if this issue is for some reason unsolvable than in my opinion is is a big mark against server components. I appreciate the ideas! For my little project I won't sweat it too much. I've looked at useTransition and it seems more for not blocking the UI when doing some work? I'll have to check that out at some point. |
There is a PR about a potential solution for this : #49077 TLDR : next will now prefetch pages content until it finds a Edit : Tested it on the latest canary and it works : https://github.com/Fredkiss3/next-13-loading-prefetched . For suspense there is something interesting : if the page is prefetched (either on hover on DEV, and by default on production), it will eagerly run and return the suspense boundary, and if suspense resolves before you navigates to that page, you won't see the fallback but the page directly. this one is pretty cool. Updated this example which is more complete : https://github.com/Fredkiss3/parallel-routes-modal-next |
Yes and
Both of these are the bottom line, and what I (and some others) tried to argue months ago.
This would be the opt-out method, which be a big improvement since it would fix more than this issue and give the dev a choice on how they want to balance performance (faster initial load vs faster transitions without relying on prefetch). Personally I prefer the (traditional) opt-in approach to code splitting, which is really as simple as a dynamic import per component (or page) you want to code split/lazy load. |
Why would you want this to be configurable? Loading states should be instant, not after a fetch to whatever server. Its just bad UX. |
I wanted to clarify a bit: the fetch happens pre-emptively when the page loads, not when you navigate. Can anybody send me a repro where their navigation is slow? I would love to investigate. Any datapoints would help us evaluate or not if we should change that part of the architecture. |
@jvandenaardweg Configurable in some way (either as opt-in or opt-out of bundling) I think is important. If you automatically bundle every possible fallback/loading.js in an initial load, it could get bloated in some cases. But in my comment above, I was thinking about the bigger picture of having more control over bundling in general.
@feedthejim I think you're referring to prefetching. Prefetching is unreliable because it can either be turned off or the user can click a link before the response(s) resolve. |
It happens when you navigate, so clicking a link to a different page where that page is expected to show a loading indicator. So with a loading.tsx or a Suspense fallback. The above gif of @arackaf shows exactly what happens. He also has a reproduction repo. To be more precise on where to look in that gif; its about the delay between the click and until the loading indicator is shown. Thát delay shouldn't be there. |
@arackaf In your reproduction, it seems that you're running the app in development mode ( Arc.2023-10-10.at.09.50.15-converted.mp4In production, routes are automatically prefetched as they become visible in the user's viewport. Prefetching happens when the page first loads or when it comes into view through scrolling. |
The fact that prefetching is not enabled in development is a common source of confusion on how this works. @feedthejim why is it disabled in development? |
So I guess people in the Next.js community feel that prefetching is a good solution. But a) it can be turned off in a Link and b) the user can act faster than the response comes in. This is in production. Lastly, it costs $ especially when serverless. |
@unleashit I'm asking for a real repro where I don't have to simulate 3G conditions to notice a delay @IGassmann we disable prefetching in dev because it would force the compilation of all the pages that you're linking to from your page. Please do note that if you do click a link in dev, we simulate the prefetching behaviour however by fetching the loading state first and then the rest. |
@feedthejim, this is a challenging request. Users may encounter poor internet connections, such as when using the app in the subway. For example, a user might load a page right before entering a tunnel and then, while in the tunnel, click a navigation link, which could result in no loading indicator. How do you provide a reproduction for this situation? |
@feedthejim sorry, I don't have time at the moment. I do have a non-minimal statically exported repo I could show you that never shows the loaders: https://github.com/unleashit/portfolio-v4/tree/master/app/portfolio. They were actually working at some point during the early app directory development, but the behavior changed several times and now nothing. I think most people here are talking about SSR, where the loader can sometimes be delayed like in @arackaf s demo. Again, I used to get this behavior during the 13 beta for my static site, but now nothing. But I've also seen it in SSR sites.... just don't have any at hand to post. You'll need to throttle it though to see, unless you want to take your laptop maybe a block from the house/office until the connection is bad ;) |
The issue also happens in production environments. The same RSC background fetch/prefetch call delays rendering of a loading indicator.
I see the prefetching happen for the pages that are linked to. But the same fetch happens when a user clicks the link to that page.
Now, nothing happens on the screen until that fetch resolves. Not even a loading indicator. Nothing. And this is exactly the point we try to make by throttling your connection. Not everyone has sub 50ms connections to the server at all times to get a server respond if we should even show a loading indicator...
I tried making a demo video of the issue. But it's giving too much away of my environment, including url's that I don't want to be publicly available. But it's the same issue as posted in the GIF a few days ago in this thread.
Also, this is worrysome yes. I already see a huge increase in calls that count up to my usage in Vercel, just by using the App Router. |
I checked @arackaf 's repo and environment. This shows the same issue i'm also seeing: Demo: https://next-instant-loading.vercel.app/ |
@jvandenaardweg On Vercel Locally Edit: The app on Vercel is actually still pointing to an old commit (arackaf/next-instant-loading@f7de8a2), which still uses Next.js 13.5.2. That explains the discrepancy. |
You sure @IGassmann ? The Preview environment on GitHub shows a succesful deploy of "Update Next" as the last succeeded deployment: https://github.com/arackaf/next-instant-loading/deployments/Preview , which points to 13.5.4: arackaf/next-instant-loading@2c90fca "Update Next" is the last commit on the feature/instant-loading branch he was pointing to: https://github.com/arackaf/next-instant-loading/tree/feature/instant-loading |
@jvandenaardweg that successful deployment in the Preview environment is hosted at https://next-instant-loading-o7zx8tsok-adam-rackis.vercel.app/, not at https://next-instant-loading.vercel.app/ On that deployment, we can see the issue fixed: Arc.2023-10-10.at.15.35.39.mp4 |
Hey all - sorry, yeah the original prod repro may not be on the latest commit. I’ll take a look later and update. I’m glad to hear it’s instant with a prefetch, but I would still feel much much much better if it was just included in the JS bundles for a number of reasons articulated above by others. Instant loading state should not depend on a prefetch. |
Thanks for clearing that up @IGassmann ! I'll have to take another look at my code then. Because i'm on the same latest Next version but dó see the delay. I'm just totally lost now what's causing it. |
I guess my issue lies in the use of Using Vercel's app router demo to demonstrate: No issue here when using a generic loading.tsx file: https://app-router.vercel.app/loading But the issue is here "Streaming with Suspense": https://app-router.vercel.app/streaming When you throttle the connection, using loading.tsx instantly shows a indicator. Which is good. But using the same throttled connection on the Streaming With Suspense demo prevents anything from being shown until a certain point. Not really clear when that exactly is. Both also tested with the latest NextJS version locally and with a deploy on Vercel. So the issue lies in the use of edit: issue about this: #54667 |
Ok I've updated my original repro with the latest Next version, and the instant loading state does show instantly, given the prefetch. I do still think loading into client bundles would be better, but it does look like this is essentially fixed and working as designed. |
It may be working as designed, but it doesn't work. Relying on a lazy loaded response can and will lead to weird glitches with loaders. Separately, I would expect loading.js (and IMO suspense fallbacks) even in static/SSG sites to show the fallback if no RSC for the page or component has yet been downloaded, which can happen when prefetch prop is set to false. It's hard to not get the feeling that Vercel has planned for a heavy use of prefetching for their own concerns and we has devs should not just brush this under the rug and give them a pass. We are more or less forced by the industry to learn and use this tool, but it can be made better if you aren't afraid to give them a little pressure about it. Prefetching all the things is a terrible idea. Worrying about adding 10-20k to a bundle is good, but never without consideration about the overall balance. |
I updated my project to the latest version of Next. In fact, loading has improved along with page change speed, but it is not yet in immediate development. I used this command: npm i next@latest react@latest react-dom@latest eslint-config-next@latest Do I need to do anything else to make page switching instantaneous? |
@murilo1008 I'd create a simple repro and upload to Vercel. Feel free to fork mine above |
I want to clarify how loading UI works in Next.js. Routing in the App Router is "server centric". We need a network roundtrip to the server when doing route transitions. The client doesn't know what loading state to render, until it communicates with the server. When we prefetch, we can retrieve both the route to navigate to and the loading UI. Prefetching is what makes navigations feel snappy. If you prefetch the loading UI which does not have data fetching, then you can "instantly" show this loading state, followed by the dynamic content on the page being streamed in after. This is enabled through React Suspense and streaming server-rendering as part of the App Router. We don't believe an alternate solution where you include all of the routing variants on the client is feasible or the best solution for Next.js. For example, consider a loading state which is There's still opportunities for us to improve the experience of transitioning between loading states. For example, we can optimize further the de-duplication of data. Thanks for the reproductions here, as they'll help us dig in further and find places to optimize. |
Does this include loading states from already loaded layouts? I have a single |
Thanks for explaining the thinking. I also just came across Deep Dive: Caching and Revalidating which is also very helpful and appreciated, The docs (at least at the point I was reading them several months ago) don't go far enough into some of these subjects or explain the thinking behind them. Personally, I wish Next.js kept to its original roots of Isomorphic code with getInitialProps. getServersideProps started this whole thing of letting Next mange client side cache which usually results in excess server requests. That I think defeats the main advantages of SPAs, which is offloading some of the expensive work to the client and the clean separation of backend concerns. I love most of the other things that got added (SSG, even the app directory and RSCs "in the right situations"), but getInitialProps was actively discouraged which IMO is mostly why the tide started turning back to the server. Not just with Next.Js, but that's when Remix, etc. started coming out. Anyhoo, it appears the same foundation is why we get loader glitches sometimes.
I can imagine that being possible, which is why it would be |
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. |
Verify canary release
Provide environment information
Which area of Next.js is affected? (leave empty if unsure)
App directory (appDir: true), Routing (next/router, next/navigation, next/link)
Link to reproduction - Issues with a link to complete (but minimal) reproduction code will be addressed faster
https://github.com/tonypizzicato/next-13-loading
To Reproduce
yarn install && yarn dev
localhost:3000
in the browserDescribe the Bug
When you have a dynamic page with
loading.tsx
the route change and showing the loading animation are instant only for the page, which was freshly loaded. For other dynamic pages it hits the server first, then shows the loading statenext-13-loading.mov
Expected Behavior
Instantly navigate to the desired dynamic page and show loading component
Which browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
NEXT-1187
The text was updated successfully, but these errors were encountered: