Fetch and transitioning server-rendered data with Next.js to realtime Firestore data. 335 words.
Last Updated
Health Check
firebase@8
next@10
react-firebase-hooks@2.2
Next.js application data can be prerendered on the server with getStaticProps
or at each request with getServerSideProps
. That’s great for search engine optimization, but what if you also need Firestore data to react to realtime updates after the initial render?
The following snippet outlines techniques for fetching and hydrating server-rendered data with Next.js, while maintaining a realtime connection to Firebase. It assumes usage of react-firebase-hooks and Cloud Firestore.
Transition to Realtime
Fetch on the Server
Data is fetched on the server with getStaticProps
, then it’s sent to the default component export as props.
// initialize Firebase JS SDK...
export async function getStaticProps(context) {
const ref = firestore.doc('items/foo');
const item = (await ref.get()).data();
return {
props: { item }
}
}
Realtime Connection on the Client
The client will receive the predendered data first, then “transition” to a realtime stream from Firestore. It other words, try to show the latest data - but if null - fallback to the props
value.
import { useDocumentData } from 'react-firebase-hooks/firestore'
export default function SomePage(props) {
const ref = firestore.doc('items/foo')
const [item] = useDocumentData(ref)
return (
<div>
{ (item ?? props.item).title }
</div>
)
}
Custom Firestore SSR Hook
The code above uses the nullish coalescing operator ??
to fallback to props, but that can be cumbersome code to maintain when dealing with many document fields. An alternative approach is to write a custom hook (by extending react-firebase-hooks) that starts with a default value. When Firestore is loading show props, otherwise show the latest realtime value.
import { useDocumentData } from 'react-firebase-hooks/firestore'
function useDocumentDataSSR(ref, options) {
const [value, loading, error] = useDocumentData(ref, options)
if (options?.startWith && loading) {
return [options.startWith, loading, error]
} else {
return [value, loading, error]
}
}
Now access fields on the Firestore document without worrying about whether it came from props or a realtime update.
export default function SomePage(props) {
const ref = firestore.doc('items/foo')
const [item] = useDocumentDataSSR(ref, { startWith: props.item })
return (
<div>
{ item?.title }
</div>
)
}