English

Trang chủ

Cài đặt Next.js 16 và tạo dự án đầu tiên

Từ thư mục trống đến trang chạy trên máy bạn mất khoảng một phút: cài Node.js, chạy một lệnh, rồi npm run dev. Trang này ghi lại đúng những gì create-next-app 16.4.4 viết ra hôm nay — sáu file nguồn in bên dưới giống từng byte với lần chạy đó — cùng những chỗ tài liệu không nói rõ: next lint không còn, Tailwind v4 cắm vào Turbopack, và một dự án mới đã có 16 cảnh báo mức moderate.

Mã trên trang này là một ứng dụng chạy được: 16/16 kiểm tra đạt ngày 02/11/2026. Phiên bản đã chạy thử: next@16.4.4, react@19.3.0, react-dom@19.3.0, typescript@5.9.3, eslint@9.39.5, tailwindcss@4.3.3, Node.js v26.0.0.

Chuẩn bị: Node.js 20.9 trở lên

Tài liệu Next.js yêu cầu Node.js tối thiểu 20.9, trên macOS, Windows (kể cả WSL) hoặc Linux. Gói next cài trong dự án cũng khai báo đúng điều đó trong engines (>=20.9.0), nên node -v là đủ để biết máy bạn chạy được hay không. Máy chạy thử trang này dùng Node.js v26.0.0.

Không cần cài Next.js toàn cục: npx tải create-next-app về chạy một lần, và npm có sẵn khi bạn cài Node.js. Dùng pnpm, yarn hay Bun thì thay npx bằng lệnh tương ứng và đổi --use-npm thành --use-pnpm, --use-yarn, --use-bun; các cờ còn lại giữ nguyên.

Một lệnh, không hỏi gì

Lệnh dưới đây là một dòng và trả lời sẵn mọi câu hỏi, nên nó chạy được trong CI hay trong script mà không dừng chờ bạn bấm Enter. Chép nguyên dòng, kể cả dấu ngoặc kép quanh '@/*': npx create-next-app@16.4.4 my-app --ts --app --tailwind --eslint --use-npm --import-alias '@/*' --no-src-dir --disable-git --yes.

--yes trả lời những câu còn lại bằng lựa chọn đã lưu hoặc mặc định. Tài liệu ghi mặc định được khuyến nghị: TypeScript, ESLint, Tailwind CSS, App Router, Cache Components, không có thư mục src/, kèm AGENTS.md; Turbopack là bundler mặc định. Cờ --no-… phủ định một mặc định, ví dụ --no-tailwind, --no-linter, --no-cache-components.

Một chỗ đo được mà tài liệu chỉ nói thoáng: create-next-app lưu lựa chọn của bạn cho lần sau, và --yes được phép dùng lại chúng — một lần chạy thiếu --eslint vẫn nhận cấu hình ESLint từ lần trước. Vì vậy trang này truyền đủ cờ thay vì tin vào --yes. --reset-preferences cũng không cứu được script: nó hỏi lại “Would you like to reset the saved preferences?” trước khi làm gì.

Công cụ còn git init và tạo commit đầu tiên, trừ khi có --disable-git. Thời gian đo trên máy này: 8 giây khi cache npm đã ấm, khoảng 46 giây khi phải tải gói về.

Công cụ viết ra những gì

Một dự án mẫu app-tw gồm 18 file ngoài node_modules: app/layout.tsx, app/page.tsx, app/globals.css, app/favicon.ico, next.config.ts, eslint.config.mjs, tsconfig.json, next-env.d.ts, package.json, AGENTS.md, README.md, năm file SVG trong public/, .gitignore và lockfile của trình quản lý gói bạn chọn.

Hai file dưới đây là hai file bạn sửa hằng ngày. layout.tsx khai báo <html>, <body>, font và metadata; page.tsx là nội dung trang /. Kiểu LayoutProps<"/"> không do bạn viết: next dev và next build sinh kiểu cho từng route vào .next/types, rồi tsconfig.json đọc thư mục đó vào.

