Kiến Trúc Next.js App Router + NestJS Cho Dự Án Enterprise 2026

Nguyễn Lê Đình Tiên
17 tháng 9, 2026

# Next.js App Router + NestJS Architecture
## Mục tiêu
Xây dựng hệ thống:
```txt
Browser
↓
Next.js App Router
↓
NestJS API
↓
Database
```
Yêu cầu:
- FE không lưu logic business.
- NestJS là hệ thống backend duy nhất.
- Không tạo thêm API BFF riêng để aggregate dữ liệu.
- Next.js chỉ đóng vai trò:
- Render UI
- Quản lý session
- Gọi NestJS
- Xử lý Authentication
- JWT không bị lộ ra JavaScript phía client.
- Hỗ trợ SSR và Client-side Fetch.
---
# Kiến trúc tổng thể
```txt
┌─────────────┐
│ Browser │
└──────┬──────┘
│
│ HttpOnly Cookie
▼
┌─────────────┐
│ Next.js │
│ App Router │
└──────┬──────┘
│
│ Authorization: Bearer xxx
▼
┌─────────────┐
│ NestJS │
└──────┬──────┘
│
▼
┌─────────────┐
│ Database │
└─────────────┘
```
---
# Cấu trúc thư mục đề xuất
```txt
src/
│
├── app/
│ ├── login/
│ │ └── page.tsx
│ │
│ ├── dashboard/
│ │ ├── page.tsx
│ │ └── loading.tsx
│ │
│ └── profile/
│ └── page.tsx
│
├── modules/
│ ├── auth/
│ │ ├── auth.service.ts
│ │ └── auth.types.ts
│ │
│ ├── user/
│ │ ├── user.service.ts
│ │ ├── user.mapper.ts
│ │ ├── user.types.ts
│ │ └── hooks/
│ │ └── useUser.ts
│ │
│ └── dashboard/
│ ├── dashboard.service.ts
│ ├── dashboard.mapper.ts
│ └── hooks/
│ └── useDashboard.ts
│
├── lib/
│ ├── api/
│ │ ├── api-client.ts
│ │ └── fetch-wrapper.ts
│ │
│ ├── auth/
│ │ ├── get-token.ts
│ │ ├── refresh-token.ts
│ │ └── logout.ts
│ │
│ └── constants/
│
├── components/
│ ├── ui/
│ ├── layout/
│ └── common/
│
├── middleware.ts
│
└── types/
```
---
# Nguyên tắc
## Không gọi NestJS trực tiếp trong Component
Không nên:
```tsx
export default async function Page() {
const data = await fetch(
"https://api.company.com/users"
);
...
}
```
Nên:
```tsx
const user = await userService.getProfile();
```
---
# Service Layer
Ví dụ:
```txt
modules/user/user.service.ts
```
```ts
export async function getProfile() {
return apiClient.get("/users/profile");
}
```
Mọi màn hình đều đi qua Service Layer.
---
# API Client
Tạo một nơi duy nhất quản lý request.
```txt
lib/api/api-client.ts
```
Ví dụ trách nhiệm:
- thêm JWT
- refresh token
- xử lý 401
- logging
- timeout
- retry
Toàn bộ project chỉ gọi:
```ts
apiClient.get()
apiClient.post()
apiClient.put()
apiClient.delete()
```
---
# Luồng Login
## Bước 1
User login:
```txt
Email
Password
```
↓
```txt
POST /auth/login
```
↓
NestJS trả về:
```json
{
"accessToken": "...",
"refreshToken": "..."
}
```
---
## Bước 2
Next.js lưu token vào Cookie
```txt
access_token
refresh_token
```
Thuộc tính:
```txt
HttpOnly
Secure
SameSite=Lax
```
JavaScript không đọc được.
---
# Request Flow
Sau khi login:
```txt
Browser
↓
Cookie
↓
Next.js
↓
NestJS
```
Khi gọi API:
```txt
Page
↓
userService
↓
apiClient
↓
NestJS
```
---
# JWT Handling
## Access Token
Mục đích:
```txt
Authentication
```
Thời gian sống:
```txt
15 phút
hoặc
30 phút
```
Ví dụ:
```txt
exp: 15m
```
---
## Refresh Token
Mục đích:
```txt
Tạo access token mới
```
Thời gian sống:
```txt
7 ngày
14 ngày
30 ngày
```
Ví dụ:
```txt
exp: 7d
```
---
# SSR Authentication Flow
Server Component:
```txt
Page Load
↓
Đọc cookie
↓
Lấy access token
↓
Call NestJS
```
Nếu thành công:
```txt
200
```
↓
Render UI.
---
# Xử lý 401
Khi access token hết hạn:
```txt
Next.js
↓
NestJS
↓
401
```
Không redirect ngay.
Thực hiện:
```txt
refresh token
```
---
# Refresh Flow
```txt
Call API
↓
401
↓
Call Refresh API
↓
Nhận Access Token mới
↓
Gọi lại API cũ
```
Flow:
```txt
Page
↓
NestJS
↓
401
↓
POST /auth/refresh
↓
New Access Token
↓
Retry Request
↓
200
```
User không nhận thấy quá trình này.
---
# Refresh Success
```txt
401
↓
Refresh thành công
↓
Retry
↓
200
```
UI vẫn hoạt động bình thường.
---
# Refresh Fail
Nếu:
```txt
Refresh Token hết hạn
```
hoặc
```txt
Refresh Token không hợp lệ
```
thì:
```txt
401
↓
Refresh
↓
401
↓
Logout
↓
Redirect Login
```
---
# Logout Flow
```txt
User Click Logout
```
↓
```txt
POST /auth/logout
```
↓
Xóa:
```txt
access_token
refresh_token
```
↓
```txt
Redirect /login
```
---
# Middleware
Chỉ xử lý:
```txt
Có cookie hay không
```
Ví dụ:
```txt
Không có access_token
```
↓
```txt
Redirect Login
```
Middleware không nên:
- refresh token
- gọi NestJS API
- thực hiện business logic
---
# Client Side Fetch
Sau khi page render:
```txt
User Click Filter
```
hoặc
```txt
User Search
```
có thể dùng:
```txt
TanStack Query
```
Flow:
```txt
Browser
↓
Next.js
↓
NestJS
```
Vẫn sử dụng chung:
```txt
apiClient
```
để đồng bộ cơ chế JWT.
---
# UI DTO Mapping
Không để UI phụ thuộc trực tiếp response từ backend.
Ví dụ NestJS:
```json
{
"id": 1,
"name": "Tien",
"email": "abc@gmail.com",
"address": "...",
"roles": []
}
```
Mapper:
```ts
UserProfileDto
```
```json
{
"name": "Tien"
}
```
Component chỉ dùng:
```ts
UserProfileDto
```
Điều này giúp:
- FE không phụ thuộc BE
- dễ thay đổi API
- dễ test
- dễ maintain
---
# Best Practices
✅ HttpOnly Cookie
✅ Access Token ngắn hạn
✅ Refresh Token dài hạn
✅ Service Layer cho toàn bộ request
✅ Một apiClient duy nhất
✅ Refresh tự động khi gặp 401
✅ Redirect login khi refresh thất bại
✅ DTO riêng cho UI
✅ TanStack Query cho client interaction
✅ Server Component cho dữ liệu SSR
---
# Kết luận
Kiến trúc đề xuất:
```txt
Browser
↓
Next.js App Router
↓
Service Layer
↓
Api Client
↓
NestJS
↓
Database
```
Trong mô hình này:
- NestJS là backend duy nhất.
- Không cần tRPC.
- Không cần BFF API Route.
- JWT được quản lý tập trung.
- FE không chứa logic auth phức tạp.
- Dễ mở rộng cho dự án lớn nhiều màn hình.


