Frontend architecture decisions that survive product evolution
How to design Nuxt 4 component systems that stay maintainable as the product grows, the team scales and requirements shift. Real patterns from real migrations.
When you start a greenfield project, every architectural decision feels reversible. You pick Vue 3, add Nuxt on top, wire up Tailwind, and ship. Clean, fast, satisfying.
But six months later — after two pivots, a growing team, and a backlog of edge cases — those early decisions become load-bearing walls. Changing them costs weeks. Ignoring them costs trust.
This article is about the decisions that matter. Not the ones that make you feel clever on day one, but the ones that still feel right on month twelve.
Layering your composables
The composables/ directory in Nuxt has a habit of becoming a junk drawer. One day you have a clean useAuth.ts and useApi.ts. Six sprints later you have useAuth.ts, useAuthV2.ts, useAuthLegacy.ts, useAuthWithRefreshToken.ts, and a utils/auth-helpers.ts that imports from three of them.
The fix is intentional layering:
// composables/core/useAuth.ts — the public API
// composables/core/internal/useTokenManager.ts — implementation detail
// composables/core/internal/useSessionStore.ts — state management
The internal folder convention signals to the team: "import from here at your own risk." It won't stop anyone determined, but it sets a clear boundary. And in architecture, clear boundaries are half the battle.
Component contracts, not props drilling
Props are documentation. But documentation that nobody reads — especially when your UserCard accepts fourteen props and three of them are only used in one edge-case slot.
Instead of a flat prop list, group related concerns:
<script setup lang="ts">
interface UserCardProps {
user: { name: string; avatar: string }
permissions?: { canEdit: boolean; canDelete: boolean }
theme?: 'light' | 'dark' | 'system'
}
</script>
This is a contract. When a new developer opens the file, they see exactly what the component needs — not a wall of individual props that might or might not be related.
Migration strategies that don't freeze the team
Migrations are not optional. Vue 2 to 3, Nuxt 2 to 4, Options API to Composition API — these are the kind of work that product managers hate scheduling and engineers dread executing.
The pattern that works: strangler fig with parallel routing.
Run both apps side by side. Route new pages to Nuxt 4. Keep legacy routes on Nuxt 2. Use a reverse proxy to stitch them together. Migrate page by page, not all at once.
This costs more infrastructure upfront but saves months of blocked deployments and the morale hit of a "big bang" migration gone wrong.
Architecture isn't about picking the right library. It's about making decisions that give your future self options. The best architecture decision is the one you don't have to undo.
David Minguela
Senior Frontend Developer. Writing about architecture, TypeScript, and self-hosted infrastructure.