LangENKO

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 scaffoldingmandu_route_add and mandu_contract_create over MCP let an editor generate a fully-typed route in one chat turn.

🤖 Agent Prompt — sketch the migration

🤖 Agent Prompt — Compare a Remix route against Mandu
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.

For Agents

AI hint

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.