Published on

Debugging Suspense

Authors
  • avatar
    Name
    Christina Yang
    Twitter

From what I understand about React Suspense, it acts similarly to error boundaries, but instead of throwing an error, it throws a promise.

After switching my Next.js app to server components, I noticed an odd behavior in my app: when I navigated to a page within the same layout, the entire page—including the navbar—would reload. This was unexpected, since the navbar is part of the shared layout and should not reload.

I wanted to figure out exactly which component was causing a promise to be thrown (and thus triggering Suspense), but I couldn't find any resources on how to debug this.

To help with this, I created a very simple component to visually locate where the promise was being thrown:

import React, { Suspense } from 'react'

const DebugSuspense = ({ children, color }: { children: React.ReactNode; color: string }) => {
    return <Suspense fallback={<div style={{ height: '100vh', width: '100vw', background: color }} />}>{children}</Suspense>
}

export default DebugSuspense

I wrapped various parts of my app with DebugSuspense, giving each instance a unique color for its fallback. This way, when a promise was thrown, I could see which color appeared, and therefore which boundary was suspending.

At first, I saw the 'blue' fallback, so I nested more DebugSuspense components inside the blue one, each with a different color. By repeating this process, I was able to pinpoint the exact component causing the promise to be thrown. It turned out I had an async component fetching data, but it wasn't wrapped in a Suspense boundary.

After wrapping that component properly in Suspense, the navbar no longer disappeared during navigation while loading. However, I still noticed that the layout was reloading and showing a skeleton every time I navigated to a different page—even though I was staying within the same layout.

Based on my understanding of Next.js layouts, and from observing other layouts, the layout component should not reload when navigating between pages that share the same layout.

I did some googling and found these resources:

It turns out the issue was that I was using a plain <a> tag for the links in my navbar, instead of the Next.js <Link> component! Using a regular <a> tag causes a full page reload, which resets the layout, while <Link> enables client-side navigation and preserves the layout as expected.

Thankfully, the mystery was solved, and the layout now works as expected.

Lessons learned:

  • Suspense works by throwing promises, similar to how error boundaries work by throwing errors.
  • Suspense can be debugged by wrapping components with DebugSuspense
  • ALWAYS use Next.js-specific navigation methods, like <Link> and router.push, otherwise you may see unexpected behavior, such as unnecessary layout reloads.
Create an ecard at CelebrateThisMortal.comBe more productive with Planda