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

Parent `loading.tsx` UI flashes prior to nested `loading.tsx` UI

Open bmcmahen opened this issue 2 years ago • 11 comments

Verify canary release

  • [X] I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 21.6.0: Sat Jun 18 17:07:22 PDT 2022; root:xnu-8020.140.41~1/RELEASE_ARM64_T6000
Binaries:
  Node: 16.14.2
  npm: 8.5.0
  Yarn: 1.22.19
  pnpm: 7.13.4
Relevant packages:
  next: 13.0.5-canary.4
  eslint-config-next: 13.0.5-canary.4
  react: 18.2.0
  react-dom: 18.2.0

What browser are you using? (if relevant)

Chrome

How are you deploying your application? (if relevant)

No response

Describe the Bug

Perhaps this is me not fully understanding the intended behavior, but when I have nested loading.tsx files, often the top-level loading will show when navigating from a top level route to a nested route in a way that feels janky / undesirable. Take the following app dir as an example:

CleanShot 2022-11-21 at 17 28 48@2x

In this scenario, I have a home page.tsx which includes links to jokes/category. The only fetches I make are in the root level page.tsx and jokes/[id]/page.tsx. When clicking a link from the home page to the nested joke, first the root loading.tsx is rendered prior to the loading.tsx defined in the jokes/[id]. This causes an undesirable UI, with multiple loading states flashing, and it's not clear to me why the top level loading indicator renders if that part of the UI has already rendered / loaded.

You can see the behavior here:

https://user-images.githubusercontent.com/1236841/203194699-b61ad08d-8ce7-4eb8-b8ca-fc3ee87f94ed.mp4

Step by step, you can see that "Loading Root" renders briefly before "Loading nested" which renders before the requested fetch has finished.

Home (app/page.tsx):

CleanShot 2022-11-21 at 17 43 33@2x

Clicking a link first renders the root loading.tsx: CleanShot 2022-11-21 at 17 43 40@2x

And then renders the nested loading.tsx: CleanShot 2022-11-21 at 17 43 43@2x

Before finally rendering the nested route: CleanShot 2022-11-21 at 17 43 48@2x

Expected Behavior

When clicking a link from the home page to jokes/[id] I'd expect only the loading UI defined next to [id] to render.

Link to reproduction - Issues with a link to complete (but minimal) reproduction code will be addressed faster

https://github.com/bmcmahen/next-13-nested-loading

To Reproduce

Create a loading.tsx file at the root level of your app directory. Fetch data in the root page.tsx file. Create a nested loading.tsx file in jokes/[id]. Create a link in the root page.tsx file to jokes/id. Run the dev server or build for production / host, and click a link. Notice that the root loading.tsx file renders prior to the jokes/[id] loading tsx.

NEXT-2282

bmcmahen avatar Nov 22 '22 01:11 bmcmahen

will there be any fix for this, or is there another way of doing this? I am experiencing this too, but only for dynamic nested pages. and it creates a really bad user experience, especially when my parent's page skeleton differs a lot from the child's skeleton.

martinso95 avatar Mar 13 '23 10:03 martinso95

I'm also curious if there's a workaround for this, I'm having the difference in skeletons issue too like @RisKakaN described

koenoe avatar Apr 04 '23 21:04 koenoe

Only workaround we've found is eliminating loading states everywhere but the leaves of our app. Feels pretty slow but not broken.

psugihara avatar Apr 25 '23 22:04 psugihara

still havent found a proper workaround for this. hopefully there will be a fix.

martinso95 avatar Apr 28 '23 16:04 martinso95

I had the same experience and wonder if this should be considered a bug. Hopefully someone from the next team can have a look soon.

jamiter avatar Jun 19 '23 14:06 jamiter

Still present in 13.5.5-canary.2

Loading ui is so close to perfect, but for me this flash is a dealbreaker for anything but leaf routes.

@leerob @balazsorban44 any updates regarding this? I could try to fix it myself if someone could point me in the right direction.

https://github.com/vercel/next.js/assets/11774195/521d3513-91ac-4632-ad51-eeea0a6520a2

LavransBjerkestrand avatar Oct 05 '23 23:10 LavransBjerkestrand

Also struggling with this issue. Sometimes I want my nested loading.tsx to show, I never want the root to show if I have an even better, more specific skeleton, or else its a bad flash of content.

ahammer-sc avatar Nov 10 '23 05:11 ahammer-sc

I deleted all of the loading.tsx files in my project until this issue gets resolved. For me, any loading.tsx under a dynamic route does not get loaded at all. It shows the nearest parent loading.tsx, and only that, no flashing.

There is no point in showing an irrelevant loading page to the user, it is just bad UX.

  • I tried deleting the parent loading.tsx, that allows nested loadings properly, but I need a parent loading.tsx as well
  • Also tried route grouping, that does not work.

