LangENKO

SSR & Streaming SSR

Render on the server per request — including chunked Suspense streams.

since v0.22
On this page

SSR & Streaming SSR

SSR (server-side rendering) runs your components per request on the server, producing HTML before the browser ever sees the page. Prerender does the same thing but once, at build time; SSR is the on-demand version.

When to use SSR

Pick SSR over prerender when:

  • The page depends on the current user (session, cookies, JWT).
  • Data comes from a fast mutable source (primary DB, feature flags).
  • The URL space is too large to enumerate at build time.
  • You want fresh data on every request (no stale build).

If none of those apply, prerender is cheaper and faster.

Basic SSR route

Just fetch data in the component. Mandu infers SSR because the data is not available at build time:

// app/account/page.tsx
import { getSession } from "@mandujs/core";

export default async function Account() {
  const session = await getSession();
  if (!session) return <LoginPrompt />;
  const user = await db.user.findOne({ id: session.userId });
  return <AccountView user={user} />;
}

No generateStaticParams, no "use client" — Mandu SSRs this route.

Streaming SSR

For pages that wait on slow data, stream the HTML as it resolves using React Suspense:

// app/feed/page.tsx
import { Suspense } from "react";

export default function Feed() {
  return (
    <main>
      <Header />                                    {/* ships immediately */}
      <Suspense fallback={<FeedSkeleton />}>
        <Timeline />                                {/* slow DB query */}
      </Suspense>
      <Suspense fallback={<SidebarSkeleton />}>
        <RecommendedUsers />                        {/* slow external API */}
      </Suspense>
    </main>
  );
}

Mandu uses renderToStream under the hood. The browser receives the shell first (Header + skeletons), then each Suspense boundary streams in as its data resolves. Time-to-first-byte stays near-zero even when total page takes seconds.

Context inside a server component

The getRequestContext() hook gives you the current request:

import { getRequestContext } from "@mandujs/core";

export default async function Page() {
  const ctx = getRequestContext();
  const locale = ctx.request.headers.get("Accept-Language") ?? "en";
  // ...
}

Combine with the Filling API for handlers with middleware, guards, and lifecycle hooks.

SSR at the edge

Every SSR route compiles for four edge targets without code changes:

mandu build --target=workers       # Cloudflare Workers
mandu build --target=deno          # Deno Deploy
mandu build --target=vercel-edge   # Vercel Edge
mandu build --target=netlify-edge  # Netlify Edge

Edge SSR has the same semantics as origin SSR — the only difference is runtime APIs. Node-only packages fail at the edge; see Edge runtimes for the compatibility matrix.

SSR + Islands

SSR and islands compose. The server renders the full tree; islands hydrate the interactive parts:

// app/article/[id]/page.tsx (SSR)
import ReactionsIsland from "./reactions.island";

export default async function Article({ params }: { params: { id: string } }) {
  const article = await db.articles.findOne({ id: params.id });
  return (
    <main>
      <h1>{article.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: article.html }} />
      <ReactionsIsland articleId={article.id} />
    </main>
  );
}

Only ReactionsIsland ships JS to the browser. The rest of the article is static HTML over the wire.

🤖 Agent Prompt

🤖 Agent Prompt — SSR & Streaming SSR
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/ssr to my project.

Summary of the page:
Mandu SSR renders React components per request via renderToStream. Use for auth'd pages, user-specific content, or data that can't be known at build time. Streaming SSR uses React Suspense to ship HTML as it resolves.

Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
   in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.

For Agents

AI hint

Mandu SSR renders React components per request via renderToStream. Use for auth'd pages, user-specific content, or data that can't be known at build time. Streaming SSR uses React Suspense to ship HTML as it resolves.

Guard scope
architecture