Architectural Subtraction in Nuxt 4
A content site does not need complex frontend state. Let routing, server rendering, and content queries each return to what they do best.

Many Nuxt projects become complicated not because the business is genuinely complex, but because we put every possible future into the architecture too early.
A personal blog is a useful calibration tool: it has a limited number of pages, a stable content structure, and interaction concentrated in a few areas. It reminds us that a framework’s most valuable contribution is often not adding capabilities, but removing unnecessary choices.
Begin with SSR by default
Technical articles need to be understood by search engines, and their body text should appear as early as possible on a slow connection. Nuxt’s default server-side rendering matches that goal.
export default defineNuxtConfig({
modules: ['@nuxt/content'],
nitro: {
preset: 'cloudflare',
},
})
There is no need to turn the whole site into an SPA or invent a separate loading strategy for every page. The page performs its first query and render on the server, and the client continues with the same router after taking over.
If one small area truly depends on browser APIs, use ClientOnly locally. Do not sacrifice the semantics and first screen of an entire page for one client component.
Keep content queries at the page boundary
The homepage, article index, and article detail page need different data shapes. Queries should stay close to the pages that consume them:
const { data: posts } = await useAsyncData('latest-posts', () => {
return queryCollection('posts')
.where('draft', '=', false)
.order('publishedAt', 'DESC')
.limit(6)
.all()
})
This has several direct benefits:
- The data participates in SSR instead of flashing into view after
onMounted. - The query key is explicit, so Nuxt can reuse the payload during hydration.
- The page knows what it needs without going through a generic store.
- Draft filtering and sorting rules appear where they are actually relevant.
Only extract a composable or store when several pages truly share the same complex business state.
Extract only stable repetition into components
“It might be reusable” is not a component boundary. Stable repetition usually includes structure, semantics, and behavior at the same time.
On this blog, an article card is a good component. It appears on both the homepage and the index, with consistent metadata, title, summary, tags, and navigation behavior.
The homepage hero, however, does not necessarily need to become five small components just because it is large. It appears once, and its internal elements work together as one visual narrative. Splitting it too early only spreads styling relationships across files.
A component is not a file-organization tool. It is a UI contract that can be understood and reused independently.
Keep server APIs narrow
Authentication and comments are runtime data, so they belong in server/api. Each endpoint does one job:
POST /api/auth/register
POST /api/auth/login
POST /api/auth/logout
GET /api/auth/session
GET /api/comments/:post
POST /api/comments/:post
Narrow interfaces make authorization rules easier to review. Reading comments is public, creating one requires a session, and logging out destroys only the current token without touching the user profile.
If an endpoint handles reads, writes, login, and format conversion at the same time, it has already lost its boundary.
Put state back in the right place
Vue’s reactive state is convenient, but not every state belongs in JavaScript:
| State | Best location |
|---|---|
| Current article | Route |
| Search term | Page ref |
| Tag filter | URL query + page state |
| Signed-in user | SSR-safe useState |
| Article body | Nuxt Content |
| Session truth | Server-side D1 |
When state lives in the right place, a great deal of synchronization logic disappears naturally. We no longer watch routes to repair a store, and we no longer use localStorage to pretend that authentication is trustworthy.
Subtraction does not mean fewer features
Good architectural subtraction does not take anything away from the user. It preserves the complexity budget for the parts people can actually feel:
- Clearer typography.
- A faster first screen.
- More reliable authentication.
- More comfortable mobile reading.
- A Markdown writing workflow that is easier to sustain.
Nuxt 4 provides more than enough capability. A mature project is not one that uses all of it, but one that knows which capabilities it can leave unused for now.
Comments
Sign in to join the discussion
Your email remains private; only your display name appears.