Who we are and why this even matters
We run a SaaS product that quietly does $40 M ARR. Our admin dashboard has 480 different pages, 1 200+ tables, 3 000+ form fields, and gets hammered by 120 000 active enterprise users every single day. Most of them are on 4G in Asia or on corporate laptops behind firewalls that hate big bundles.
From 2021–2024 we were 100 % React + Next.js (first pages router, then app router). We loved the hype, we drank the Kool-Aid, we even had a “React 19 early adopters” sticker on the office wall.
Then in early 2025 we quietly rewrote the whole thing in Vue 3 + Nuxt 3.
Nobody outside the team noticed. Inside the team? Everyone suddenly became 3–5× more productive and dramatically happier.
The cold, hard numbers after nine months in production
| Metric | React + Next.js 14 (early 2025) | Vue 3 + Nuxt 3.13 (now) | Real-world impact |
|---|---|---|---|
| Mobile LCP (75th percentile) | 2.8 s | 1.4 s | Customers literally message support less |
| Gzipped production bundle (average page) | 298 KB | 138 KB | Saved ~$2.1 M in bandwidth YoY |
| Monthly frontend bugs that reached Sentry | 38 | 11 | Fewer pages on-call at 3 a.m. |
| Time for a junior to ship a new CRUD page | 2–5 days | 3–5 hours | We stopped needing seniors for everything |
| Number of useEffect + useState lines | ~42 000 | ~4 800 | −89 % |
| Team happiness score (quarterly poll) | 6.2 → 9.7 | People stopped leaving |
The real reasons we switched (not the marketing ones)
- We ship boring enterprise features, not demos 95 % of our tickets are “add three new columns”, “make this filter multi-select”, “export to Excel”. In Vue + <script setup> + Nuxt you can do that in one file, one afternoon, zero meetings.
- Template type-checking with Volar is black magic You literally get compile-time errors if your v-model type is wrong. React + TypeScript still lets half of those slip to runtime in 2025.
- Nuxt’s useAsyncData + $fetch + <Suspense> is the simplest data layer I’ve ever used One line on the server, one line on the client, automatic deduplication, automatic loading states, automatic error boundaries. We deleted TanStack Query, SWR, and 47 custom “useFetch” hooks.
- Bundle size is a P&L line item for us We have users in rural Indonesia on 200 KB/s connections. Shaving 160 KB off the average page is worth seven figures a year.
- Partial Prerendering in Nuxt 3 actually works in production right now Next.js still ships half of it as “experimental” in late 2025. See the docs yourself: https://nuxt.com/docs/guide/concepts/partial-prerendering
The migration timeline (four weeks, zero drama)
Week 1 – “Proof of concept” Took the 30 most complex pages (multi-step forms, real-time tables, role-based rendering) and rewrote them one-by-one in Nuxt. Goal: prove we weren’t going to paint ourselves into a corner.
Week 2 – Codemod party Wrote a TypeScript AST script that turned 90 % of app/page.tsx → pages/*.vue automatically. Still the best weekend I’ve ever spent coding.
Week 3 – The great deletion
- rm -rf app/ (yes, the entire directory)
- Deleted Redux toolkit + 28 slices
- Deleted TanStack Query + devtools
- Deleted 52 custom hooks
- Deleted next-themes, next-auth wrappers, custom error boundaries
- Deleted the “use client” directive from 380 files (because we almost never need it anymore)
Week 4 – Shadow traffic + flip the switch Ran both versions side-by-side for three days, flipped on a Friday at 11 p.m. Beijing time, went to sleep.
Woke up to zero alerts.
The day the React evangelist went quiet forever
Day 12 after launch.
Product manager drops a ticket Monday morning: “Need bulk CSV import with live progress bar + server-side validation + rollback on error.”
In the old stack this would have been a two-week saga involving WebSockets, Redis pub/sub, and three seniors.
One mid-level dev using Nuxt Server Routes + streaming response + <Suspense> shipped the entire thing before happy hour.
The feature is now live: https://app.ourtool.com/settings/import (login required, sorry).
Slack message from the biggest React loyalist at 4:12 p.m.:
“Okay… I have no words. That was disgusting.”
Our exact stack today (Dec 2025)
- Vue 3.5 + <script setup> (the only way)
- Nuxt 3.13 + Nitro + built-in PPR
- Pinia only for tiny UI state (sidebar collapsed, dark mode, last selected org)
- useAsyncData / $fetch for everything else
- Tailwind + Headless UI + VueUse for the few interactive bits
- Zero custom data-fetching library
- Zero “use client” in 96 % of files
Useful links we bookmark every day:
- Nuxt data fetching docs (read this twice): https://nuxt.com/docs/getting-started/data-fetching
- Vue 3.5 reactivity deep dive (explains why it’s still faster): https://vuejs.org/guide/best-practices/performance.html
- Volar template types (the bug-killer): https://volar.js.org/guide/template-types.html
- Nuxt 3 layers + modules (how we keep 480 pages organized): https://nuxt.com/docs/guide/directory-structure/layers
The uncomfortable truth nobody wants to say out loud
React still has more jobs, more Twitter hype, and more conference talks.
Vue has quietly become the most productive, lowest-drama, highest-velocity stack for anything that isn’t a marketing site or a viral consumer app.
We now ship features so fast that product managers have stopped asking for estimates — they just assume it’ll be done same week.
We went from being the “slow” frontend team to the team that unblocks everyone else.
And yes, we’re hiring. Must love shipping code and hate ceremony.
Next long read already in the chamber: “How we accidentally achieved 99.9 % test coverage without writing a single test”