Rendering Modes
Mandu blends eight rendering strategies — here's when to use each.
On this page
Rendering Modes
Mandu is not one rendering strategy — it composes eight. Most pages pick a primary mode and mix in islands for interactivity.
The eight modes
| Mode | Runs | Output | Good for |
|---|---|---|---|
| Prerender | Build time | Static HTML | Docs, marketing, blog |
| SSR | Per request | HTML | Auth'd pages, user data |
| Streaming SSR | Per request | Chunked HTML | Slow dependencies, Suspense |
| Island | Client, after SSR/prerender | JS bundle mounts on placeholder | Interactive widgets inside static pages |
| Partial | Client, after SSR/prerender | Smaller than island | Single buttons, form fields |
"use client" page |
Client | Full React app on page | Dashboards, editors, games |
| SPA navigation | Client, on link click | Patches DOM, no reload | Multi-page feel, single-page speed |
| Edge SSR | Per request, at edge PoP | HTML (low latency) | Global users, per-region content |
Decision tree
Does the page depend on the request (user, cookies, query)?
├── NO → Prerender (fastest, zero server cost)
│
└── YES → How fast does the data resolve?
├── Fast (<200ms) → SSR
├── Slow / parallel → Streaming SSR with Suspense
└── Fully interactive? → "use client" page
Need interactivity inside a static/SSR page?
└── Use an Island (.island.tsx) or Partial
├── Many children interactive → Island
└── One button, one form → Partial
Mixing modes
A single Mandu route can use three modes at once:
// app/dashboard/page.tsx (server component — SSR)
import ChartIsland from "./chart.island"; // <-- Island (client-hydrated)
import { DeletePartial } from "./delete.partial"; // <-- Partial
export default async function Dashboard() {
const user = await loadUser(); // Server SSR
return (
<main>
<h1>Welcome, {user.name}</h1> {/* SSR */}
<ChartIsland data={user.stats} /> {/* Island */}
<DeletePartial.Render id={user.id} /> {/* Partial */}
</main>
);
}
The server renders the full tree once (HTML ships to browser), then the client hydrates only the island and partial — the rest stays static.
Default mode
If you write app/foo/page.tsx with no annotations:
- No dynamic data → prerendered at build time
- Has
export async function generateStaticParams()→ prerendered per param - Has server-only data fetching in the component → SSR per request
- File starts with
"use client"→ full client-rendered page (no prerender)
Mandu picks prerender by default when possible — it's the fastest path.
Edge vs origin
Set --target=workers | deno | vercel-edge | netlify-edge at build time
(mandu build --target=workers). Edge SSR runs the same code as origin SSR
but closer to the user. Not every Node API works at the edge — see
Deploy to Cloudflare Workers for the compatibility list.
🤖 Agent Prompt
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/architect/rendering-modes to my project.
Summary of the page:
Mandu supports 8 rendering modes. Pages default to prerender + island hydration. Use SSR for user-specific data, streaming SSR for slow pages with Suspense, 'use client' for fully interactive pages, partials for fine-grained hydration.
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.
Related
- Prerender — the default mode in depth
- SSR and Streaming SSR — runtime server rendering
- Client rendering —
"use client", islands, partials - Smooth navigation — SPA-style transitions
For Agents
Mandu supports 8 rendering modes. Pages default to prerender + island hydration. Use SSR for user-specific data, streaming SSR for slow pages with Suspense, 'use client' for fully interactive pages, partials for fine-grained hydration.