Fixing Page Transition Flicker (White Screen) When Using React Lazy + Suspense
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
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
/* 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
...
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.
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