Skip to content
Suspense실무 경험

Fixing Page Transition Flicker (White Screen) When Using React Lazy + Suspense

3 min readWorkMoved from my Tistory blogRead the Korean original

While optimizing the performance of the WiniLogis project earlier, I applied code splitting.

Before that, because it was an SPA, visiting the main page loaded the resources of every page bundled into a single JS file.

Because of this, unnecessary code was also loaded on the initial load.

To improve this, I used React's Lazy and Suspense to split files by domain, and changed the structure so resources are loaded only when they are needed.

As a result, the amount of code loaded on the main page went from 281,862 lines → 63,303 lines, which improved performance.

Along with that, I also optimized image resources and improved the i18n-based translation handling, which improved the overall performance score and accessibility score as well.

The problem

Figure 1. Before

After applying code splitting, one problem came up.

Every time I moved to another page, the screen disappeared for a moment and a white screen showed up.

Also, even though it was an SPA, page transitions did not feel natural and felt choppy.

Cause

Suspense was used at the top of App like this.

App.tsx

jsx
/* Apply lazy loading */
const Main = lazy(() => import('./components/main'));

export default function App() {
  return (
    <Suspense fallback={<></>}>
      <Routes>
        <Route path='/' element={<Main />} />
        ...
      </Routes>
    </Suspense>
  );
}

I left the fallback empty for these reasons.

  • API loading for each page is handled separately within that page
  • I judged that a global loading UI during page transitions was not appropriate for UX

But with this structure, the moment a lazy component was loading, Suspense rendered the fallback. As a result, the screen was emptied and a white screen appeared.

Options I considered

I considered a few directions to solve this.

However, this approach would require separate Suspense and fallback handling for each component, and I judged that the growing number of places to maintain could increase code complexity.

I also considered removing React's Suspense itself and using a regular SPA structure without code splitting.

But the project is large, and removing chunking would produce many bundle files over 100KB. I judged that this could hurt initial loading performance again.

In the end, I needed a way to fix the UX problem while keeping the code splitting structure.

How I fixed it

While looking for a solution, I was able to fix the problem with react-router-dom's v7_startTransition option.

index.tsx

jsx
...

root.render(
  <>
    <BrowserRouter future={{ v7_startTransition: true }}>
      <App />
    </BrowserRouter>
  </>
);

When this option is enabled, route transitions are wrapped in React's startTransition internally.

startTransition handles a state update as a transition, which helps keep the existing UI in place while the work is in progress.

Before, when a lazy component was loading during a page transition, Suspense rendered the fallback UI immediately, and this caused the white screen.

With this option applied, even when a page suspends, it is handled as a transition, so loading proceeds while the existing screen stays in place.

As a result, the fallback (<></>) no longer shows immediately while a lazy component is loading. The existing screen stays, and when loading finishes it moves naturally to the next screen. From the user's point of view, the page now changes without a break, and I was able to fix the white screen that appeared when moving between pages.

Figure 2. After (It may look like a white screen appears during the page transition, but no white screen appears in the actual behavior.), the flashing seems to have come from converting the mov file to gif.

Looking back

At first I thought the problem was the code splitting structure or the way I used Suspense, so I considered splitting the chunks into smaller units or changing the structure.

But I was able to fix it with a one-line option without a major structural change, and I learned one more thing from this.

After development was done, I also learned that updating react-router-dom to v7 applies the v7_startTransition option by default, so you get the same effect without any extra setting. So I updated react-router-dom to version 7 or later.

  • #React Lazy
  • #react-router-dom
  • #react-router-dom v7
  • #startTransition
  • #Suspense
  • #UX Improvement
  • #v7_startTransition
  • #WiniLogis
  • #Rendering Priority
  • #Performance Optimization

More in Work