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.