Skip to main content

Command Palette

Search for a command to run...

Stop Showing Blank Screens: Understanding React Suspense and Lazy Loading

Updated
5 min read
Stop Showing Blank Screens: Understanding React Suspense and Lazy Loading
E

Ezinne Nwani is a Frontend Developer based in Lagos, Nigeria. She shares her journey through clear, beginner-friendly technical articles on web development.

Have you ever navigated to a page or a dashboard in a React application and seen a blank screen for a few seconds?

No images. No text. Just... nothing.

Or maybe, you have opened a React app and noticed that it loads everything at once, even parts of the app you have not visited.

If you have experienced this, then you have seen what happens when code is loaded inefficiently.

That's where lazy loading and Suspense come in.

They allow us to:

  • Load only what we need, when we need it

  • Avoid shipping unnecessary JavaScript upfront

  • Display a fallback UI while components load

  • Improve perceived performance

And in real world applications, perceived performance is just as important as actual performance.


What is Lazy Loading?

Lazy loading is a technique where components are loaded only when they are needed, instead of loading everything during the initial render.

By default, React bundles your entire component tree into one large JavaScript file. The larger your app grows, the larger that bundle becomes.

With lazy loading, we split that bundle.

import { lazy } from "react";

const Dashboard = lazy(() => import("./Dashboard"));

Let’s break this down:

  • lazy() is imported from React.

  • It takes a function that uses a dynamic import().

  • That dynamic import tells the bundler (like Vite or Webpack) to create a separate chunk.

  • The component is loaded only when it’s actually rendered.

This is called code splitting.

Instead of forcing users to download your entire application upfront, you let them download only what they need at that moment.


What is Suspense?

Lazy loading alone is not enough.

When a lazy component is being fetched, React needs to show something.

That’s where Suspense comes in.

import { Suspense } from "react";

<Suspense fallback={<p>Loading...</p>}>
  <Dashboard />
</Suspense>

The fallback prop defines what users see while the component is loading.

This could be:

  • A text

  • A loading spinner

  • A skeleton screen

  • A progress bar

  • A branded loader component

Instead of showing a blank screen, you show intentional UI, and that small detail significantly improves perceived performance.


Why Blank Screens Happen (And Why They're Bad)

Without lazy loading:

  • Your initial JavaScript bundle grows large.

  • The browser blocks rendering until it downloads and parses everything.

  • On slower networks, users wait.

  • Sometimes they see nothing.

Large dashboards, chart-heavy pages, authentication flows, and route-based apps are especially affected.

But with lazy loading + Suspense:

  • Your initial bundle is smaller.

  • Components load progressively.

  • Users get visual feedback immediately.

  • The app feels faster.

Performance shouldn't only be about getting a high number in a testing tool, it should also be about how fast the app feels.


An Example using Lazy Loading Routes

Here's a practical example using React Router.

import { BrowserRouter, Routes, Route } from "react-router-dom";
import { lazy, Suspense } from "react";

const HomePage = lazy(() => import("./pages/home-page"));
const AboutPage = lazy(() => import("./pages/about-page"));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading page...</div>}>
        <Routes>
          <Route path="/home" element={<HomePage />} />
          <Route path="/about" element={<AboutPage />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

export default App;

With this setup:

  • The HomePage component loads only when /home is visited.

  • The AboutPage component loads only when /about is visited.

  • The app doesn’t download every route upfront.

  • Your initial load becomes lighter.

This is especially powerful in apps with multiple pages.


Multiple Suspense Boundaries

With Suspense, you are not limited to wrapping your entire app. You can use multiple Suspense boundaries:

<Suspense fallback={<SidebarSkeleton />}>
  <Sidebar />
</Suspense>

<Suspense fallback={<ContentLoader />}>
  <MainContent />
</Suspense>

<Suspense fallback={<div>Loading comments... </div>}>
  <Comments />
</Suspense>

This allows parts of your UI to load independently.

For example:

  • Sidebar loads first

  • The main content loads later

  • Comments load after that

This creates progressive rendering, which feels extremely smooth to users.


When Should You Use Lazy Loading?

Lazy loading makes sense when you’re building:

  • Large applications

  • Dashboard systems

  • Route-heavy apps

  • Apps with heavy components (charts, maps, editors)

  • Feature-based modules

It’s usually unnecessary for very small applications. Over-optimizing a tiny app can actually complicate your architecture.


Important Notes

  1. Suspense does NOT fetch data by default. It only handles component loading (unless using modern data frameworks like React Server Components or specific data libraries).

  2. Lazy loading works only with default exports.

  3. Always wrap lazy components inside <Suspense>.

  4. Error boundaries are important. If a lazy import fails, Suspense alone won’t handle the error, you’ll need an error boundary.


Final Thoughts

Using lazy loading and Suspense was a game changer for me when I first learned it.

At first, it felt like a small optimization, but as my projects grew, especially when working with multiple route-based apps, I realized that they’re not just React features but performance tools. And as frontend developers, performance is a part of user experience.

Thank you so much for reading! ❤️