Learn

SSR with Loaders

When you have a server runtime that can execute loaders at request time, set runLoaders: true in the ssr config to produce fully rendered HTML — including loader data — on the server.

How runLoaders Works

As described in the Static Site Generation guide, the ssr prop enables path-based route matching during SSR. By default, routes with loaders are skipped. Setting runLoaders: true changes this: those routes are matched and their loaders execute during SSR.

// Without runLoaders (default): loaders are skipped
<Router routes={routes} ssr={{ path: "/dashboard" }} />

// With runLoaders: loaders execute during SSR
<Router routes={routes} ssr={{ path: "/dashboard", runLoaders: true }} />

When loaders run during SSR, their results are passed to components as the data prop. The server-rendered HTML includes the loader content, so users see the full page immediately.

Example

Consider a dashboard route with a loader that fetches user data:

const routes = [
  route({
    component: AppShell,
    children: [
      route({ path: "/", component: HomePage }),
      route({
        path: "/dashboard",
        component: DashboardPage,
        loader: dashboardLoader,
      }),
    ],
  }),
];

// Without runLoaders: DashboardPage is skipped during SSR.
// Users see only the app shell; dashboard content fills in after hydration.
<Router routes={routes} ssr={{ path: "/dashboard" }} />

// With runLoaders: dashboardLoader executes during SSR.
// DashboardPage renders with data — users see the full page immediately.
<Router routes={routes} ssr={{ path: "/dashboard", runLoaders: true }} />

Server Setup

Unlike static site generation, SSR with loaders requires a server runtime that can handle requests and render pages dynamically. Your server needs to know the requested pathname and pass it to the router:

// App.tsx — receives the pathname from the server
export default function App({ pathname }: { pathname: string }) {
  return (
    <Router
      routes={routes}
      ssr={{ path: pathname, runLoaders: true }}
    />
  );
}

Because loaders run during SSR, they must be able to execute in the server environment. If your loaders call browser-only APIs, you may need to adjust them or use environment checks.

Comparison with ssr (No Loaders)

The choice between ssr alone and ssr + runLoaders depends on your deployment model:

  • ssr={{ path }} — Ideal for static site generation. Pages without loaders are fully pre-rendered. Pages with loaders show the app shell during SSR and fill in data after hydration. No server runtime needed.
  • ssr={{ path, runLoaders: true }} — Ideal for dynamic SSR with a server runtime. All matched routes render during SSR, including those with loaders. Users see the complete page immediately.

Key Takeaways

  • Set runLoaders: true to execute loaders during SSR for fully rendered server output
  • Loader results are passed to components as the data prop during SSR, just as they are on the client
  • This mode requires a server runtime that handles requests dynamically
  • After hydration, the real URL from the Navigation API takes over and ssr is ignored