HydrateFallbackA HydrateFallback component is your way of informing Remix that you do not want to render your route component until after the clientLoader has run on hydration. When exported, Remix will render the fallback during SSR instead of your default route component, and will render your route component client-side once the clientLoader completes.
The most common use-cases for this are client-only routes (such as an in-browser canvas game) and augmenting your server data with client-side data (such as saved user preferences).
export async function clientLoader() {
const data = await loadSavedGameOrPrepareNewGame();
return data;
}
// Note clientLoader.hydrate is implied without a server loader
export function HydrateFallback() {
return <p>Loading Game...</p>;
}
export default function Component() {
const data = useLoaderData<typeof clientLoader>();
return <Game data={data} />;
}
export async function loader() {
const data = getServerData();
return json(data);
}
export async function clientLoader({
request,
params,
serverLoader,
}: ClientLoaderFunctionArgs) {
const [serverData, preferences] = await Promise.all([
serverLoader(),
getUserPreferences(),
]);
return {
...serverData,
preferences,
};
}
clientLoader.hydrate = true;
export function HydrateFallback() {
return <p>Loading user preferences...</p>;
}
export default function Component() {
const data = useLoaderData<typeof clientLoader>();
if (data.preferences.display === "list") {
return <ListView items={data.items} />;
} else {
return <GridView items={data.items} />;
}
}
There are a few nuances worth noting around the behavior of HydrateFallback:
clientLoader.hydrate=true on a given routeclientLoader without a server loader, as this implies clientLoader.hydrate=true since there is otherwise no loader data at all to return from useLoaderData
HydrateFallback in this case, Remix will not render your route component and will bubble up to any ancestor HydrateFallback componentuseLoaderData remains "happy-path"loader, useLoaderData would return undefined in any rendered route components<Outlet/> in a HydrateFallback because children routes can't be guaranteed to operate correctly since their ancestor loader data may not yet be available if they are running clientLoader functions on hydration (i.e., use cases such as useRouteLoaderData() or useMatches())See also: