jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Next.js 16 from Zero to Senior · Part 1 — Foundations & the App Router

Start Next.js 16 from zero: what the framework really is, install it with Turbopack, understand the App Router file conventions, and grasp the single most important idea — Server Components by default. With exercises.

Đây là Phần 1 của series 10 bài đưa bạn từ “mới nghe tới Next.js” đến trình độ senior trên Next.js mới nhất (phiên bản 16). Mỗi phần đều thực hành và kết thúc bằng bài tập.

Lưu ý trước: phần lớn tutorial trên mạng mô tả Next.js 13–15, nơi caching là ngầm định và gây rối. Next.js 16 đã thay đổi mặc định một cách lớn. Ta học mô hình mới ngay từ đầu, để khỏi phải bỏ thói quen xấu về sau.


1. Next.js thực ra là gì

React tự thân là một thư viện dựng UI — nó render component trong trình duyệt, chỉ vậy. Để ra sản phẩm thật bạn còn cần routing, data fetching, server rendering, bundling, tối ưu ảnh, và cách deploy.

Next.js là framework React full-stack cung cấp tất cả những thứ đó như một hệ thống thống nhất:

  • Routing theo file — thư mục trở thành URL, không cần cấu hình router.
  • Render ưu tiên server — component chạy trên server theo mặc định, gửi HTML (và rất ít JS) về client.
  • Backend ngay trong cùng project — route handler và Server Action cho phép viết code server cạnh UI.
  • Công cụ build — Turbopack (viết bằng Rust) bundle và hot-reload ứng dụng.

Mô hình tư duy cho cả series: Next.js là một server render React, và chỉ gửi JavaScript về trình duyệt khi một mẩu UI thực sự cần tương tác.

Hai router: vì sao ta chỉ dùng App Router

Next.js trong lịch sử có hai router:

  • Pages Router (pages/) — mô hình gốc, render phía client theo mặc định.
  • App Router (app/) — mô hình hiện đại xây trên React Server Components.

Series này dùng 100% App Router. Pages Router vẫn chạy cho app cũ, nhưng mọi tính năng mới của Next.js 16 — Cache Components, PPR, async API — đều nằm ở App Router.


2. Cài đặt & tạo app đầu tiên

Bạn cần Node.js 20.9+ (Next.js 16 đã bỏ các bản cũ hơn). Kiểm tra:

node -v   # must be >= 20.9

Tạo project bằng CLI chính thức:

npx create-next-app@latest senior-next

Bạn sẽ được hỏi vài câu. Cho series này, chọn:

✔ Would you like to use TypeScript?        … Yes
✔ Would you like to use ESLint?            … Yes
✔ Would you like to use Tailwind CSS?      … Yes
✔ Would you like your code inside a `src/` directory? … Yes
✔ Would you like to use App Router?        … Yes   (the default)
✔ Would you like to use Turbopack?         … Yes   (now the default)

Sau đó chạy dev server:

cd senior-next
npm run dev

Mở http://localhost:3000, bạn sẽ thấy trang khởi tạo.

Turbopack giờ là mặc định

Ở Next.js 16, Turbopack là bundler mặc định cho cả devbuild — webpack không còn được dùng trừ khi bạn chủ động chọn. Script trong package.json đơn giản là:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

Nếu một plugin chỉ hỗ trợ webpack bị lỗi khi migrate, bạn có thể tạm quay về:

next dev --webpack    # opt out of Turbopack (escape hatch)

Nhưng với app mới: cứ dùng mặc định — Turbopack cho hot reload dưới 200ms kể cả với graph module lớn.


3. Hình hài một project App Router

Sau khi tạo, các phần quan trọng trông như sau:

senior-next/
├── src/
│   └── app/
│       ├── layout.tsx        # root layout — wraps every page (required)
│       ├── page.tsx          # the "/" route
│       ├── globals.css       # global styles
│       └── favicon.ico
├── public/                   # static files served as-is (/logo.png)
├── next.config.ts            # framework configuration
├── tsconfig.json
└── package.json

Quy tắc vàng của App Router: một thư mục là một route segment, và các file đặc biệt gán hành vi cho segment đó.

Các file đặc biệt (hãy nhớ)

FileVai trò
page.tsxUI cho một route — làm segment có thể truy cập
layout.tsxKhung dùng chung bọc page và con; giữ state khi điều hướng
loading.tsxUI loading tức thì (fallback của Suspense)
error.tsxError boundary cho segment (phải là Client Component)
not-found.tsxUI cho 404 trong segment
route.tsMột API endpoint (không UI) — xem Phần 7
template.tsxGiống layout nhưng mount lại mỗi lần điều hướng

Ta sẽ đào sâu routing ở Phần 2. Bây giờ chỉ hai file quan trọng.

Root layout

Mọi app App Router bắt buộclayout.tsx gốc render <html><body>:

// src/app/layout.tsx
import type { Metadata } from 'next';
import './globals.css';

