2 February 2023 2 min read

Remix: Use Suspense for client-only components

React 18 has this cool pattern for rendering content only on the client:

export default function SomeRoute() {
  return (
    <Suspense fallback={<Loading />}>
      <Chat />
    </Suspense>
  );
}

function Chat() {
  if (typeof window === "undefined") {
    throw Error("Chat should only render on the client.");
  }
  // ...
}

Here we use a <Suspense> boundary to render a loading component on the server, and then render the <Chat> component on the client. This works because React will retry rendering trees inside a suspense boundary on the client after they throw an error on the server.

If you're using a streaming server rendering API eg. renderToReadableStream then you should start using this today!

This is great for Remix apps, especially if you're using client-only components. We can move our chat component into chat.client.tsx to ensure that it's never included in our server bundle.

import Chat from "~/components/chat.client";

export default function SomeRoute() {
  return (
    <Suspense fallback={<Loading />}>
      <Chat />
    </Suspense>
  );
}

What's great about this pattern is that we can seamlessly transition to a lazy() component, which will ensure the code is codesplit from our main client bundle, leading to faster initial page loads when the chat component is large.

import { lazy } from "react";

const Chat = lazy(() => import("~/components/chat.client"));

export default function SomeRoute() {
  return (
    <Suspense fallback={<Loading />}>
      <Chat />
    </Suspense>
  );
}

And finally, there are some additional benefits here with regards to hydration. I won't go into all of these in this blog post, but you can read about them in the React 18 architecture overview.

Tom Sherman

Hey 👋 I'm Tom, a Software Engineer from the UK.

I'm currently a Software Engineer at OVO Energy. I'm super into the web, functional programming, and strong type systems.

You can most easily contact me on Mastodon but I'm also on Twitter, LinkedIn, and GitHub.