Giới thiệu và Cài đặt Nuxt 4
1. Nuxt là gì?
Nuxt = meta-framework xây trên Vue 3 — kèm sẵn:
- File-based routing.
- SSR / SSG / SPA / Hybrid rendering.
- Auto-import composable, component.
- Server engine (Nitro) — write API route trong cùng project.
- Module ecosystem cực rộng.
Tương đương Next.js cho React, SvelteKit cho Svelte.
2. Nuxt 4.0 — bản stable (07/2025)
Cấu trúc app/ mới
components/, pages/, layouts/, composables/) chuyển vào app/. Config & dependency ở root → tách biệt rõ.Smarter data fetching
useFetch/useAsyncData — auto cache theo key, dedupe request, cleanup khi unmount, reactive key tự refetch.TS chia 3 project
app/, server/, shared/ → type inference chính xác, server util không leak sang client.Performance (4.2)
Drop legacy
@nuxt/kit, một số legacy util gone. Migration codemod chạy được tự động.Migration painless
npx nuxt upgrade --dedupe + npx codemod@latest nuxt/4/migration-recipe — đa số project xong trong vài phút.3. Nuxt 3 vs Nuxt 4 — chỗ đổi đáng kể
| Nuxt 3 | Nuxt 4 | |
|---|---|---|
| Cấu trúc | Files ở root | app/ directory mặc định |
useFetch stale | Giữ data cũ khi refetch (stale-while-revalidate) | Clear data → null, pending=true |
| Share data cùng key | Không tự động | ✅ Tự động share |
| TS project | 1 project tổng | 3 project (app/server/shared) |
| Data cleanup | Manual | ✅ Auto khi component unmount |
| Module compat Nuxt 2 | Có | ❌ Drop |
Breaking change useFetch data behavior
<!-- Nuxt 3 — stale-while-revalidate -->
<script setup>
const { data, pending } = await useFetch("/api/users");
// Khi refetch — data cũ giữ, pending = true
</script>
<!-- Nuxt 4 — clear immediately -->
<script setup>
const { data, pending } = await useFetch("/api/users");
// Khi refetch — data = null, pending = true
// Muốn giữ behavior cũ: `getCachedData`
</script>
4. Yêu cầu hệ thống
- Node.js 18.x trở lên (khuyến nghị 20 LTS / 22 LTS).
- npm / pnpm / yarn / bun.
- Editor có Volar (Vue Language Server) — VS Code mặc định có.
5. Tạo project mới
# Khuyên dùng — nuxi (CLI chính chủ)
npx nuxi@latest init my-app
# Hoặc với pnpm
pnpm dlx nuxi@latest init my-app
# Vào project
cd my-app
pnpm install
pnpm dev
→ Mở http://localhost:3000.
Cấu trúc khởi tạo (Nuxt 4)
my-app/
├── app/ ← code app
│ ├── app.vue ← root component
│ ├── components/
│ ├── composables/
│ ├── layouts/
│ ├── middleware/
│ ├── pages/
│ ├── plugins/
│ └── assets/
├── server/ ← API routes, server middleware
│ ├── api/
│ ├── middleware/
│ └── plugins/
├── shared/ ← code share giữa app + server
│ ├── types/
│ └── utils/
├── public/ ← static files (favicon, images)
├── content/ ← (nếu dùng @nuxt/content)
├── .nuxt/ ← generated, ignore
├── nuxt.config.ts ← config chính
├── package.json
└── tsconfig.json
"Lớn nhất: cấu trúc
app/mới — code app tách khỏi config. TypeScript chia 3 project riêng (app/server/shared) → type chính xác hơn, server-only util không truy cập được từ client. Data fetching mới: tự share data cùng key, auto cleanup khi unmount, behavior khi refetch đổi từ stale-while-revalidate sang clear immediately."
6. nuxt.config.ts — file quan trọng nhất
// nuxt.config.ts
export default defineNuxtConfig({
// Compatibility mode
compatibilityDate: '2025-07-01',
// App config
app: {
head: {
title: 'My App',
meta: [{ name: 'description', content: 'My great app' }],
},
pageTransition: { name: 'page', mode: 'out-in' },
},
// Module
modules: [
'@nuxt/ui',
'@pinia/nuxt',
'@vueuse/nuxt',
'@nuxt/image',
'@nuxtjs/i18n',
],
// Runtime config — runtime accessible
runtimeConfig: {
apiSecret: '', // server-only (env: NUXT_API_SECRET)
public: {
apiBase: '/api', // exposed to client (env: NUXT_PUBLIC_API_BASE)
},
},
// CSS global
css: ['~/assets/css/main.css'],
// TypeScript
typescript: {
strict: true,
typeCheck: true, // chạy vue-tsc song song
},
// Nitro server
nitro: {
preset: 'node-server', // hoặc 'vercel', 'cloudflare', 'static'...
},
// Dev
devtools: { enabled: true },
});
Environment variable
# .env
NUXT_API_SECRET=my-secret # → runtimeConfig.apiSecret (server only)
NUXT_PUBLIC_API_BASE=https://api.x # → runtimeConfig.public.apiBase (cả client + server)
Dùng trong code:
const config = useRuntimeConfig();
config.apiSecret; // server only — undefined ở client
config.public.apiBase; // both
public mới expose client. Mọi key khác chỉ server, đừng nhầm.7. TypeScript trong Nuxt 4
3 TS project tự sinh trong .nuxt/tsconfig.*.json:
| Project | Scope |
|---|---|
tsconfig.app.json | Code trong app/ — client + universal |
tsconfig.server.json | Code trong server/ — Node.js, có Nitro types |
tsconfig.shared.json | Code trong shared/ — strict cả 2 môi trường |
fs từ component app/ → TS báo lỗi ngay (không có ở client). Trước Nuxt 3 chỉ runtime mới biết.8. Auto-import — đặc thù Nuxt
Bạn không cần import cho:
- Composable trong
app/composables/. - Component trong
app/components/. - Built-in composable:
useFetch,useState,useRoute,useRouter,useRuntimeConfig,useHead,useSeoMeta,defineNuxtPlugin... - Vue 3 reactivity:
ref,reactive,computed,watch,onMounted... - Utils trong
app/utils/vàshared/utils/.
<!-- Không cần import gì hết -->
<script setup lang="ts">
const count = ref(0); // auto-imported
const { data } = await useFetch("/api/users"); // auto
</script>
"Không. Auto-import được Nuxt scan và add import statement lúc build. Build output có import explicit như bình thường, tree-shake ngon. Chỉ là tiện DX khi viết, không phải global runtime."
9. Composables built-in cần biết
| Composable | Tác dụng |
|---|---|
useFetch(url) | Fetch + cache, SSR-aware |
useAsyncData(key, fn) | Generic async data với key, SSR-aware |
$fetch(url) | Fetch raw (không reactive) — dùng trong event handler |
useState(key, init) | Cross-component SSR-safe state |
useRoute() | Route hiện tại |
useRouter() | Programmatic navigation |
navigateTo(path) | Redirect (cả server + client) |
useRuntimeConfig() | Runtime config |
useHead(meta) | Modify <head> |
useSeoMeta(meta) | SEO-typed meta |
useCookie(name) | Cookie reactive |
useNuxtApp() | Truy cập Nuxt instance (plugin, $... helper) |
10. Khởi động Hello World
<!-- app/app.vue -->
<template>
<div>
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
<button @click="count++">Increment</button>
</div>
</template>
<script setup lang="ts">
const title = "Nuxt 4 — Hello!";
const count = ref(0);
useHead({
title: title,
meta: [{ name: 'description', content: 'My first Nuxt 4 app' }]
});
</script>
// server/api/hello.ts — server route đầu tiên
export default defineEventHandler(() => ({
message: "Hello from server!",
time: new Date().toISOString(),
}));
→ GET /api/hello trả JSON. Cùng project, không server riêng.