Browser-Only React Components With Suspense
When building your Gatsby site, every page of it is prerendered completely to be shipped to your browser and displayed blazingly fast, compared to parsing and setting up the JavaScript
code required to render the React components first, as done in traditional SPAs (e.g. create-react-app). The Gatsby team released an
awesome, in-depth post about the reasons for doing this.
One natural problem with this approach is, that it gets hard to pre-render content that depends on the user, for example, components depending on your viewport to determine which width and
height to use. Another issue happens when you try to pre-render components that change a lot, if the structure that gets returned during build time change on hydration, you'll be in for a chaotic ride.
Luckily, Gatsby, or rather React Suspense, has us covered in this case, too! There are a few ways to get client-side-only code running with Gatsby, as documented here,
but I'll be showcasing the React Suspense way!
const ThisOnlyWorksInABrowser = () => {
// Do some browser-specific logic here
return <p>This will not be pre-rendered</p>;
};
// This is important! We need to use a default
// export for the dynamic import to work!
export default ThisOnlyWorksInABrowser;Our component above doesn't do much, but it's an example of a piece of code that will only work in a browser environment. To get this imported later, we need to use a default export, not a named one.
A great post on understanding pre-rendering and mitigating issues arising from Gatsby's SSR flow inspired me to use the mounting check
logic to isolate browser-only code.
import React from 'react';
// Lazy-load client-side-only component
const ClientSideOnlyLazy = React.lazy(() =>
import('./PathToMyBrowserOnlyComponent')
);
const ClientSideOnlyComponent = () => {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
// We can render our browser-only component
// lazily using Suspense and a dynamic import
return (
<React.Suspense fallback={<div />}>
<ClientSideOnlyLazy />
</React.Suspense>
);
};
const MyPage = () => (
<>
<Header />
<Navigation />
<ClientSideOnlyComponent />
</>
);We've added quite a few parts now: We're essentially creating a lazy-loadable
version of our browser-only component using a dynamic import and React's lazy
feature, then rendering this component wrapped in a Suspense component, guarded by ourhasMounted condition that makes sure we're not trying to render this component while
in SSR mode.
That's already everything you'll need to make your browser-only components working without
SSR interfering! Please note, that this is kind of an escape-hatch functionality provided
by the build tooling, in most cases you'll want to make your components SSR-compatible to
harness the performance and SEO benefits you would be missing out on otherwise.
Thanks for reading this short guide! If you've got any questions, suggestions, or feedback
in general, don't hesitate to reach out on Twitter or
by mail.