The nearest parent loading.tsx to a dynamic route ALWAYS gets prioritized over any nested loading.tsx’s under that dynamic route

Re4GD avatar Dec 25 '23 02:12 Re4GD

any hopes 🥲

bonface221 avatar Jan 05 '24 13:01 bonface221

This is still happening as of 14.1.0.

When hitting the leaf route directly on the server, only the leaf's loading page is shown (correct behaviour). However, when you first load the parent route, then navigate to the leaf page, the parent's loading page is also rendered.

I have confirmed that the nearest loading page is rendered when this happens. E.g.

app
├── layout.tsx
├── page.tsx
└── parent
    └── [id]
        ├── layout.tsx
        ├── leaf
        │   ├── [slug]
        │   │   ├── loading.tsx
        │   │   └── page.tsx
        │   ├── loading.tsx  <-- This will flash before loading from [slug] is shown.
        │   └── page.tsx
        ├── loading.tsx <-- This will flash if leaf/loading.tsx is removed.
        └── page.tsx

The only 'workaround' I've found is to put a dummy loading.tsx below the leaf node. E.g. in this situation, if you change leaf/loading.tsx to be a component which returns null, you'll get a flash of empty page, which is slightly better than a completely incorrect skeleton. Obviously this is painful if you're also showing a complex page at leaf/ as you're now left without any skeleton for that page.

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

Willyham avatar Jan 30 '24 22:01 Willyham

I've figured out a kind of workaround. You can use route groups (group) to 'mirror' what would be a nested route, e.g:

app
├── (app)
│   ├── (leaves)
│   │   └── parents
│   │       └── [id]
│   │           └── leaf
│   │               └── [slug]
│   │                   ├── loading.tsx
│   │                   └── page.tsx
│   ├── (parents)
│   │   └── parent
│   │       └── [id]
│   │           ├── loading.tsx
│   │           └── page.tsx
│   └── page.tsx
├── favicon.ico
├── globals.css
├── layout.tsx
└── page.tsx

In this case, neither route has a parent loading.tsx, but they still render at the correct urls /parent/id and /parent/id/leaf/slug/.

Of course, if you put a top level loading.tsx in /app, they'll both flash that one instead.

Willyham avatar Jan 30 '24 23:01 Willyham

Please fix 🥲 this is so vital and issue has been open since 2022. Continuing to bump here

moAlobaidi avatar Feb 21 '24 00:02 moAlobaidi

+1

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

bqst avatar Feb 29 '24 16:02 bqst

Adding my voice to those experiencing this. Hopefully a fix is prioritised.

antsmo avatar Feb 29 '24 18:02 antsmo

Experiencing the same thing, hope there is an update soon.

long-kr avatar Mar 22 '24 22:03 long-kr

Hey folks! Going to do my best to explain what's happening here, specifically in the case where you're noticing it during next dev or on links with prefetch={false}:

  • You load /. No prefetching happens, so we do not yet download the loading component(s) for the /jokes/<id> links.
  • You click on /jokes/1. Since Next.js doesn't know about the loading component, it has to download the loading component from the server when you click on the link. While this is happening, it's going to fallback to the nearest Suspense boundary that it knows about, in this case the "root" loading.tsx component.
  • Once the loading boundary for /jokes/1 is loaded, it switches to it, while waiting for the page data to come back from the server.

There are a couple of ways to fix this:

  • Move the loading.tsx component a level higher in your folder hierarchy. For example, in the original post, the loading component is part of app/jokes/[id]. But if we're trying to make a generic loading component to be shown when clicking any of the jokes links, it should be part of app/jokes instead.
  • If you can't change this hierarchy because you want app/jokes to have it's own loading component, you can leverage Route Groups to control this hierarchy. Here's an example of how the original repo could use route groups:
(without-root-loading)
  layout.tsx
  jokes
    [id]
      loading.tsx
      page.tsx
(with-root-loading)
  layout.tsx
  loading.tsx
  page.tsx
  • Ensure that you have prefetching turned on so that they can be downloaded when the user loads the page. This will make it so that by the time the user clicks the link, it'll already have that loading state in the cache and won't need to suspend to download it from the server.

Here's a video where I've taken the original reproduction in the first post, and moved app/jokes/[id]/loading.tsx to app/jokes/loading.tsx:

https://github.com/vercel/next.js/assets/1939140/37211444-9ed8-4ad4-b382-9ebe25585af6

Please let me know if there's something I'm missing! Otherwise I will be closing this issue after some time of inactivity.

ztanner avatar Apr 01 '24 18:04 ztanner

@ztanner amazing explanation, thank you 🙏

i came to the same conclusion with some experimentation but was wondering if this was intended behavior of the loading boundary in routes.

this really clears things up 🙌

moAlobaidi avatar Apr 02 '24 01:04 moAlobaidi

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.

github-actions[bot] avatar May 06 '24 12:05 github-actions[bot]