Ask
96
@quiet_stacker ·

loading.tsx never renders on client nav but works on hard refresh, 3s of nothing Streaming

Next 15.3, app/(dash)/reports/loading.tsx. If I paste the URL in the address bar and hit enter I get the skeleton immediately, then the page. If I click the <Link> from the sidebar I get nothing at all - the old page just sits there for about 3 seconds and then the reports page appears.

The slow part is a ClickHouse query that takes 2.6s. I expected the skeleton either way. Am I misunderstanding what loading.tsx does on a client-side transition, or is something in my tree defeating it?

8 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @awkward_ash · 5d ago · 3 replies

    loading.tsx only covers the segments below it. If the 2.6s await lives in the layout at or above that segment, there is no boundary between the navigation start and your slow work, so the router has nothing to show and it waits.

    The fastest way to find out: put await new Promise(r => setTimeout(r, 5000)) at the top of page.tsx and click the link. If the skeleton shows, the boundary is fine and your real await is higher up. If it still hangs, the await is in the layout.

    Either way the fix is the same shape - stop awaiting in the layout, move the slow component into the page and wrap it yourself:

    <Suspense fallback={<ReportsSkeleton />}>
      <SlowChart range={range} />
    </Suspense>
    
    39
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @quiet_stacker · 5d ago

      It was the layout. (dash)/layout.tsx awaits a workspace lookup for the sidebar, which normally takes 40ms so I never noticed, but the client transition waits for the whole layout payload before it commits. Moved the lookup into the sidebar component behind its own Suspense and the skeleton shows instantly now.

      14
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @lowtech_lior · 3d ago

      This is the one thing I wish were louder in the docs: a layout is not free, it is on the critical path of every navigation into it, and it does not re-render but it does have to resolve.

      10
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @arrayformula_al · 4d ago

    Before you change anything, look at the network tab and find the request with ?_rsc= in it during the click. Time it. If that request takes 2.8s and returns one payload, nothing is streaming and you are debugging the wrong layer. If it takes 200ms and streams chunks, your fallback is rendering somewhere you cannot see and it is a CSS/layout problem instead.

    Half the "streaming does not work" threads I read are people who never checked whether the response was chunked.

    26
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @litercount_lou · 3d ago

    Also check generateMetadata on that route. If it awaits the same slow query, the render can sit behind it, and because the hard navigation path and the client navigation path do not treat it identically you get exactly this asymmetry. Comment it out for one test run - it costs you thirty seconds and rules out a whole branch.

    19
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @kerf_wander · 3d ago · 2 replies

    This is prefetch. <Link> prefetches the route, so by the time you click it has a payload and skips the loading state deliberately. Set prefetch={false} and you will see your skeleton.

    12
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @awkward_ash · 5d ago

      Half right in a misleading way. Prefetching a route that has a loading boundary is exactly how you get an instant skeleton, so turning it off makes his case worse, not better. His problem is that there is no boundary between the click and the slow await, and prefetch cannot help with that because a dynamic segment's data is not what gets prefetched.

      8
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @hemline_hank · 4d ago

    Unrelated to the bug but 2.6s for a chart nobody asked for yet - can you render the page shell with the last cached numbers and let the fresh ones land when they land? I stopped fighting boundaries once I accepted that most dashboards are fine slightly stale.

    11
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report