Sáu file nguồn và cấu hình in trên trang này đã được đối chiếu từng byte với thứ create-next-app@16.4.4 viết ra trong lần chạy hôm nay — không phải bản chép tay rồi sửa cho đẹp.

app/layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({ children }: LayoutProps<"/">) {
  return (
    <html
      lang="en"
      className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
    >
      <body className="min-h-full flex flex-col">{children}</body>
    </html>
  );
}
app/page.tsx
import Image from "next/image";

export default function Home() {
  return (
    <div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
      <main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
        <Image
          className="dark:invert h-5 w-[100px]"
          src="/next.svg"
          alt="Next.js logo"
          width={100}
          height={20}
          priority
        />
        <div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
          <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
            To get started, edit the{" "}
            <code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
              page.tsx
            </code>{" "}
            file.
          </h1>
          <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
            Looking for a starting point or more instructions? Head over to{" "}
            <a
              href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
              className="font-medium text-zinc-950 dark:text-zinc-50"
            >
              Templates
            </a>{" "}
            or the{" "}
            <a
              href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
              className="font-medium text-zinc-950 dark:text-zinc-50"
            >
              Learning
            </a>{" "}
            center.
          </p>
        </div>
        <div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
          <a
            className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
            href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
            target="_blank"
            rel="noopener noreferrer"
          >
            <Image
              className="dark:invert h-[14px] w-4"
              src="/vercel.svg"
              alt="Vercel logomark"
              width={16}
              height={14}
            />
            Deploy Now
          </a>
          <a
            className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
            href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
            target="_blank"
            rel="noopener noreferrer"
          >
            Documentation
          </a>
        </div>
      </main>
    </div>
  );
}

Cấu hình mặc định của bản 16.4.4

next.config.ts mặc định bật cacheComponents và partialPrefetching, và cắm Tailwind CSS v4 vào Turbopack bằng một rule loader. Không có postcss.config.mjs và không có tailwind.config.js: Tailwind v4 đọc @import "tailwindcss" trong app/globals.css, còn theme khai báo bằng @theme ngay trong CSS.

tsconfig.json bật strict, thêm plugin next và đường dẫn @/* trỏ về gốc dự án. Công cụ viết sẵn, bạn không phải sửa gì để chạy. Đổi --no-src-dir thành --src-dir thì code vào src/app/ và đường dẫn đổi theo.

next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  cacheComponents: true,
  partialPrefetching: true,
  turbopack: {
    rules: {
      "*.css": {
        loaders: ["@tailwindcss/turbopack"],
        as: "*.css",
      },
    },
  },
};

export default nextConfig;
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts",
    ".next/dev/types/**/*.ts",
    "**/*.mts"
  ],
  "exclude": ["node_modules"]
}

Chạy: dev, build, start

cd my-app && npm run dev mở http://localhost:3000; sửa file là trình duyệt cập nhật. Bản production thì npm run build rồi npm run start. Next.js 16 dùng Turbopack cho cả hai lệnh; muốn Webpack thì thêm --webpack.

Trang / của dự án mới không đọc gì từ request, nên nó được render sẵn một lần lúc build — đã kiểm tra trong prerender-manifest.json. Trình duyệt vẫn phải tải runtime React và Next.js: đo được 178 KB (gzip) cho một trang. Đó là mức nền của App Router, không phải vì trang mẫu nặng.

Font Geist khai báo trong layout.tsx được tải một lần lúc build rồi tự phục vụ: 11 file .woff2 nằm trong .next/static/media, và HTML không trỏ tới fonts.gstatic.com. Hệ quả thực tế: lúc build cần mạng (hoặc cache) để lấy font, còn người xem thì không gọi Google.

Lint: `next lint` đã bị bỏ ở Next.js 16

