Choosing Nuxt Over Angular for a Rewrite That Had to Get SSR Auth Right
Travelstart's existing B2C site ran on Angular, and when the call came to rewrite it as a platform that could serve however many brands the business signed up next, the easy answer was picking Angular again. Same stack the team already knew, same patterns already proven in production, nothing new to learn under deadline pressure. I almost did exactly that. Then I made myself write down what the new system actually needed before defaulting to what we already had lying around.
This isn't a framework popularity contest. It's the actual comparison I ran across Next.js, Nuxt, Angular, and SvelteKit before picking one to build a multi tenant platform on, one that had to handle a real bank partnership embedding the site inside a native app's WebView, where you can't lean on localStorage carrying a session the way you can in an ordinary browser tab.
what the rewrite actually needed
The requirements weren't about developer experience. They came directly from a partnership with a bank I'll call Tenant A here, and from wanting an architecture that wouldn't need a rewrite of its own in two years:
- predictable rendering on the server
- authentication that works without localStorage
- support for HttpOnly cookies
- support for SameSite restrictions
- correct behavior inside a WebView, where storage can be more limited than a normal browser
- an architecture the team could maintain for years, not just ship once
- minimal dependency risk
- access to UI libraries mature enough to not become a maintenance project of their own
In an environment like that, the correct authentication flow looks like this, not much room to improvise:
- HttpOnly cookies
- SameSite set to Lax or Strict
- a session store on the server
- session validation on every server rendered request
- no auth state trusted on the client
Browser -> Request
Server -> Validate cookie
Server -> Session store
Server -> Render SSR
Response -> HTML
// server-side session validation export async function getSession(req: { headers: { cookie?: string } }) { const cookie = req.headers.cookie; if (!cookie) { throw new Error("No session"); } return sessionStore.get(cookie); }
A framework with a complicated runtime makes a flow this strict harder to reason about, and harder to audit later once nobody remembers exactly why a particular check exists.
why runtime complexity matters more than speed benchmarks
Security risk here doesn't come only from known vulnerabilities. It also climbs whenever:
- runtime behavior is hard to explain to a new team member
- the boundary between server and client isn't obvious from reading the code
- multiple execution environments exist for the same request
- a lot of dependencies sit between your code and the actual response
- caching and rendering rules are implicit rather than written down
A framework with more layers is a framework that's harder to audit, and for something serving a bank partnership, auditability mattered more than shaving milliseconds off a benchmark.
Next.js
Next.js is one of the most capable frontend frameworks available, and also one of the hardest to keep a clear mental model of. A modern Next.js app can involve React Server Components, middleware, an edge runtime, server actions, hybrid rendering, and multiple caching layers stacked on top of each other, which means a single request can pass through several different execution contexts, the browser, a Node server, an edge runtime, and the RSC protocol layer, before a response comes back.
Every one of those layers is a place a mistake can hide. Real security issues in the Next.js ecosystem have included remote code execution, authentication bypass, and vulnerabilities that cause denial of service, not because the framework is careless, but because more moving parts means more places for something to go quietly wrong. For an app relying on strict cookie sessions, that same flexibility shows up as state leaking between requests, caching that serves the wrong thing to the wrong user, hydration mismatches, or client code running when nobody expected it to.
- very flexible
- an enormous ecosystem
- genuinely powerful
- a runtime that's hard to hold a full mental model of
- real risk of misconfiguration under that flexibility
Angular
Angular takes the opposite approach. Instead of flexibility, it enforces structure whether you want it that day or not, and gives you strict templates, dependency injection, sanitization built in, strong typing, predictable rendering, and security helpers you don't have to remember to add yourself.
// Angular's sanitizer, opted into explicitly this.sanitizer.bypassSecurityTrustHtml(html);
Once Angular's SSR is configured, and getting there takes real setup, the runtime behaves consistently. Next.js has a complicated runtime. Angular has a complicated setup and a runtime you can actually predict, and for a system meant to last years rather than get rewritten again soon, predictable runtime behavior is often the safer trade.
- heavier to work with day to day
- slower to build features in
- a real learning curve for anyone new to it
- but easier to audit
- fewer architectural mistakes slip through
- strong for a system that has to survive a long time
- built for large teams working on the same codebase
This is exactly why Angular is everywhere in enterprise environments, and it's also, not coincidentally, the framework the old B2C site already ran on. Reusing it for the rewrite would have been the technically defensible choice.
Nuxt
Nuxt's SSR model is simpler than Next's. File based routing, a structured server model, a predictable rendering flow, a smaller runtime, and fewer hidden layers to explain to whoever joins the project next.
It's also more flexible than Angular, which cuts both ways. Development moves faster, but correctness leans more on the team's own discipline than on the framework refusing to let you do the wrong thing.
-
a simple SSR model
-
a mature ecosystem
-
an authentication flow that's straightforward to implement correctly
-
a runtime you can actually predict
-
less enforced architecture than Angular
-
unsafe patterns are possible if nobody's watching for them
-
needs real linting and real review to hold the line Angular would have held by default
SvelteKit
SvelteKit has the smallest runtime of the four. Fast SSR, minimal dependencies, a simple server model, easy to audit, low runtime complexity, a smaller vulnerability surface just by having less surface area to begin with.
The cost is ecosystem size. Fewer mature enterprise UI libraries, fewer heavily audited packages, more that the team would have to build and maintain itself.
| Framework | Runtime size | Ecosystem | Maintenance cost |
|---|---|---|---|
| Next | large | very large | low |
| Angular | medium | large | medium |
| Nuxt | medium | medium | medium |
| Svelte | small | small | high |
Technically strong, and probably not the right call for a system that needs to keep shipping features for a bank partnership rather than spend time rebuilding things the ecosystem would otherwise have handed us.
authentication in a WebView changes the calculus
Some environments restrict or block outright:
- localStorage
- cookies from third parties
- sessionStorage
- SameSite cookies, depending on configuration
Banking apps, mobile WebViews, embedded browsers, and enterprise security containers all show up on that list, and Tenant A's WebView was exactly that kind of environment. Authentication had to lean entirely on HttpOnly cookies, a real server session, and validation happening on the server on every request, nothing assumed to survive on the client.
Frameworks with a simple SSR model handle that better than frameworks with a complicated one. A complicated runtime is exactly where state leaks, wrong caching, cookie handling bugs, and hydration bugs like to hide, and simplicity there wasn't a nice thing to have, it was the actual requirement driving the decision.
the actual tradeoff, once I stopped comparing frameworks in the abstract
For this project, in order, the requirements were: predictable SSR, authentication that only trusts cookies, correct behavior inside a restricted WebView, a runtime without many surprises left in it, and an architecture the team could maintain for years without dreading it.
Next.js is powerful, and its runtime is hard enough to audit that I didn't trust it here. SvelteKit is simple and fast, and its ecosystem was too small to bet a bank partnership on. Angular enforces the architecture I wanted and gives you a predictable runtime almost by default, which is the honest, defensible argument for picking it again.
I didn't. Angular was also the framework behind the system we were rewriting specifically because it had become slow to change and expensive to extend across however many brands the business wanted to add next. Picking it again would have solved the auth problem and quietly recreated the other one. Nuxt gave up some of Angular's enforced structure, and we made up that gap somewhere else, in the lint rules, the pre-commit checks, and the review discipline the rest of the guardrails work on this site covers, rather than by leaning on a heavier framework to hold the line for us by default.
Nuxt is what the white-label rewrite actually shipped on.