Mandu vs Remix
Two routing-first React frameworks compared — loaders/actions vs filling chain, web standards vs MCP-driven agents.
On this page
Mandu vs Remix
TL;DR — Both are routing-first React frameworks built on web standards. Remix wires the UI to loaders/actions; Mandu separates UI from API via explicit contracts and adds runtime architecture Guard + 100+ MCP tools so agents can extend the codebase without breaking it.
Side by side
| Feature | Remix v2 | Mandu 0.46 |
|---|---|---|
| Runtime | Node.js, Cloudflare, Deno (adapter-based) | Bun ≥ 1.3.12 native |
| Data flow | loader / action exports per route |
Mandu.filling() chain or Mandu.contract({...}) + Mandu.handler(contract, ...) |
| Type safety end-to-end | Inferred from loader return types | Derived from a contract file (validates at runtime AND type-checks at compile) |
| Architecture enforcement | None at runtime | Guard rejects layer/import violations live |
| AI agent surface | None native | 100+ MCP tools for scaffolding/guard/test/deploy |
| Auto-generated tests | Manual | ATE generates contract tests from zod schemas |
| OpenAPI | Manual | Auto-derived from contracts |
| Deploy targets | Adapters per platform | First-class for CF Workers · Vercel · Fly · Railway · Netlify · Docker — mandu deploy --to=<target> |
Code comparison — fetching and listing users
Remix — loader + UI in the same file:
// app/routes/users.tsx
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { db } from "~/db.server";
export async function loader({ request }: LoaderFunctionArgs) {
const url = new URL(request.url);
const page = Number(url.searchParams.get("page") ?? 1);
const users = await db.users.findMany({ skip: (page - 1) * 20, take: 20 });
return json({ users, page });
}
export default function Users() {
const { users, page } = useLoaderData<typeof loader>();
return <ul>{users.map((u) => <li key={u.id}>{u.email}</li>)}</ul>;
}
Mandu — UI consumes a typed contract; same logic, explicitly separated:
// spec/contracts/users.contract.ts
export const usersContract = Mandu.contract({
request: { GET: { query: z.object({ page: z.coerce.number().default(1) }) } },
response: { 200: z.object({ users: UserSchema.array(), page: z.number() }) },
});
// app/api/users/route.ts
export default Mandu.handler(usersContract, {
GET: async (ctx) => ({
users: await ctx.db.users.findMany({ skip: (ctx.query.page - 1) * 20, take: 20 }),
page: ctx.query.page,
}),
});
// app/users/page.tsx
import { client } from "@mandujs/core/client";
import { usersContract } from "@/spec/contracts/users.contract";
export default async function UsersPage({ searchParams }: { searchParams: { page?: string } }) {
const { users, page } = await client(usersContract, { query: { page: searchParams.page } });
return <ul>{users.map((u) => <li key={u.id}>{u.email}</li>)}</ul>;
}
The contract is the single source of truth — your UI, server handler, and any third-party client all read from the same shape. An agent extending this only needs to read the contract.
Where Remix shines
- Progressive enhancement — Remix's
<Form>works without JS by default. Mandu requires the same care manually if you need it. - Tighter route↔UI coupling — when the loader is the UI's data, having both in one file is genuinely nicer for small apps.
- Mature catch-boundary / error UX — Remix has years of polish here.
Where Mandu shines
- Explicit contracts — when the same data feeds three callers (UI, mobile, internal CLI), one contract beats three loader copies.
- Runtime Guard — agents pasting code into a Mandu app get rejected on architectural violations the moment they save. Remix relies on team discipline + lint.
- Agent-driven scaffolding —
mandu_route_addandmandu_contract_createover MCP let an editor generate a fully-typed route in one chat turn.
🤖 Agent Prompt — sketch the migration
I have a Remix v2 app and I'm evaluating Mandu.
1. Pick one of my routes that has a loader + action + UI.
2. Show me the Mandu shape — contract file, API route using
Mandu.handler, and UI page consuming `client(contract)`.
3. Identify which Remix-specific patterns disappear (loader return
types, _action discriminator, fetcher data) and which require
manual replacement (progressive enhancement, useFetcher state).
4. Estimate the percentage of routes that would port cleanly vs
need rewriting.
5. List the top 3 risks of switching for my project.
Related
For Agents
Mandu and Remix both lean on web standards and file-system routing, but Mandu adds runtime architecture guard, contract-first APIs, and 100+ MCP tools so AI editors can drive the codebase. Remix focuses on progressive enhancement and tight loader/action coupling; Mandu focuses on agent-safe scaffolding and explicit contracts.