When I started building Net Terms Tracker — a full-stack Shopify B2B app — I had to make a choice that would define the project's architecture for months: Remix or Next.js? I'd used both before, but this was the first time I was forced to pick one under real production constraints: Shopify's opinionated OAuth flow, a requirement for real-time server-side data mutations, and a tight integration with Shopify's Polaris design system.
I chose Remix. This post explains why — and also when I would choose Next.js instead.
The Core Mental Model Difference
Both frameworks are React-based, but they have fundamentally different philosophies about where data lives and how it moves.
Next.js is a platform for rendering React — it gives you flexibility to mix Server Components, Client Components, API Routes, and static generation however you see fit. The power is immense. The tradeoff is that you're constantly making architectural decisions: where does this data fetch? Is this a Server Component or a Client Component? Do I need an API route for this or can I use a Server Action?
Remix is built around the web platform itself — specifically around Request / Response and the nested routing model. Every route is a full data-fetching unit with a loader (server-side GET) and an action (server-side POST/PUT/DELETE). Your UI is always a function of server state. There's far less architectural ambiguity, which I've found invaluable on complex apps.
Why Remix Won for the Shopify App
Shopify's app development model maps almost perfectly to Remix's architecture. Here's why:
1. The OAuth flow is a form submission, not a client-side dance
Shopify's OAuth install flow is a series of redirects and token exchanges. In Remix, this is just a loader on the install route that reads the query params, exchanges the code for a token, and redirects. Clean, server-rendered, no hydration weirdness.
// app/routes/auth.$.tsx — Shopify OAuth handled in a loader
export async function loader({ request, context }: LoaderFunctionArgs) {
const { admin, session } = await authenticate.admin(request);
// If we reach here, auth succeeded. Redirect to app.
return redirect("/app");
}
2. Mutations are first-class citizens
The Net Terms Tracker is mutation-heavy: approving credit applications, updating credit limits, marking invoices paid. In Next.js, each of these would typically be a Server Action or an API route that the client calls. In Remix, each of these is just an action on the relevant route. The useFetcher hook handles the loading state automatically. Less code, less complexity.
// Approving a credit application — entire mutation in one place
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const applicationId = formData.get("applicationId") as string;
await db.creditApplication.update({
where: { id: applicationId },
data: { status: "APPROVED", reviewedAt: new Date() },
});
return json({ success: true });
}
3. Nested layouts with isolated data fetching
The admin dashboard has a persistent sidebar (merchant config, credit stats) that shouldn't re-fetch every time you navigate between sections. Remix's nested routing model handles this perfectly — the sidebar data lives in a parent route's loader, and child routes fetch only their own data independently. In Next.js with the App Router, this is achievable but requires more deliberate component boundary management.
When I'd Choose Next.js Instead
Remix is not a universal win. Here's where I'd reach for Next.js:
- Marketing sites with heavy static content. Next.js's SSG and ISR are best-in-class. Remix doesn't have a comparable static generation story — everything is server-rendered on request.
- Apps that rely heavily on React Server Components. The RSC ecosystem (server-only data streaming, Suspense integration, partial hydration) is maturing fast in Next.js. Remix has its own streaming model but it's different.
- Vercel-first deployments. If you're deploying to Vercel, Next.js integration is seamless — Edge Functions, Image Optimization, Analytics all just work. Remix on Vercel works but you're not a first-class citizen.
- Team familiarity. Next.js has a much larger community and more available developers. If you're hiring, this matters.
The Real Decision Framework
Choose Remix when: your app is mutation-heavy, your data is mostly dynamic (no static pages), you're building on a platform with server-driven flows (Shopify, OAuth-heavy apps), or you want a simpler data layer with less architectural decision-making.
Choose Next.js when: you need static generation, you're deep in the RSC ecosystem, you're on Vercel, you need a larger talent pool, or your app has a significant content/marketing surface area alongside the app itself.
Neither framework is universally better. The mistake I see founders make is treating this as a theological debate rather than a tool selection. Pick the one that maps to your problem. For B2B SaaS apps built on third-party platforms, Remix's server-first, mutation-centric model gives you a cleaner architecture with less ceremony. For content-heavy products that need fine-grained rendering control, Next.js's flexibility is unmatched.
The Bottom Line
After shipping Net Terms Tracker in Remix, I'd make the same call again. The loader/action model kept the codebase clean and the Shopify integration straightforward. But I'm also shipping a SaaS dashboard right now in Next.js 15 because the project has a heavy marketing site component and the team I'm working with knows Next.js.
Use the tool that fits the job. That's what a Forge does.