# Server frameworks and hydration

Last reviewed: 2026-10-16. Basestack contracts target published
`@basestack/flags-react@1.2.4` / flags-js 1.2.3. Check the installed Next.js or
TanStack Start release before adapting framework boundaries. Read
[React](react.md) for client provider behavior and peer requirements.

## Exact server exports

Import from `@basestack/flags-react/server`:
- `createServerFlagsClient(config: SDKConfig): FlagsSDK`
- `fetchFlag(slug: string, config: SDKConfig): Promise<Flag>`
- `fetchFlags(config: SDKConfig, slugs?: string[], options?: { fallback?: Flag[];
  onError?: (error: unknown) => void }): Promise<Flag[]>`
- `FlagsHydrationScript` (optional inline serialization, not required below).

`fetchFlag` creates a new client per call. It may return an HTTP error value or
throw. `fetchFlags` handles thrown failures with a fallback, but fulfilled
single-flag error values still need inspection. Neither helper guarantees fresh
upstream data. Do not import hooks or browser components in a server-only module.

## Next.js App Router: server evaluation

This page is for a server-rendered app, not a static-export-only application.
For a static export, server evaluation happens at build time; use browser
evaluation or deliberately rebuild when flags change.

```tsx
import { fetchFlag } from "@basestack/flags-react/server";

export default async function CheckoutPage() {
  const projectKey = process.env.BASESTACK_PROJECT_KEY;
  const environmentKey = process.env.BASESTACK_ENVIRONMENT_KEY;
  if (!projectKey || !environmentKey) return <p>Standard checkout</p>;
  try {
    const flag = await fetchFlag("new-checkout", {
      projectKey,
      environmentKey,
      baseURL: "https://flags-api.basestack.co/v1",
      cache: { enabled: false },
      fetchImpl: (input, init) => fetch(input, { ...init, cache: "no-store" }),
    });
    return (
      <p>
        {!flag.error && flag.enabled === true
          ? "New checkout"
          : "Standard checkout"}
      </p>
    );
  } catch {
    return <p>Standard checkout</p>;
  }
}
```

This disables the local SDK and Next fetch caches, not the API/CDN cache. Follow
your Next.js version's route rendering policy to avoid accidentally prerendering
a flag decision intended to run per request.

## Initial flags across the server/client boundary

If client hooks need the same initial decision, obtain a narrow list using
`await fetchFlags(config, ["new-checkout"], { fallback: [] })`. Filter out
error values and validate `slug`, `enabled`, and payload shape before crossing
the boundary. Strip sensitive payloads entirely. For Pages Router JSON props,
normalize transport timestamps explicitly rather than assuming SDK `Date` types.

```tsx
"use client";

import { FlagsProvider, type Flag } from "@basestack/flags-react/client";
import { useMemo, type ReactNode } from "react";

export function HydratedFlags({
  initialFlags,
  projectKey,
  environmentKey,
  children,
}: {
  initialFlags: Flag[];
  projectKey: string;
  environmentKey: string;
  children: ReactNode;
}) {
  const config = useMemo(
    () => ({ projectKey, environmentKey }),
    [projectKey, environmentKey],
  );
  return (
    <FlagsProvider config={config} initialFlags={initialFlags} preload={false}>
      {children}
    </FlagsProvider>
  );
}
```

The server passes only reviewed public flags and evaluation identifiers to this
component, never the full server config. With `preload={false}`, a missing flag
can still be fetched by `useFlag` unless its `fetch` option is false. Keep the
same initial values on server and client to avoid hydration mismatches.
An inline `FlagsHydrationScript` is another option, but requires the same data
review and the application's CSP nonce handling. Never interpolate raw JSON into
an ad hoc script.

## Pages Router and TanStack Start

In Next.js Pages Router, evaluate in `getServerSideProps` with the same server
imports and error checks. Return only a safe boolean or reviewed JSON-compatible
snapshot in `props`. `getStaticProps` evaluates at build/revalidation time, not
on every request. Use a `/client` provider in the existing `_app` boundary only
if hooks are needed.

For TanStack Start, use `createServerFn({ method: "GET" }).handler(...)` from
`@tanstack/react-start` to wrap a server-only module using these same helpers.
Call it from a route loader; return only the validated public decision/snapshot,
not credentials. This follows the
[official server-function boundary](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions).
Do not call an ordinary loader server-only: loaders can run in the browser.
Server configuration is server-only `process.env` (or the deployment's server env
adapter). Vite client configuration is `import.meta.env.VITE_*`; Next.js browser
configuration uses explicitly public `process.env.NEXT_PUBLIC_*`. Never prefix
private API/MCP tokens with either public prefix.

Verify enabled/disabled/missing/error paths, initial-render consistency, route
rendering/cache timing, and absence of tokens or sensitive payloads in page
source, serialized props, scripts, and browser bundles. Enforce authorization
independently on the server.

Human docs: [Next.js](https://docs.basestack.co/content/feature-flags/sdks/backend/nextjs).