Script lint do công cụ sinh ra là eslint, không phải next lint. Chạy thử next lint trên 16.4.4: nó hiểu lint là tên thư mục, in “Invalid project directory provided, no such directory: …/lint” và thoát với mã 1. Tài liệu xác nhận từ Next.js 16, next build không chạy linter nữa — bạn chạy linter qua script.

npm run lint trên dự án vừa sinh thoát 0. Dự án cũ còn next lint trong package.json thì đổi script, hoặc chạy codemod npx @next/codemod@canary next-lint-to-eslint-cli .

npm cũng cảnh báo eslint@9.39.5 trong mẫu không còn được hỗ trợ. Đó là gói công cụ, không phải lỗi của bạn; nâng ESLint khi bạn sẵn sàng, đừng để nó chặn việc tạo dự án.

Một dự án mới vẫn có cảnh báo bảo mật

npm audit trên dự án vừa tạo báo 16 lỗ hổng mức moderate, 0 high, 0 critical. Phần lớn nằm trong chuỗi công cụ phát triển (ESLint và các gói con của nó), không phải mã chạy trên máy chủ của bạn.

Đừng chạy npm audit fix --force cho xong: nó có thể đổi major của một gói bạn không định nâng. Đọc danh sách, nâng gói nào nâng được, và ghi lại phần còn lại.

Việc cần làm ngay là biết mình đang chạy bản nào: npx next --version, hoặc dán địa chỉ trang vào công cụ kiểm tra của chúng tôi, rồi đối chiếu với danh sách lỗ hổng Next.js ở các liên kết bên dưới.

Thêm Next.js vào dự án có sẵn

Không muốn dùng mẫu: npm i next@latest react@latest react-dom@latest, thêm bốn script dev, build, start, lint, rồi tạo app/layout.tsx (bắt buộc, phải có <html> và <body>) và app/page.tsx. Khi bạn đổi một file sang .tsx, next dev tự tạo tsconfig.json, next-env.d.ts và cài @types/*; TypeScript cần từ 5.1 theo tài liệu.

Hai thứ dễ quên: .next, node_modules và next-env.d.ts phải nằm trong .gitignore (mẫu đã có sẵn), và next-env.d.ts không được sửa tay — nó được sinh lại mỗi lần chạy.

Đang ở Pages Router mà muốn sang App Router thì đó là việc nâng cấp, không phải cài mới: xem hướng dẫn nâng cấp lên Next.js 16 bên dưới trước khi đổi cấu trúc thư mục.

Đã kiểm tra

  • create-next-app with the page's flags, no prompt: exit 0 in 8s, Success!
  • the files the page prints are the files create-next-app writes: 6/6 identical
  • public/ still carries the five starter SVGs: 5/5
  • same dependencies, so the page's project matches the tool: @tailwindcss/turbopack @types/node @types/react @types/react-dom eslint eslint-config-next next react react-dom tailwindcss typescript
  • the version create-next-app puts in package.json: 16.4.4
  • scripts: dev, build, start, lint: next dev next build next start eslint
  • Node.js against next's engines field (>=20.9.0): v26.0.0, ok
  • next --version: Next.js v16.4.4
  • next build: / is prerendered: build time
  • GET / with no JavaScript: the starter heading is in the HTML: 200, present
  • public/ is served as static files: 200 image/svg+xml
  • next/font: Geist self-hosted at build (woff2 in .next/static/media), no fonts.gstatic.com in the HTML: 11 woff2, 0 external
  • JavaScript the browser loads for / (gzip): 178 KB in 7 files
  • `next lint` on 16: exit 1: Invalid project directory provided, no such directory: <project>/lint
  • npm run lint (eslint on the generated code): exit 0
  • npm audit on the freshly generated app: 16 total: 0 critical, 0 high, 16 moderate, 0 low

Chạy lại kiểm tra: bun scripts/guides/verify-install.ts

Liên quan

Nguồn

Tóm tắt và diễn giải bằng lời của chúng tôi; mã viết lại từ ví dụ trong tài liệu Next.js. Khi có khác biệt, tài liệu Next.js là chuẩn.