Islands settled for content-heavy surfaces in the Astro post. When the product is a React app, authenticated workflows, nested layouts, mutations that own the UX - you need a framework that treats routing and data as first-class, not as plugins you assemble from blog posts.
The tribal version of this post is "Next vs Remix vs React Router." The useful version is three axes: navigation model, data/mutation model, and deploy surface. Pick on those. Logos come after.
What I am comparing (spring 2025)
- Next.js (App Router): React meta-framework with file-based routes, Server Components by default, server actions / route handlers, and a strong Vercel affinity with portable Node/edge options.
- Remix: nested routes, loaders/actions as the data story, progressive enhancement as a design center, adapters for many hosts.
- React Router (framework mode / v7 lineage): the router library grown into a framework path, loaders/actions familiar to Remix users, Vite-centric tooling, SPA or SSR depending on how you assemble it.
React 19 is available (Dec 2024). RSC is stable in the Next App Router world. None of this makes Next mandatory, it makes "ignore RSC forever" a conscious choice, not an accident.
I am not ranking "best." I am naming failure modes of picking the wrong default for the job.
Axis 1: Navigation
| Need | Lean |
|---|---|
| Nested layouts with independent loading/error UI | Remix / RR nested routes; Next layouts too |
| Soft navigations + browser-native forms as baseline | Remix (and RR framework patterns) |
| Document-ish marketing + app under one roof | Next (or Astro + a separate app, see Astro post) |
| Client-only SPA, own the server later | React Router (library or SPA mode) + Vite |
Next App Router navigation is powerful and opinionated (RSC payloads, soft nav). Remix/RR keep a clearer "URL → loader → UI" story that maps cleanly to HTTP. If your team thinks in fetch + cache layers already, RR/Remix feel like home. If your team thinks in "server components render trees," Next feels like home.
Axis 2: Data and mutations
Sketch of the Remix/RR mental model:
tsx// app/routes/invoices.$id.tsx, illustrative Remix-style import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node'; import { Form, useLoaderData } from '@remix-run/react'; export async function loader({ params }: LoaderFunctionArgs) { const invoice = await db.invoice.find(params.id!); if (!invoice) throw new Response('Not found', { status: 404 }); return { invoice }; } export async function action({ request, params }: ActionFunctionArgs) { const form = await request.formData(); await db.invoice.update(params.id!, { status: String(form.get('status')), }); return null; // or redirect } export default function InvoiceRoute() { const { invoice } = useLoaderData<typeof loader>(); return ( <Form method="post"> <h1>{invoice.number}</h1> <select name="status" defaultValue={invoice.status} > <option value="draft">Draft</option> <option value="sent">Sent</option> </select> <button type="submit">Save</button> </Form> ); }
Next's App Router equivalent often splits across Server Components, route handlers, and server actions. That is more surface area and more power, also more ways to accidentally fetch on the client after the server already could have done it.
Decision criteria I actually use:
- Do mutations need to work without JS as a product requirement? → Remix / progressive-enhancement-first stacks win.
- Do we want RSC as the default composition model? → Next App Router.
- Do we already own a Vite SPA and need framework features without a rewrite religion? → React Router framework path is the least surprising migration for many Vite teams.
Axis 3: Deploy
| Constraint | Implication |
|---|---|
| Team already on Vercel, happy with it | Next friction is low |
| Must run on Cloudflare / custom Node / Fly | Check adapters; Remix and RR are often clearer multi-host stories |
| Edge-only, tiny cold starts | Prototype the real host early, not localhost Express |
| Static export only | Next can do it with constraints; Astro may still be the content answer |
"Deploy later" is how you discover your framework's Node APIs do not exist on Workers. I treat host choice as a week-one spike, not a launch surprise.
Continuity with the SPA / Vite SSR posts
- Vanilla Vite SPA: own everything, ship fast for authenticated tools.
- Vite SSR plugins: HTML for a few routes without full meta-framework overhead.
- Astro islands: content default, React as guest.
- This post: React app is the product, pick the framework that matches navigation + data + host.
If you are still arguing frameworks before scoring SPA vs MPA, go back two weeks. Framework choice is downstream of architecture.
Failure modes
Choosing Next because "everyone uses it." The talent pool is real; so is RSC education cost for a team that only knows CSR.
Choosing Remix/RR and then rebuilding RSC patterns by hand. You wanted Next's model; admit it.
SPA-mode forever with no loader discipline. You bought a framework and kept
useEffect waterfalls. The router was not the bottleneck, the data model was.
Ignoring React Router's dual identity. Library vs framework mode are different products. Name which one you adopted in the README.
Decision table
| If your center of gravity is… | Default |
|---|---|
| RSC + server-first React UI | Next App Router |
| Nested routes + loaders/actions + forms | Remix or RR framework |
| Vite SPA migrating upward gradually | React Router (framework) |
| Content with a few widgets | Astro (not these three) |
| Full control, small team, minimal stack | Vite CSR ± targeted SSR |
Takeaway
Next, Remix, and React Router are not personalities, they are bets on navigation, data, and deploy. Score the product on those axes, then pick the stack that matches. Tribe membership is not a deploy strategy.