This episode breaks down Nuxt's architecture behind the scenes: the SSR, SSG, ISR, and SPA rendering modes, the role of Nitro as the server engine, the auto-import mechanism for components and composables, the project directory structure, and the boundary between server code and client code.

Now we get to the most important technical part: how Nuxt works behind the scenes. You don't have to memorize every internal detail, but understanding the main architecture will save you from a lot of confusion — for example, why a composable runs on the server, or why a page is rendered twice.
Episode 2 covers the four pillars of Nuxt's architecture: rendering modes, the Nitro server engine, auto-import, and the project structure. We will also look at how Nuxt determines the boundary between code that runs on the server and code that runs in the browser. This is the lens you will use to read all the episodes that follow.
Nuxt can render the same application with different strategies, even per page. Its four main modes:
You configure the mode with routeRules in nuxt.config.ts:
export default defineNuxtConfig({
routeRules: {
"/": { prerender: true },
"/produk/**": { swr: 300 },
"/dashboard/**": { ssr: false }
}
})prerender: true produces static HTML at build time, swr makes the page re-render after a set number of seconds, and ssr: false turns the page into a pure SPA.
Nitro is Nuxt's server engine. It renders pages on the server, serves APIs from the server folder, and produces deployment artifacts for many targets at once. Its advantage: you write server code once, then deploy to Node, Vercel, Netlify, Cloudflare, or even serverless functions without changing the code.
export default defineEventHandler(async (event) => {
const body = await readBody(event)
return { status: "ok", data: body }
})Nitro provides utilities like readBody(event) to read the request payload, getQuery(event) for the query string, and setCookie for managing cookies.
Nitro also has distributed storage accessible from server code, useful for caching and shared state:
const nilai = await useStorage("data").getItem("kunci")
await useStorage("data").setItem("kunci", "nilai")useStorage("data").getItem("kunci") reads from Nitro storage, whose abstraction is the same for the filesystem, Redis, or Cloudflare KV.
Nuxt automatically imports components from components, composables from composables, and utilities from utils. You use them directly in your template without an import line:
<template>
<div>
<SiteHeader />
<KartuProduk v-for="item in daftar" :key="item.id" :item="item" />
</div>
</template>
<script setup lang="ts">
const daftar = await useFetch("/api/produk")
</script><SiteHeader /> and <KartuProduk /> are used without imports because Nuxt scans the components folder automatically. useFetch is also auto-imported from Nuxt.
A module is a structured way to extend Nuxt. Famous examples: @nuxt/image, @nuxt/content, @pinia/nuxt, and @nuxtjs/i18n. Modules are registered in nuxt.config.ts and usually configure themselves when the project runs.
A modern Nuxt 4 project has the following structure:
app/
app.vue
pages/
components/
composables/
layouts/
middleware/
plugins/
server/
api/
nuxt.config.ts
package.jsonapp/ holds all the client-facing code, server/ holds the API and server middleware. The public/ folder is for static assets like images and favicons.
Every .vue file in pages becomes one route. Nested routes are created with a folder named after the parent page. app/pages/produk/index.vue becomes /produk, and app/pages/produk/[id].vue becomes /produk/1.
nuxt.config.ts is the single source of truth for build configuration, modules, and runtime config. Runtime config values can be accessed from both server and client code according to their visibility level — the details are in episode 8.
When a user opens a page, the flow goes roughly like this: Nitro receives the request, runs server middleware, renders the components on the server, sends the HTML plus a payload, then Vue takes over in the browser and brings interactivity to life through a process called hydration.
This is the concept most often misunderstood. Code in a page's <script setup> can run twice: once on the server during rendering, and once on the client during hydration. Certain composables only run on one side:
if (import.meta.server) {
console.log("berjalan di server")
}
if (import.meta.client) {
console.log("berjalan di browser")
}Use import.meta.server and import.meta.client to write side-specific code. Episode 6 will discuss how this pattern relates to data fetching.
Episode 2 lifts the veil on Nuxt's architecture: four rendering modes configurable per page through route rules, Nitro as a universal server engine, auto-import that eliminates import boilerplate, a standardized directory structure, and a clear server-client boundary.
Key takeaways:
routeRules.app/ and server/ structure separates client-facing code from server code.pages folder structure.<script setup> can run on the server and the client; know the boundary with import.meta.server and import.meta.client.In the next episode, episode 3, we will create your first real Nuxt project — using npx nuxi init, exploring the folder structure, running the dev server with hot reload, and configuring TypeScript, ESLint, and Prettier. It's time for you to start typing code.