export const metadata: Metadata = {
  title: 'Senior Next',
  description: 'Learning Next.js 16 properly',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

children là page (hoặc layout lồng) đang active. Layout render một lần và giữ nguyên khi user điều hướng giữa các page con.

Một page

// src/app/page.tsx
export default function HomePage() {
  return (
    <main>
      <h1>Hello from a Server Component</h1>
    </main>
  );
}

Chỉ vậy — không đăng ký router, không import, không export const path. Vị trí file chính là route.


4. Ý tưởng quan trọng nhất: Server Components là mặc định

Đây là khái niệm tách biệt người dùng App Router với người hiểu nó.

Mọi component trong app/ là một React Server Component (RSC) trừ khi bạn nói khác đi.

Một Server Component:

  • Chạy chỉ trên server — code của nó không bao giờ gửi về trình duyệt.
  • Có thể asyncawait dữ liệu trực tiếp (database, file, API).
  • Có thể dùng secret an toàn (API key, DB credential) — chúng ở lại server.
  • Không thể dùng useState, useEffect, onClick, hay browser API.

Đây là một Server Component fetch dữ liệu không cần useEffect, không cần loading state, không cần API route:

// src/app/page.tsx  (a Server Component — the default)
export default async function HomePage() {
  // This runs on the server. The browser never sees this fetch,
  // never sees the URL, never sees any token you might attach.
  const res = await fetch('https://api.example.com/stats');
  const stats = await res.json();

  return (
    <main>
      <h1>Dashboard</h1>
      <p>Users: {stats.users}</p>
    </main>
  );
}

Server render cái này thành HTML rồi stream về trình duyệt. User thấy nội dung ngay, và không một byte JavaScript nào được gửi cho component này.

Khi cần tương tác: 'use client'

Khoảnh khắc bạn cần useState, một event handler, hay browser API, bạn chọn component đó thành Client Component bằng một directive ở đầu file:

// src/app/counter.tsx
'use client';

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

'use client' đánh dấu ranh giới nơi server render bàn giao cho trình duyệt. Mọi thứ được import vào một Client Component cũng được gửi về trình duyệt.

Ta dành hẳn Phần 5 cho Client Components. Điều cốt lõi bây giờ: mặc định dùng Server Components; chỉ dùng 'use client' ở các “lá” của cây cần tương tác.

   Server Component (page)         ← runs on server, ships 0 JS
     ├── Server Component (header)
     ├── Server Component (article)
     └── Client Component (LikeButton)   ← 'use client' boundary, ships JS

5. Dynamic theo mặc định — sự thiết lập lại của Next.js 16

Đây là thay đổi hành vi lớn so với các tutorial cũ.

Ở Next.js 13–15, kết quả fetch() được cache tự động, nghĩa là dữ liệu có thể cũ một cách âm thầm và bạn phải “chiến đấu” với framework để lấy dữ liệu mới.

Next.js 16, không gì được cache trừ khi bạn chủ động chọn. Theo mặc định, code động (bất cứ thứ gì fetch dữ liệu hay đọc request) chạy lúc có request, như một server bình thường.

// Next.js 16: this fetches fresh on every request by default.
const res = await fetch('https://api.example.com/price');

Để làm gì đó nhanh và cache được, bạn chủ động đánh dấu nó bằng directive mới 'use cache':

async function getPrice() {
  'use cache'; // opt IN to caching — covered in depth in Part 4
  const res = await fetch('https://api.example.com/price');
  return res.json();
}

Đừng lo về chi tiết — Phần 4 dành trọn cho Cache Components. Tư duy cần ghi nhớ ngay: caching là một quyết định chủ động, tự chọn trong Next.js 16, không phải mặc định ẩn.


6. Đọc output của dev server

Khi chạy npm run dev, hãy học cách đọc log:

  ▲ Next.js 16.2.0 (Turbopack)
  - Local:        http://localhost:3000
  - Environments: .env.local

 ✓ Starting...
 ✓ Ready in 1.1s
 ○ Compiling / ...
 ✓ Compiled / in 320ms
 GET / 200 in 28ms

Next.js 16 cải thiện log request. Một dòng GET / 200 cho mỗi request cho bạn biết route, status và thời gian. Khi có gì chậm, đây là manh mối đầu tiên.

Để soi sâu hơn, Next.js 16 có Devtools MCP server bạn có thể kết nối với công cụ AI để debug — ta sẽ dùng ở Phần 10.


7. next.config.ts — bảng điều khiển

Cấu hình nằm trong một file có type:

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  // We'll enable Cache Components in Part 4:
  // cacheComponents: true,
  images: {
    remotePatterns: [{ protocol: 'https', hostname: 'images.example.com' }],
  },
};

export default nextConfig;

Bạn không cần đụng phần lớn tùy chọn. Cái bạn sẽ gặp sớm là cacheComponents: true, nó mở khóa directive 'use cache' (không bật cờ này thì directive vô tác dụng).


8. Bài tập

Làm trước khi sang Phần 2 — tạo phản xạ quan trọng hơn đọc.

  1. Tạo & chạy: tạo app bằng create-next-app, chạy npm run dev, xác nhận có Turbopack trong banner khởi động.

  2. Hai route: thêm src/app/about/page.tsx để /about render <h1>About</h1>. Không cần cấu hình — tự chứng minh routing theo file.

  3. Fetch trên server: trong page.tsx, await fetch(...) và render stargazers_count. Mở DevTools → Network và xác nhận trình duyệt không hề gửi request đó.

  4. Tìm ranh giới: thử thêm useState vào page.tsx. Đọc lỗi. Rồi sửa bằng cách tách Counter tương tác ra file 'use client' riêng và import vào.

  5. Dự đoán JS: trước khi kiểm tra, dự đoán component nào ở bài 4 gửi JavaScript về trình duyệt, rồi xác nhận trong tab Network.


Phần tiếp theo

Giờ bạn đã tạo được app, hiểu các quy ước file của App Router, và nắm ý tưởng quan trọng nhất: Server Components là mặc định, Client Components ở các lá tương tác, và caching là tự chọn.

Phần 2, ta đào sâu routing — dynamic segment, route group, parallel & intercepting route, layout vs template, và các file loading/error/not-found giúp app cảm giác tức thì.