BTST
PluginsQuickstartDocs
Live Blog

From research to product evaluation

Evaluating a publishing workflow for an app you already own?

See what the BTST Blog plugin adds to an existing React or Next.js app
BTST

Open-source TypeScript features for the React application, data, and deployment you already own.

Released plugins

  • Blog
  • AI Chat
  • CMS
  • Form Builder
  • UI Builder
  • Kanban
  • Comments
  • Media
  • Route Docs
  • OpenAPI
  • Better Auth UI

Resources

  • Quickstart
  • Documentation
  • All plugins
  • Live Blog
  • GitHub (opens in a new tab)
© 2026 BTST. Open source under the MIT License.
AI Chat
September 13, 2026ReactReact Router

React Router and BTST: Load Plugin Data Before Hydration

Connect the server loader, request-scoped QueryClient, metadata, and HydrationBoundary so direct requests and client navigation render the same plugin content.

React Router and BTST: Load Plugin Data Before Hydration

A plugin page that works after clicking a link can still render incomplete HTML on a direct visit. In a React Router application, check the server loader and the query data handed to the browser before changing the page component.

This guide covers the React Router framework integration in BTST 3.0.2. It assumes server rendering is enabled and the generated BTST catch-all route, layout, and client factories are in place. A standalone BrowserRouter application needs a different server-rendering setup.

Follow one request through the integration#

The released createReactRouterPage helper resolves the plugin route, runs its loader, dehydrates the React Query cache, and computes route metadata. Its page component uses HydrationBoundary to restore that cache around the resolved plugin component.

That sequence connects four responsibilities:

PartResponsibility
React Router server loaderReceives the request and chooses the request-aware stack
BTST route loaderFetches the plugin data needed by that route
Dehydrated QueryClient stateCarries eligible cached results into the rendered response
HydrationBoundary and provider stackMake those results available to the browser's plugin hooks

BTST's released page helper defines this ordering. React Router's route module reference explains the surrounding framework exports.

Keep the generated request-aware loader#

The BTST 0.2.1 generator uses this route wiring. The local imports refer to the factories generated for your application; this is not a complete route without those files:

TSX
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
import { createReactRouterPage } from "@btst/stack/react-router";
import { getOrCreateQueryClient } from "~/lib/query-client";
import { getStackClient } from "~/lib/stack-client";
import { getStackClientForRequest } from "~/lib/stack-client.server";

const page = createReactRouterPage({
  getStackClient,
  getQueryClient: getOrCreateQueryClient,
});

export const loader = page.createLoader((queryClient, { request }) =>
  getStackClientForRequest(queryClient, {
    headers: request.headers,
    requestOrigin: new URL(request.url).origin,
  }),
);

export const meta = page.meta;
export const ErrorBoundary = page.ErrorBoundary;
export default page.Component;

Use the same alias convention as your project. Passing request headers allows the server client factory to resolve the current request's identity and API transport as configured by the application. Keep that factory in its server module; browser bundles should not import server credentials or backend initialization.

The QueryClient factory must create a fresh cache for server requests while reusing the intended browser instance. A module-level server QueryClient can retain another request's data. Replacing the generated factory with a single shared cache to eliminate duplicate fetches changes the privacy boundary.

Diagnose the stage that is missing#

If direct HTML lacks the article or plugin content, check whether the catch-all route exports the server loader and whether its resolved plugin route has the expected loader. Inspect the document response, not only the DOM after JavaScript runs.

If HTML contains the content but the browser refetches unexpectedly, inspect the query keys, hydration state, and provider wiring. Some refetches follow normal stale-time settings; a network request alone is not proof that hydration failed. Verify that the browser is using the same resolved API and site configuration and the same query-key shape as the server.

If metadata is missing, retain the helper's loader-before-metadata ordering and meta export. Calling metadata separately before its data has loaded can produce a different result from the rendered page.

The helper also supports custom dehydration options. Review serialized errors and data before exposing them in HTML. Do not place secrets or unrelated user records in a cache that will be dehydrated into a public response.

Verify direct requests and navigation#

Test a direct request to a published page, a refresh, and navigation from another route. Inspect the initial HTML, title and canonical metadata, browser console, and visible content. Then repeat with a missing route and a controlled loader error, checking the actual HTTP status as well as the error UI.

For protected content, test anonymous and authorized requests independently and inspect the serialized response for private data. Hydration is data transfer, not authorization. The React Router permission guide covers loader and API access checks, while the BTST installation reference supplies the complete framework wiring.

In This Post

Follow one request through the integrationKeep the generated request-aware loaderDiagnose the stage that is missingVerify direct requests and navigation