FUNSTACK Router is designed to work with React Server Components (RSC). The package provides a dedicated server entry point so that route definitions can live in server modules, keeping client bundle sizes small.
In an RSC architecture, the module graph is split into server modules and client modules. Server modules run at build time (or on the server) and are never sent to the browser. Client modules are marked with the "use client" directive and are included in the browser bundle.
The main @funstack/router entry point is marked "use client" because it exports components and hooks that depend on browser APIs (the Navigation API, React context, etc.). This means importing from @funstack/router in a server module would fail.
To solve this, the package provides a separate entry point: @funstack/router/server.
@funstack/router/server Entry PointThe server entry point exports the route() and routeState() helper functions without the "use client" directive. This lets you define your route tree in a server module:
// App.tsx — a Server Component (no "use client" directive)
import { Router } from "@funstack/router";
import { route } from "@funstack/router/server";
const routes = [
route({
path: "/",
component: Layout,
children: [
route({ path: "/", component: HomePage }),
route({ path: "/about", component: AboutPage }),
],
}),
];
export default function App() {
return <Router routes={routes} />;
}In this example, App is a server component. It builds the route array using route() from @funstack/router/server and renders the Router component from @funstack/router which is a client component.
route — Route definition helper (same API as the main entry point)routeState — Route definition helper with typed navigation stateLoaderArgs, RouteDefinition, PathParams, RouteComponentProps, RouteComponentPropsWithDataRoute definitions can be defined in server modules because they are plain data structures except for page components and loader functions. Fortunately, it is possible to import both of these from client modules which results in client references that can be passed from the server to the client through the routes prop.
// App.tsx — Server Component
import { Router } from "@funstack/router";
import { route } from "@funstack/router/server";
import { lazy } from "react";
// Import page components from client modules
import HomePage from "./pages/HomePage.js";
import DashboardPage from "./pages/DashboardPage.js";
import SettingsPage from "./pages/SettingsPage.js";
const routes = [
route({
component: Layout,
children: [
route({ path: "/", component: HomePage }),
route({ path: "/dashboard", component: DashboardPage }),
route({ path: "/settings", component: SettingsPage }),
],
}),
];
export default function App() {
return <Router routes={routes} />;
}Loaders run client-side — they execute in the browser when a route is matched. This means a loader function cannot be defined inline within a server module. Instead, define the loader in a client module and import it:
// loaders/dashboard.ts — a Client Module
"use client";
export async function dashboardLoader({ params }: LoaderArgs) {
const res = await fetch(`/api/dashboard/${params.id}`);
return res.json();
}// App.tsx — Server Component
import { Router } from "@funstack/router";
import { route } from "@funstack/router/server";
import { dashboardLoader } from "./loaders/dashboard.js";
const routes = [
route({
component: Layout,
children: [
route({ path: "/", component: HomePage }),
route({
path: "/dashboard/:id",
component: DashboardPage,
loader: dashboardLoader,
}),
],
}),
];
export default function App() {
return <Router routes={routes} />;
}By placing the loader in a "use client" module, it is included in the client bundle where it can access browser APIs. The server component imports the reference and passes it as part of the route definition.
All examples so far have used client components as route components, but you can go even further and use server components as route components. Actually, this is the primary use case for the RSC support in FUNSTACK Router — it allows pre-rendering each route on the server (or at build time for static sites).
When you use server components as route components, the route's component must be a React node (i.e. <MyComponent />) instead of a component reference (i.e. MyComponent) because a references to server components cannot be passed to the client.
// App.tsx — Server Component
import { Router } from "@funstack/router";
import { route } from "@funstack/router/server";
import HomePage from "./pages/HomePage.js";
import AboutPage from "./pages/AboutPage.js";
const routes = [
route({
component: <Layout />,
children: [
route({ path: "/", component: <HomePage /> }),
route({ path: "/about", component: <AboutPage /> }),
],
}),
];
export default function App() {
return <Router routes={routes} />;
}In this example, HomePage and AboutPage are server components. They are rendered on the server and the resulting HTML is sent to the client.
Due to this nature, a route component defined as a server component cannot receive route props (params, search params, navigation state, etc). We are exploring ways to lift this limitation in the future, but for now if you need to access route props you will need to use client components as route components.
For some use cases it is enough to have a client component child as a pathless route:
const routes = [
route({
path: "/",
component: <HomePage />, // Server Component
children: [
route({
component: InteractivePartOfHomePage, // Client Component
loader: someLoaderForHomePage,
}),
],
}),
];In this example, HomePage is a server component that renders the static parts of the page while InteractivePartOfHomePage is a client component that can access route props (like loader data). HomePage can render <Outlet /> to render its child routes.
route and routeState from @funstack/router/server to define routes in server modulesRouter is a client component and serves as the client boundary — render it directly from your server component"use client" modules and import them into your route definitions<MyPage />) instead of component references (e.g. MyPage)