sdk/client
Client Side Functions
The rwsdk/client module provides a set of functions for client-side operations.
initClient
The initClient function is used to initialize the React Client. This hydrates the RSC flight payload that's add at the bottom of the page. This makes the page interactive.
Parameters
initClient() accepts an optional configuration object:
| Parameter | Type | Description |
|---|---|---|
transport | Transport | Custom transport for server communication (defaults to fetchTransport) |
hydrateRootOptions | HydrationOptions | Options passed directly to React's hydrateRoot. Supports all React 19 hydration options including error handling (see examples below). |
handleResponse | (response: Response) => boolean | Custom response handler for navigation errors (navigation GETs) |
onHydrated | (meta?: RscPayloadMeta) => void | Callback invoked after a new RSC payload has been committed on the client |
onActionResponse | (actionResponse) => boolean | void | Optional hook invoked when an action returns a Response; return true to signal that the response has been handled and default behaviour should be skipped |
Error Handling
React 19 introduced powerful error handling APIs that you can use via hydrateRootOptions:
onUncaughtError: Handles uncaught errors (async errors, event handler errors, errors that escape error boundaries)onCaughtError: Handles errors caught by error boundariesonRecoverableError: Handles recoverable errors during rendering
These handlers are client-side only and do not handle server-side RSC rendering errors or router-level errors.
Usage Examples
import { initClient } from "rwsdk/client";
initClient();import { initClient } from "rwsdk/client";
initClient({
hydrateRootOptions: {
onUncaughtError: (error, errorInfo) => {
console.error("Uncaught error:", error);
console.error("Component stack:", errorInfo.componentStack);
// Send to monitoring service
sendToSentry(error, errorInfo);
},
onCaughtError: (error, errorInfo) => {
console.error("Caught error:", error);
// Handle errors from error boundaries
sendToSentry(error, errorInfo);
},
},
});import { initClient } from "rwsdk/client";
import * as Sentry from "@sentry/browser";
initClient({
hydrateRootOptions: {
onUncaughtError: (error, errorInfo) => {
Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack,
errorBoundary: errorInfo.errorBoundary?.constructor.name,
},
},
tags: { errorType: "uncaught" },
});
},
onCaughtError: (error, errorInfo) => {
Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack,
errorBoundary: errorInfo.errorBoundary?.constructor.name,
},
},
tags: { errorType: "caught" },
});
},
},
});import { initClient } from "rwsdk/client";
initClient({
hydrateRootOptions: {
onUncaughtError: (error, errorInfo) => {
// Log error
logError(error, errorInfo);
// Show user-friendly message
showErrorToast("Something went wrong. Please try again.");
// Optionally reload the page for critical errors
if (isCriticalError(error)) {
window.location.reload();
}
},
},
});import { initClient, initClientNavigation } from "rwsdk/client";
const { handleResponse, onHydrated } = initClientNavigation();
initClient({ handleResponse, onHydrated });initClientNavigation
The initClientNavigation function is used to initialize the client side navigation. An event handler is assocated to clicking the document. If the clicked element contains a link, href, and the href is a relative path, the event handler will be triggered. This will then fetch the RSC payload for the new page, and hydrate it on the client.
ClientNavigationOptions
initClientNavigation() accepts an optional ClientNavigationOptions object that lets you
control how the browser scrolls after each navigation:
| Option | Type | Default | Description |
|---|---|---|---|
scrollToTop | boolean | true | Whether to scroll to the top of the page after a successful navigation. Set it to false when you want to preserve the existing scroll position (for example, an infinite-scroll list). |
scrollBehavior | 'instant' | 'smooth' | 'auto' | 'instant' | How the scroll happens when scrollToTop is true (ignored otherwise). |
onNavigate | () => Promise<void> | void | — | Callback executed after the history entry is pushed but before the new RSC payload is fetched. Use it to run custom analytics or side-effects. |
Usage Examples
import { initClientNavigation } from "rwsdk/client";
initClientNavigation();initClientNavigation({
scrollBehavior: "smooth",
});initClientNavigation({
scrollToTop: false,
});initClientNavigation({
scrollBehavior: "auto",
onNavigate: async () => {
// e.g. send page-view to analytics before RSC fetch starts
await myAnalytics.track(window.location.pathname);
},
});Rationale & Defaults
RedwoodSDK mirrors the behaviour of classic Multi Page Apps where each link click brings you back to the top of the next page. This is the most common expectation and is therefore the default. You can turn it off or make it smooth with a single option – no additional libraries required.
NavigationPending
NavigationPending is a Suspense-aware client component. Put it inside your own
<Suspense fallback> to hide stale server-backed UI while a matching
client-side RSC navigation is pending.
import { Suspense } from "react";
import { NavigationPending } from "rwsdk/client";
<Suspense fallback={<ResultsSkeleton />}>
<NavigationPending searchParams={["search", "page"]}>
<ResultsTable />
</NavigationPending>
</Suspense>;By default it suspends for any pending navigation. Use searchParams, watch,
or when to make a subtree wait only for relevant URL changes. Use one option
at a time; if you combine them, when takes precedence over watch, and
watch takes precedence over searchParams.
Options
| Prop | Type | Description |
|---|---|---|
searchParams | readonly string[] | Shorthand for watching only the listed search params. |
watch | { pathname?: boolean; searchParams?: boolean | readonly string[]; hash?: boolean } | Explicit URL parts to watch. Defaults are pathname: true, searchParams: true, and hash: false. |
when | ({ currentUrl, pendingUrl }) => boolean | Custom predicate. Return true when this subtree should suspend for the pending navigation. |
<Suspense fallback={<ResultsSkeleton />}>
<NavigationPending
watch={{
pathname: false,
searchParams: ["search", "page"],
hash: false,
}}
>
<ResultsTable />
</NavigationPending>
</Suspense><Suspense fallback={<TabSkeleton />}>
<NavigationPending
when={({ currentUrl, pendingUrl }) =>
currentUrl.searchParams.get("tab") !== pendingUrl.searchParams.get("tab")
}
>
<TabPanel />
</NavigationPending>
</Suspense>The lower-level useNavigationPending() hook uses the same options and suspends
in the same way. Make sure the onHydrated callback returned by
initClientNavigation() is passed to initClient() so pending navigations can
resolve after commit.
navigate
The navigate function is used to programmatically navigate to a new page. It
accepts a href parameter (the destination URL) and an optional options object.
| Option | Type | Default | Description |
|---|---|---|---|
history | 'push' | 'replace' | 'push' | Determines how the history stack is updated. 'push' adds a new entry, 'replace' replaces the current one. |
info.scrollToTop | boolean | true | Whether to scroll to the top of the page after navigation. |
info.scrollBehavior | 'instant' | 'smooth' | 'auto' | 'instant' | How the scroll happens when scrollToTop is true. |
Usage Examples
import { navigate } from "rwsdk/client";
navigate("/about");import { navigate } from "rwsdk/client";
navigate("/profile", { history: "replace" });import { navigate } from "rwsdk/client";
navigate("/dashboard", {
info: {
scrollBehavior: "smooth",
},
});