bedda.tech logobedda.tech
← Back to blog

Next.js 15 App Router vs Remix vs SvelteKit

Matthew J. Whitney
8 min read
nextjsjavascripttypescriptreactfrontend

The Next.js App Router was supposed to be the easy call. We'd been on Pages Router for years across multiple client engagements, the team knew React deeply, and Vercel's pitch for React Server Components was compelling on paper: less JavaScript shipped to the client, streaming out of the box, collocated data fetching that didn't require prop-drilling through layout trees. When we started scoping Nozio, a travel tech platform with serious SEO requirements and a heavy server-side data dependency, the framework decision felt almost academic.

Then we actually built it.

Three products later, including Crowdia (a community funding platform) and the Bedda.tech marketing site, I've got a much more honest read on where the App Router genuinely earns its complexity and where it still punishes you for believing the demos. This isn't a framework ranking post with a winner at the end. It's the breakdown I wish I'd had before committing to RSC architecture across all three codebases.


Why We Kept Picking Next.js App Router

The short answer: we picked it for different reasons each time, and that's actually the tell.

For Nozio, the decision was about streaming and SEO. The product surfaces a lot of dynamic travel data that needed to be server-rendered for search indexing, but the data sources had variable latency. Streaming with Suspense boundaries meant we could ship a meaningful shell to the browser fast and fill in slower data without blocking the entire page render. That part worked. It worked better than anything I'd seen in the Pages Router era and better than what Remix was offering at the time for our specific data topology.

For Crowdia, the driver was the React ecosystem. The team was already deep in React, we were integrating with several React-specific libraries for rich text editing and financial data visualization, and the switching cost to SvelteKit would have been real. Remix was the more honest alternative here, but the App Router's file-based routing with parallel routes was a better fit for Crowdia's dashboard architecture, where multiple independently-loading panels needed to coexist.

For the Bedda.tech site, we just wanted to move fast. We know the App Router. We know how to deploy to Vercel. The friction was low.

Three different justifications. That tells you something important: the App Router wins by default for React teams more often than it wins on technical merit. That's not a knock on Vercel. It's a structural reality that Remix and SvelteKit haven't fully overcome despite being genuinely excellent frameworks.


Where RSC Actually Delivered

Let me be specific about what worked, because I've read too many posts that hand-wave this.

On Nozio, the colocated server-side data fetching inside Server Components removed an entire class of API route boilerplate. In the Pages Router world, you'd write getServerSideProps, export it, wire it to your component, and manage the type contract between them. In the App Router, a Server Component just fetches directly. The resulting code is shorter, the data is typed at the source, and there's no serialization boundary to think about for that first render. For a codebase with dozens of data-fetching paths, that reduction in ceremony added up.

Streaming was the other genuine win. The React 19 streaming model that underpins App Router's Suspense integration means you can ship a fast first byte even when some data is slow. On Nozio, pages with external API dependencies that had variable response times stopped feeling sluggish even when the data was late, because the layout, navigation, and above-the-fold content were already in the browser.

The Next.js 15 release brought async request APIs that fixed one of the sharper early footguns: accessing cookies() or headers() in ways that accidentally opted your entire route into dynamic rendering. Making those APIs explicitly async forced you to be intentional, which is the right call even if the migration was annoying.


Where the Mental Model Still Breaks Down

Here's the part that doesn't make the conference talks.

The Server Component / Client Component boundary is conceptually clean and practically confusing in a large codebase. The rule is simple: if it needs interactivity or browser APIs, it's a Client Component. But the boundary has to propagate correctly through your entire component tree, and when you're integrating third-party libraries that weren't written with RSC in mind, you hit friction constantly. Adding "use client" to a wrapper component and watching it quietly pull a large subtree out of server rendering is the kind of bug that doesn't announce itself loudly.

On Crowdia, we had a situation where a charting library we'd committed to for financial visualizations required client-side rendering. That was expected. What wasn't expected was how much of the surrounding layout we ended up marking as client-rendered to avoid the boundary complexity. By the time we'd traced through the implications, a larger portion of the dashboard than we'd planned was hydrating on the client. The RSC model rewards greenfield code written with it in mind. It punishes integration work.

The caching model in Next.js 15 is better than it was in 13 and 14, but it's still the most common source of "why is this stale" confusion on the team. Vercel has acknowledged this publicly and the Next.js 15 docs are more explicit about the behavior now, but the number of distinct caching layers (fetch cache, Data Cache, Full Route Cache, Router Cache) means you need to understand all four and how they interact before you can reason confidently about what your users are seeing.

Remix's mental model here is genuinely simpler. Loaders run on every request by default. You opt into caching explicitly. That's the right default for teams that don't want to spend cognitive overhead on caching semantics. I've recommended Remix to clients whose data changes frequently and who don't have the team bandwidth to reason through Next.js's caching layers.


The Edge Runtime Problem

This one bit us on Nozio and it's not discussed enough.

The Next.js Edge Runtime is a constrained environment. It's not Node.js. It doesn't have access to the full Node.js API surface, and several libraries that work fine in a Node.js serverless function simply don't work at the edge. We ran into this with a dependency that used Node's crypto module in a way the Edge Runtime didn't support.

The fix was straightforward once we diagnosed it: move that route handler back to the Node.js runtime. But diagnosing it wasn't fast. The error messages from Edge Runtime incompatibilities aren't always clear, and the failure mode can be a silent build-time issue or a runtime error that only surfaces in production, depending on how the incompatible code is imported.

The broader issue is that "deploy to the edge" is presented as a performance default when it's actually a tradeoff. Edge functions have lower latency for compute, but they have cold start behavior, they can't use all your Node.js dependencies, and they can't connect directly to a database in most configurations without a connection pooler that supports edge environments. For Nozio, which had real database reads on several routes, the edge wasn't the right target for those handlers. We were more selective the second time around on Crowdia.

SvelteKit's adapter model handles this more transparently, in my opinion. You choose your deployment target and the adapter tells you what's supported. The Next.js model lets you mix runtimes per route, which is flexible, but it also means the constraints aren't visible until you hit them.


The Honest Comparison

Remix is the right call when your data model maps cleanly to the loader/action pattern, your team wants explicit control over caching, and you're not heavily invested in React libraries that haven't been tested in an RSC context. Remix's error handling and progressive enhancement story is also stronger out of the box. The Remix docs are dense but honest about the tradeoffs.

SvelteKit is the right call when you're not locked into React, you want a smaller runtime footprint, and you're deploying to a variety of targets. Svelte's compiled output is genuinely leaner than React's runtime, and SvelteKit's adapter system is more explicit about deployment constraints than Next.js. The tradeoff is ecosystem depth and the cost of leaving React behind.

Next.js App Router is the right call when you have a React team, you need serious SEO with streaming, and you're willing to invest in understanding the RSC mental model and the caching layers. The Vercel deployment story is genuinely frictionless. The ecosystem is the deepest. But you're accepting complexity that Remix and SvelteKit don't ask you to carry.

We picked it three times. We'd pick it again for the right projects. But we'd be more deliberate about the edge runtime from day one, more aggressive about auditing third-party library RSC compatibility before committing, and more explicit with the team about the caching model before the first production confusion.

The App Router is powerful and it's still maturing. Those two things are both true at the same time.

Have Questions or Need Help?

Our team is ready to assist you with your project needs.

Contact Us