Next.js 16 from Zero to Senior · Part 10 — Production, Testing & Debugging
Ship like a senior: performance budgets and bundle analysis, testing with Vitest and Playwright, deploying to Vercel and self-hosting with Docker (standalone output), debugging with the Next Devtools MCP, and a capstone project.
Giờ bạn có thể dựng gần như mọi thứ trong Next.js 16. Phần cuối này nói về khác biệt giữa “chạy trên máy tôi” và “đáng tin trên production” — hiệu năng, test, deploy, và kỹ năng debug tách senior khỏi phần còn lại.
1. Build production & phân tích bundle
next build chạy Turbopack tạo build tối ưu và in báo cáo theo route:
npm run build
Route (app) Size First Load JS
┌ ○ / 1.2 kB 98 kB
├ ◐ /product/[id] 2.1 kB 110 kB
└ ƒ /dashboard 3.4 kB 120 kB
○ (Static) prerendered as static content
◐ (PPR) partial prerender — static shell + dynamic holes
ƒ (Dynamic) server-rendered on demand
Đọc chú thích: ○ tĩnh, ◐ prerender một phần (PPR), ƒ động. Nếu một route bạn nghĩ là tĩnh lại hiện ƒ, có gì đó đang đọc request — truy tìm nó.
Để xem cái gì trong JS, chạy bundle analyzer:
npm i -D @next/bundle-analyzer
// next.config.ts
import withBundleAnalyzer from '@next/bundle-analyzer';
const analyze = withBundleAnalyzer({ enabled: process.env.ANALYZE === 'true' });
export default analyze(nextConfig);
ANALYZE=true npm run build # opens an interactive treemap
Săn các thư viện nặng vô tình ở client (date picker, vẽ chart, render markdown) và chuyển server-side hoặc lazy-load.
2. Ngân sách hiệu năng
Senior đặt con số, không cảm tính:
- First Load JS dưới ~120 kB mỗi route; điều tra cái nào lớn hơn.
- Core Web Vitals — LCP < 2.5s, CLS < 0.1, INP < 200ms.
- Giữ
'use client'ở các lá; mặc định Server Components (Phần 5). - Cache thứ dùng chung/ít đổi; stream thứ cá nhân/đổi nhanh (Phần 4).
Lazy-load component client nặng bằng next/dynamic:
import dynamic from 'next/dynamic';
const Chart = dynamic(() => import('./chart'), {
loading: () => <ChartSkeleton />,
});
3. Chiến lược test
Một kim tự tháp thực dụng cho app Next.js:
- Unit — hàm thuần, schema validate, logic DAL.
- Component — Client Component cô lập.
- E2E — luồng user quan trọng qua trình duyệt thật.
Unit & component test với Vitest
npm i -D vitest @testing-library/react @testing-library/jest-dom jsdom
// counter.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Counter } from './counter';
test('increments', async () => {
render(<Counter />);
await userEvent.click(screen.getByRole('button'));
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});
Async Server Component chưa test đầy đủ được trong jsdom — hãy test các hàm dữ liệu chúng gọi (unit) và phủ kết quả render bằng E2E.
E2E với Playwright
npm init playwright@latest
// e2e/todos.spec.ts
import { test, expect } from '@playwright/test';
test('user can add a todo', async ({ page }) => {
await page.goto('/todos');
await page.getByRole('textbox', { name: /title/i }).fill('Ship it');
await page.getByRole('button', { name: /add/i }).click();
await expect(page.getByText('Ship it')).toBeVisible();
});
Playwright chạy toàn bộ stack: Server Components, Server Actions, revalidation — đúng thứ user gặp.
4. Deploy lên Vercel
Đường không-cấu-hình: push lên Git, import repo trong Vercel, set env var. Vercel ánh xạ tính năng Next.js tự động — trang tĩnh tới CDN, vỏ PPR tới edge, route động và Server Action tới serverless function, proxy.ts tới edge/runtime. Cache tag và revalidateTag chạy ngay.
5. Tự host với Docker
Next.js chạy ở mọi nơi Node chạy. Cho container, bật standalone output để image chỉ chứa thứ cần thiết:
// next.config.ts
const nextConfig: NextConfig = {
output: 'standalone',
};
# Dockerfile (multi-stage)
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# standalone output bundles a minimal server + only required deps
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
docker build -t senior-next .
docker run -p 3000:3000 --env-file .env.production senior-next
Next.js 16 đã thay đổi vài hành vi bộ nhớ trong container — đặt giới hạn bộ nhớ thực tế và test dưới tải, nếu không container chạy lâu có thể “bất ngờ” trên production. Đặt Nginx phía trước cho TLS và cache tĩnh (xem series Nginx trên blog này).
6. Quan sát
instrumentation.ts— file gốc chạy một lần khi server khởi động; gắn OpenTelemetry / Sentry ở đây.- Web Vitals — báo cáo số liệu người-dùng-thật bằng hook
useReportWebVitals. - Log có cấu trúc — Next.js 16 cải thiện log build/request; gửi log tới nền tảng của bạn và cảnh báo theo tỉ lệ lỗi.
7. Debug với Next Devtools MCP
Next.js 16 có Devtools MCP server — tích hợp Model Context Protocol cho phép công cụ AI (và bạn) soi app đang chạy: thông tin route, chế độ render, hành vi cache, và chẩn đoán build. Kết nối nó với trợ lý AI trong editor để hỏi “tại sao /dashboard render động?” và nhận câu trả lời có căn cứ từ chính app.
Những bug bạn sẽ thực sự gặp
| Triệu chứng | Nguyên nhân khả dĩ |
|---|---|
| ”Lỗi “params should be awaited” | Đọc đồng bộ — hãy await (Phần 2) |
| Không khớp hydration | Date/random/localStorage Date/random/localStorage trong Client Component — hoãn vào useEffect (Phần 5) |
| Data cũ sau mutation | Quên revalidateTag/updateTag/revalidatePath (Phần 4, 6) |
'use cache' vô tác dụng | cacheComponents: true Chưa bật cacheComponents: true (Phần 4) |
| Secret lộ ra trình duyệt | Thiếu server-only, hoặc sai tiền tố NEXT_PUBLIC_ (Phần 3, 9) |
Route động ngoài ý muốn (ƒ) | Đọc cookies()/headers() không cache, không bọc <Suspense> (Phần 8) |
| 500 500 trên một Server Action | Input không validate hoặc redirect() bị nuốt (Phần 6) |
Biết bảng này chính là một phần lớn của việc làm senior — phần lớn sự cố production là một trong số này.
8. Dự án capstone
Dựng một app nhỏ nhưng hoàn chỉnh dùng mọi khái niệm trong series. Gợi ý: một dashboard “Links” (trình quản lý bookmark cá nhân).
Yêu cầu:
- Trang marketing công khai tại
/— tĩnh, có metadata + ảnh OG (Phần 1, 8). - Auth — đăng nhập/xuất với cookie session httpOnly và một DAL (Phần 9).
proxy.tschuyển hướng user chưa đăng nhập khỏi/app(Phần 7, 9).- Dashboard tại
/app— một trang PPR: vỏ thống kê đã cache + feed cá nhân hóa được stream (Phần 3, 4). - CRUD link qua Server Actions với validate zod, UI lạc quan, và
updateTag(Phần 4, 6). - API JSON công khai tại
/api/linkscho một client mobile giả định (Phần 7). - Lọc theo URL các link qua
searchParams(Phần 5). - Test — Vitest cho DAL + một Playwright E2E cho “thêm một link” (phần này).
- Deploy — lên Vercel và dưới dạng Docker image với
output: 'standalone'(phần này).
Nếu bạn dựng được cái này từ đầu đến cuối và giải thích vì sao mỗi quyết định render và cache được đưa ra, bạn đang ở trình độ senior trên Next.js hiện đại.
9. Đi đâu tiếp
- Đọc lại những thứ bạn dùng nhiều nhất trên production: docs về caching, rendering, và proxy.
- Dựng lại một side project bạn đã có trên App Router với mô hình Next.js 16.
- Đọc source: template
commercecủa Vercel là một codebase App Router thực tế dùng Server Components + Server Actions.
Tóm tắt series
1 Foundations & App Router — server-first, file routing, 'use client'
2 Routing deep dive — dynamic, groups, parallel/intercepting, layouts
3 Server Components & fetching — async data, no waterfalls, streaming
4 Cache Components — 'use cache', cacheLife, cacheTag, PPR
5 Client Components & URL state — minimal client, URL as state
6 Server Actions & forms — mutations, validation, optimistic, revalidate
7 Route Handlers & Proxy — APIs, cookies/headers, proxy.ts
8 Rendering, metadata & assets — SEO, OG, image/font/script
9 Auth, sessions & security — cookies, DAL, headers
10 Production & debugging — perf, tests, deploy, the bug table
Bạn bắt đầu khi chưa biết App Router là gì. Giờ bạn hiểu render ưu tiên server, mô hình caching rõ ràng của Next.js 16, mutation, API, auth, và cách ship cùng debug tất cả. Đó là bộ công cụ của senior. Giờ hãy đi dựng thứ gì đó thật.