Optimistic Transition: Why Next.js `loading.tsx` Isn’t Showing (App Router + middleware/proxy delay) - Giselle: AI App Builder

Optimistic Transition: Why Next.js loading.tsx Isn’t Showing (App Router + middleware/proxy delay)

PUBLISHED DECEMBER 24, 2025

Satoshi Toyama, Founding Engineer

You open Giselle Playground, pick an app, type a request, and Run.

The best version of this experience is immediate:

This instant feeling feels natural to users. However, in Next.js, it's surprisingly hard to achieve using only the framework's standard features.

In Next.js, when you implement a flow like an input screen → a results screen, it’s common to split it into separate pages. And it’s also common to use loading.tsx (or loading.js) to improve the experience until the next page renders. However, loading.tsx can only show after the next page’s response has started coming back—meaning while your proxy (formerly middleware) is still running, loading.tsx won’t show at all. That moment of dead air is what I’ll call the No UI gap.

In this post, I’ll use a sample application to show two things:

1) Why “just add a spinner” isn’t good enough

A spinner is fine for “this button is busy.”

But Playground is a destination-shaped flow:

A spinner on the source screen says: “wait here.”
A destination-like shell says: “you’re already on your way there.”

That difference matters most when navigation can’t even start rendering.

2) Next.js is great at smooth transitions—until it isn’t

In the App Router, two big tools improve perceived performance after the destination starts rendering:

loading.tsx: route-level render-time loading UI

A loading.tsx file can show while a segment is being prepared.

But the critical condition is timing:

Streaming (Suspense): progressive rendering once the response starts

Streaming helps once the server starts producing bytes for the destination.

Again, same boundary:

One boundary to remember

Both tools help after render starts.

If the delay happens before Next.js can start rendering the destination, these tools can’t show yet.

That’s exactly what middleware / proxy delay can create.

3) The surprise: loading.tsx and streaming don’t start under middleware / proxy.ts delay

To make this concrete, we’ll use a small sample app you can try in the browser: Next.js Loading Lab.

If you add a deterministic delay in request-time code (middleware/proxy), the timeline looks like this:

  1. user triggers navigation (Link, router.push, action → redirect, etc.)
  2. the request is held upstream by middleware / proxy delay
  3. only when that finishes can Next.js route + render the destination
  4. only then can loading.tsx / streaming appear

So when you feel the No UI gap, it’s not that Next “forgot” your loading.tsx.
It’s that render-time UI can’t start yet because the request hasn’t reached rendering.

This is an ordering problem (request-time vs render-time), not a bug.

4) Try it yourself: feel the No UI gap with proxy delay

If this still feels abstract, let’s try it. The goal is to make the boundary undeniable by introducing a delay you can feel consistently.

Steps

  1. Open Next.js Loading Lab.
  2. In the left menu, select loading.tsx.
  3. On the right, enter a keyword and click Open Summary.
    • You should see the skeleton UI immediately, then the result page renders and fetches the Wikipedia summary.

Here is the baseline behavior without proxy delay: the loading.tsx skeleton shows immediately after click.

  1. Now go to Proxy (middleware) in the left menu and check Enable proxy.
  2. Set Proxy delay to something noticeable (e.g. 500–1500ms) and click Save.
  3. Repeat steps 2–3.
    • This time, you’ll notice a “dead air” period: the skeleton won’t appear until after the proxy delay has elapsed.

With proxy delay enabled, notice the No UI gap: nothing can render (including loading.tsx) until the proxy finishes.

What to observe

Under proxy delay, look for:

That split is the entire point of this post.

5) The fix: “Optimistic Transition” (destination-shaped overlay that bridges the No UI gap)

You can see this solution working in the same sample app. In Next.js Loading Lab, enable Proxy (middleware) with a noticeable delay, then open Optimistic Transition and click Open Summary. Unlike loading.tsx, the overlay shows immediately on click—bridging the dead air while the proxy is still running.

With proxy delay enabled, this is what it looks like: the overlay appears immediately on click, even while the proxy is still running.

When the No UI gap happens, the problem isn’t “we need a loader.”

The real problem is:

the destination hasn’t started yet—so the user can’t see evidence of progress or state transition.

The solution is to render immediate, destination-shaped UI on the client before the destination can render.

Definition

An Optimistic Transition is UI that appears immediately and acts as a proxy of the final destination experience—so the user feels the transition has started even before the destination can render.

Why it works (relative to the boundary)

This is exactly the gap middleware/proxy delay exposes.

6) How Giselle applies it: layout-persistent overlay + store-driven control

To cover the No UI gap, two things must be true:

That leads to a clean division of responsibilities:

Want the code? Start with the demo + the real product

If you’re an engineer, you probably want to verify the claim by reading actual code. Two good starting points:

Placement: mount it in a persistent layout, not in page.tsx

If the overlay lives inside a page, it can unmount during navigation—exactly when you need it.

Mount it in a layout that persists across the transition (segment layout or root layout), and render it alongside {children}.

Control: trigger from the source screen (client), render from the layout

The source action (like “Run”) can:

The overlay can then:

Dismissal: make it explicit

Because a layout-mounted overlay persists, it won’t magically disappear.
Make hiding explicit on success/failure:

7) When to use this pattern (and when not to)

Use Optimistic Transition when:

Avoid it when:

Pitfalls to design for:

8) Takeaway

If you remember one thing, make it this:

Next.js can only show loading.tsx / streaming after the destination has started rendering.

Middleware / proxy delay can block before that point—creating a No UI gap that render-time tools cannot cover.

Decision rule:

In Giselle Playground, we optimize for “instant” by making the transition feel continuous—even when navigation hasn’t started rendering yet.