# Checklist Migration Next.js 14 → Next.js 15

> **Thời gian ước tính:** 2–8 giờ tùy quy mô dự án  
> **Áp dụng cho:** Next.js 14.x → 15.x (App Router & Pages Router)  
> **Cập nhật lần cuối:** 2024 — dựa trên Next.js 15 stable release

---

## Mục lục

- [1. Chuẩn bị](#1-chuẩn-bị)
- [2. Cài đặt & Nâng cấp](#2-cài-đặt--nâng-cấp)
- [3. Breaking Changes — Fix thủ công](#3-breaking-changes--fix-thủ-công)
- [4. Bật tính năng mới](#4-bật-tính-năng-mới)
- [5. Kiểm tra sau Migration](#5-kiểm-tra-sau-migration)

---

## 1. Chuẩn bị

### 1.1 Backup & Version Control

- [ ] **Tạo branch riêng cho migration**
  ```bash
  git checkout -b feat/migrate-nextjs-15
  ```
  > ⚠️ Không làm thẳng trên `main`/`master`. Branch riêng giúp rollback dễ dàng nếu có sự cố.

- [ ] **Commit toàn bộ code đang có** (working tree sạch trước khi bắt đầu)
  ```bash
  git add . && git commit -m "chore: snapshot before Next.js 15 migration"
  ```
  > Đảm bảo `git status` trả về clean trước khi chạy bất kỳ lệnh nào.

- [ ] **Backup `package.json` và `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`**
  ```bash
  cp package.json package.json.bak
  ```
  > Dùng để so sánh dependency trước/sau khi có lỗi conflict.

- [ ] **Tag release hiện tại trên git**
  ```bash
  git tag v-pre-nextjs15-migration
  git push origin v-pre-nextjs15-migration
  ```
  > Dễ tìm lại điểm rollback chính xác về sau.

---

### 1.2 Đọc Changelog & Release Notes

- [ ] **Đọc official upgrade guide của Next.js 15**
  > 🔗 https://nextjs.org/docs/app/building-your-application/upgrading/version-15  
  > Đọc kỹ phần **"Breaking Changes"** — ghi lại những mục ảnh hưởng đến dự án bạn.

- [ ] **Đọc Next.js 15 Release Blog**
  > 🔗 https://nextjs.org/blog/next-15  
  > Hiểu tổng quan các tính năng mới: PPR, Server Actions stable, Turbopack dev stable, `next/after`.

- [ ] **Kiểm tra các dependency quan trọng có hỗ trợ Next.js 15 chưa**
  > Danh sách cần kiểm tra nhanh:
  ```
  □ next-auth / auth.js      → v5+ (beta) tương thích Next.js 15
  □ next-intl                → v3.x+ tương thích
  □ @tanstack/react-query    → v5 tương thích
  □ next-themes              → kiểm tra peer dependencies
  □ @next/font               → đã được tích hợp vào core, xóa package riêng
  □ next-seo                 → kiểm tra version mới nhất
  ```
  > Chạy `npx npm-check-updates` để xem tổng quan các package cần update.

- [ ] **Ghi lại các Custom Middleware, Edge Runtime code trong dự án**
  > Liệt kê tất cả file `middleware.ts`, route handlers dùng `EdgeRuntime`, vì đây là vùng thay đổi nhiều nhất.

---

### 1.3 Cập nhật Node.js

- [ ] **Kiểm tra version Node.js hiện tại**
  ```bash
  node --version
  ```

- [ ] **Next.js 15 yêu cầu Node.js >= 18.18.0**
  > ✅ Supported: `18.18.0+`, `20.x`, `22.x`  
  > ❌ Không còn hỗ trợ: Node.js 16, 17, bất kỳ version < 18.18.0

- [ ] **Nâng cấp Node.js nếu cần** (khuyến nghị dùng `nvm`)
  ```bash
  nvm install 20
  nvm use 20
  nvm alias default 20
  node --version   # → v20.x.x
  ```

- [ ] **Cập nhật file `.nvmrc` hoặc `.node-version` trong project**
  ```bash
  echo "20" > .nvmrc
  ```

- [ ] **Cập nhật `engines` field trong `package.json`**
  ```json
  {
    "engines": {
      "node": ">=18.18.0"
    }
  }
  ```

- [ ] **Cập nhật CI/CD pipeline** (GitHub Actions, GitLab CI, Jenkins...)
  ```yaml
  # .github/workflows/ci.yml — ví dụ GitHub Actions
  - uses: actions/setup-node@v4
    with:
      node-version: '20'   # Đổi từ 18 → 20
  ```
  > Đừng quên môi trường deploy (Vercel tự detect, nhưng VPS/Docker cần update thủ công).

---

## 2. Cài đặt & Nâng cấp

### 2.1 Nâng cấp Package

- [ ] **Chạy lệnh upgrade chính thức** (khuyến nghị nhất)
  ```bash
  # npm
  npm install next@latest react@latest react-dom@latest eslint-config-next@latest

  # yarn
  yarn add next@latest react@latest react-dom@latest eslint-config-next@latest

  # pnpm
  pnpm add next@latest react@latest react-dom@latest eslint-config-next@latest
  ```
  > Next.js 15 yêu cầu React 19 RC hoặc React 18.3+. Kiểm tra xem dự án bạn dùng React version nào.

- [ ] **Kiểm tra version sau khi install**
  ```bash
  npx next --version   # → Next.js v15.x.x
  node_modules/.bin/react --version || cat node_modules/react/package.json | grep '"version"'
  ```

- [ ] **Nếu dự án mới — tạo từ template Next.js 15**
  ```bash
  npx create-next-app@15 my-new-app \
    --typescript \
    --tailwind \
    --eslint \
    --app \
    --turbopack
  ```
  > Flag `--turbopack` bật Turbopack làm default dev bundler (stable trong Next.js 15).

---

### 2.2 Chạy Codemod Tự động

> Next.js cung cấp codemods để tự động sửa một số breaking changes. **Chạy theo thứ tự từng bước.**

- [ ] **Chạy codemod tổng hợp cho Next.js 15**
  ```bash
  npx @next/codemod@canary upgrade latest
  ```
  > Lệnh này sẽ: nâng cấp package + áp dụng tất cả codemods liên quan. Review diff sau khi chạy.

- [ ] **Hoặc chạy từng codemod riêng lẻ (nếu muốn kiểm soát hơn)**

  ```bash
  # Fix async request APIs (cookies, headers, params)
  npx @next/codemod@latest next-async-request-api .

  # Fix viewport export tách khỏi metadata
  npx @next/codemod@latest metadata-to-viewport-export .

  # Fix Link component (xóa <a> tag bên trong)
  npx @next/codemod@latest new-link .
  ```

- [ ] **Review toàn bộ diff sau khi chạy codemod**
  ```bash
  git diff --stat        # xem tổng số file thay đổi
  git diff               # xem chi tiết từng thay đổi
  ```
  > ⚠️ Codemod không bao giờ 100% chính xác. Cần đọc lại từng thay đổi, đặc biệt với logic phức tạp.

- [ ] **Commit sau khi codemod hoạt động ổn định**
  ```bash
  git add . && git commit -m "chore: apply Next.js 15 codemods"
  ```

---

## 3. Breaking Changes — Fix thủ công

> Phần này **codemod không xử lý được hết** — cần review và sửa bằng tay.

---

### 3.1 Async Request APIs (`cookies`, `headers`, `params`, `searchParams`)

- [ ] **Hiểu thay đổi cốt lõi:** `cookies()`, `headers()`, `params`, `searchParams` giờ là **async**

  ```typescript
  // ❌ Next.js 14 — Synchronous (không còn hoạt động)
  import { cookies, headers } from 'next/headers'

  export default function Page() {
    const cookieStore = cookies()           // sync
    const headersList = headers()           // sync
    const token = cookieStore.get('token')
  }

  // ✅ Next.js 15 — Asynchronous
  import { cookies, headers } from 'next/headers'

  export default async function Page() {
    const cookieStore = await cookies()     // async
    const headersList = await headers()     // async
    const token = cookieStore.get('token')
  }
  ```

- [ ] **Tìm tất cả nơi dùng `cookies()` và thêm `await`**
  ```bash
  grep -rn "cookies()" --include="*.ts" --include="*.tsx" src/
  grep -rn "headers()" --include="*.ts" --include="*.tsx" src/
  ```

- [ ] **Fix `params` và `searchParams` trong Page components**
  ```typescript
  // ❌ Next.js 14
  export default function Page({ params }: { params: { id: string } }) {
    const { id } = params   // sync access
  }

  // ✅ Next.js 15
  export default async function Page({
    params,
  }: {
    params: Promise<{ id: string }>   // type đổi thành Promise
  }) {
    const { id } = await params       // cần await
  }
  ```

- [ ] **Fix `searchParams` tương tự**
  ```typescript
  // ✅ Next.js 15
  export default async function Page({
    searchParams,
  }: {
    searchParams: Promise<{ query?: string }>
  }) {
    const { query } = await searchParams
  }
  ```

- [ ] **Fix trong Route Handlers (`route.ts`)**
  ```typescript
  // ✅ Next.js 15 — Route Handler
  export async function GET(
    request: Request,
    { params }: { params: Promise<{ slug: string }> }
  ) {
    const { slug } = await params
    // ...
  }
  ```

- [ ] **Fix trong Middleware nếu dùng `cookies()` hoặc `headers()`**
  > Middleware chạy trên Edge Runtime — kiểm tra kỹ vì có thêm thay đổi riêng (xem mục 3.3).

---

### 3.2 `use server` Directive & Server Actions

- [ ] **Kiểm tra tất cả file Server Actions**
  ```bash
  grep -rn '"use server"' --include="*.ts" --include="*.tsx" src/
  ```

- [ ] **`use server` ở top-level file:** file đó export tất cả functions dưới dạng Server Actions — không đổi, vẫn hoạt động như cũ

- [ ] **`use server` trong function body:** vẫn hoạt động, nhưng Next.js 15 yêu cầu function phải là `async`
  ```typescript
  // ❌ Không hợp lệ trong Next.js 15
  function myAction() {
    'use server'
    // ...
  }

  // ✅ Phải là async
  async function myAction() {
    'use server'
    // ...
  }
  ```

- [ ] **Server Actions giờ mặc định bảo mật hơn** — kiểm tra các action nhận input từ client có validate đúng không
  ```typescript
  // ✅ Best practice — luôn validate input trong Server Action
  'use server'
  import { z } from 'zod'

  const schema = z.object({ email: z.string().email() })

  export async function submitForm(formData: FormData) {
    const parsed = schema.safeParse({
      email: formData.get('email'),
    })
    if (!parsed.success) throw new Error('Invalid input')
    // xử lý tiếp...
  }
  ```

- [ ] **`useFormState` đổi tên thành `useActionState`** (từ React 19)
  ```typescript
  // ❌ Next.js 14 / React 18
  import { useFormState } from 'react-dom'
  const [state, action] = useFormState(myServerAction, initialState)

  // ✅ Next.js 15 / React 19
  import { useActionState } from 'react'
  const [state, action, isPending] = useActionState(myServerAction, initialState)
  //                     ^^^^^^^^^ thêm isPending — không cần useTransition nữa
  ```

---

### 3.3 Middleware API Changes

- [ ] **Kiểm tra `middleware.ts` tồn tại trong dự án**
  ```bash
  find . -name "middleware.ts" -not -path "*/node_modules/*"
  ```

- [ ] **`NextResponse.next()` với headers — cách truyền headers thay đổi**
  ```typescript
  // ❌ Next.js 14
  export function middleware(request: NextRequest) {
    const requestHeaders = new Headers(request.headers)
    requestHeaders.set('x-custom-header', 'value')

    return NextResponse.next({
      request: { headers: requestHeaders },
    })
  }

  // ✅ Next.js 15 — vẫn hoạt động nhưng kiểm tra thêm
  // Next.js 15 tự động forward custom headers — kiểm tra lại behavior
  ```

- [ ] **Middleware `config.matcher` — review lại pattern matching**
  ```typescript
  // Kiểm tra matcher không bị ảnh hưởng bởi thay đổi static asset filtering
  export const config = {
    matcher: [
      /*
       * ✅ Exclude static files, images, favicon
       */
      '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
    ],
  }
  ```

- [ ] **Kiểm tra `waitUntil` trong Middleware** (tính năng mới — nếu dùng `next/after`)
  ```typescript
  import { unstable_after as after } from 'next/server'

  export function middleware(request: NextRequest) {
    after(() => {
      // Code này chạy SAU khi response đã gửi
      console.log('Middleware cleanup after response')
    })
    return NextResponse.next()
  }
  ```

---

### 3.4 `next.config.ts` (TypeScript Config)

- [ ] **Next.js 15 hỗ trợ `next.config.ts` native** — có thể đổi từ `.js` sang `.ts`
  ```bash
  # Đổi tên file (optional nhưng khuyến nghị)
  mv next.config.js next.config.ts
  ```

- [ ] **Cập nhật syntax của config file**
  ```typescript
  // next.config.ts — ✅ Next.js 15 style
  import type { NextConfig } from 'next'

  const nextConfig: NextConfig = {
    // Không cần @type JSDoc nữa — TypeScript tự infer
    experimental: {
      ppr: 'incremental',    // Xem mục 4
    },
  }

  export default nextConfig
  ```

- [ ] **Loại bỏ các config options đã deprecated**
  ```typescript
  // ❌ Xóa các options này khỏi config (deprecated/removed trong v15)
  const removedOptions = {
    experimental: {
      appDir: true,           // ❌ Không cần — App Router là default
      serverActions: true,    // ❌ Không cần — Server Actions là stable
      serverComponentsExternalPackages: [], // ❌ Đổi sang top-level
    }
  }

  // ✅ Thay thế đúng
  const nextConfig: NextConfig = {
    serverExternalPackages: ['some-package'],   // Đã lên top-level
  }
  ```

- [ ] **Kiểm tra `images.domains` → nên đổi sang `images.remotePatterns`**
  ```typescript
  // ❌ Cũ (deprecated)
  images: {
    domains: ['example.com'],
  }

  // ✅ Mới (Next.js 15 khuyến nghị)
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        pathname: '/images/**',
      },
    ],
  }
  ```

---

### 3.5 Caching Behavior (Thay đổi lớn)

- [ ] **Hiểu thay đổi quan trọng nhất:** `fetch()` requests **không còn cache mặc định**

  ```typescript
  // ❌ Next.js 14 — fetch cache mặc định là 'force-cache'
  const data = await fetch('https://api.example.com/data')
  // → tự động cache

  // ✅ Next.js 15 — fetch cache mặc định là 'no-store'
  const data = await fetch('https://api.example.com/data')
  // → KHÔNG cache — phải opt-in thủ công

  // Nếu muốn cache như cũ — thêm explicit option:
  const data = await fetch('https://api.example.com/data', {
    cache: 'force-cache',
  })
  ```

- [ ] **Tìm tất cả `fetch()` calls trong Server Components và Route Handlers**
  ```bash
  grep -rn "await fetch(" --include="*.ts" --include="*.tsx" src/
  ```
  > Với mỗi fetch, quyết định có cần cache không và thêm option phù hợp.

- [ ] **`GET` Route Handlers không còn cache mặc định**
  ```typescript
  // ❌ Next.js 14 — GET handler tự động cache
  export async function GET() {
    const data = await getData()
    return Response.json(data)
  }

  // ✅ Next.js 15 — phải opt-in nếu muốn cache
  export const dynamic = 'force-static'   // Thêm dòng này để cache

  export async function GET() {
    const data = await getData()
    return Response.json(data)
  }
  ```

- [ ] **Client Router Cache behavior thay đổi**
  ```typescript
  // Next.js 15: Page components KHÔNG còn cache trên client router by default
  // Nếu muốn giữ cache cũ — configure trong next.config.ts:
  const nextConfig: NextConfig = {
    experimental: {
      staleTimes: {
        dynamic: 30,    // seconds — cache dynamic pages
        static: 180,    // seconds — cache static pages
      },
    },
  }
  ```

---

### 3.6 Các thay đổi nhỏ khác cần kiểm tra

- [ ] **`<Font>` component từ `@next/font`** — package này đã bị xóa, dùng `next/font` built-in
  ```bash
  # Kiểm tra xem còn import từ @next/font không
  grep -rn "@next/font" --include="*.ts" --include="*.tsx" src/

  # Fix: đổi thành next/font
  # ❌ import { Inter } from '@next/font/google'
  # ✅ import { Inter } from 'next/font/google'
  ```

- [ ] **`viewport` export tách khỏi `metadata`**
  ```typescript
  // ❌ Next.js 14 — viewport trong metadata
  export const metadata: Metadata = {
    viewport: 'width=device-width, initial-scale=1',   // deprecated
    themeColor: '#000000',                              // deprecated
  }

  // ✅ Next.js 15 — export riêng
  import type { Metadata, Viewport } from 'next'

  export const metadata: Metadata = {
    title: 'My App',
  }

  export const viewport: Viewport = {
    width: 'device-width',
    initialScale: 1,
    themeColor: '#000000',
  }
  ```

- [ ] **`unstable_noStore` → đổi thành `connection`**
  ```typescript
  // ❌ Cũ
  import { unstable_noStore as noStore } from 'next/cache'
  noStore()

  // ✅ Mới (Next.js 15)
  import { connection } from 'next/server'
  await connection()  // Marks component as dynamic
  ```

- [ ] **`ReactDOM.preload`, `preconnect`, `prefetchDNS`** — giờ available qua `next/head`-free approach
  ```typescript
  import { preload, preconnect } from 'react-dom'
  // Có thể gọi trong Server Components
  ```

---

## 4. Bật tính năng mới

### 4.1 Partial Prerendering (PPR)

- [ ] **Hiểu PPR là gì:** Cho phép một page vừa có phần static (render ở build time) vừa có phần dynamic (stream sau) — kết hợp best of both worlds.

- [ ] **Bật PPR incremental mode** (an toàn nhất — chỉ opt-in từng route)
  ```typescript
  // next.config.ts
  const nextConfig: NextConfig = {
    experimental: {
      ppr: 'incremental',   // Không bật global — chỉ bật theo từng layout/page
    },
  }
  ```

- [ ] **Opt-in PPR cho từng route cụ thể**
  ```typescript
  // app/dashboard/page.tsx
  export const experimental_ppr = true   // Bật PPR cho route này

  import { Suspense } from 'react'
  import { StaticHeader } from './StaticHeader'
  import { DynamicFeed } from './DynamicFeed'

  export default function Dashboard() {
    return (
      <>
        <StaticHeader />   {/* Render tĩnh — không cần Suspense */}
        <Suspense fallback={<div>Loading feed...</div>}>
          <DynamicFeed />  {/* Stream sau — bọc trong Suspense */}
        </Suspense>
      </>
    )
  }
  ```

- [ ] **Kiểm tra PPR hoạt động đúng với `<Suspense>` boundaries**
  > Mọi dynamic component phải được bọc trong `<Suspense>` để PPR hoạt động.

- [ ] **Test build output với PPR**
  ```bash
  npm run build
  # Kiểm tra output: route có PPR sẽ hiển thị "◐" (partial) thay vì "○" hay "●"
  ```

---

### 4.2 Server Actions Patterns mới

- [ ] **Dùng `useActionState` thay `useFormState`** (đã đề cập ở 3.2, confirm lại đã đổi hết)

- [ ] **Tận dụng `isPending` state từ `useActionState`**
  ```tsx
  'use client'
  import { useActionState } from 'react'
  import { submitForm } from './actions'

  export function ContactForm() {
    const [state, action, isPending] = useActionState(submitForm, null)

    return (
      <form action={action}>
        <input name="email" type="email" required />
        <button type="submit" disabled={isPending}>
          {isPending ? 'Đang gửi...' : 'Gửi'}
        </button>
        {state?.error && <p className="text-red-500">{state.error}</p>}
      </form>
    )
  }
  ```

- [ ] **Sử dụng `next/after` cho post-response logic**
  ```typescript
  // app/api/webhook/route.ts
  import { after } from 'next/server'

  export async function POST(request: Request) {
    const payload = await request.json()

    after(async () => {
      // Chạy SAU KHI response đã gửi về client
      // Phù hợp cho: logging, analytics, cleanup
      await logWebhookEvent(payload)
      await updateAnalytics(payload.userId)
    })

    return Response.json({ received: true })
  }
  ```

- [ ] **Tận dụng `useOptimistic` cho optimistic UI**
  ```tsx
  'use client'
  import { useOptimistic } from 'react'

  export function LikeButton({ postId, initialLikes }: Props) {
    const [optimisticLikes, addOptimisticLike] = useOptimistic(
      initialLikes,
      (state, newLike) => state + 1
    )

    async function handleLike() {
      addOptimisticLike(1)        // Cập nhật UI ngay
      await likePost(postId)      // Gọi Server Action
    }

    return (
      <button onClick={handleLike}>
        ❤️ {optimisticLikes}
      </button>
    )
  }
  ```

---

### 4.3 Turbopack (Dev)

- [ ] **Bật Turbopack cho development** (stable trong Next.js 15)
  ```json
  // package.json
  {
    "scripts": {
      "dev": "next dev --turbopack",   // Thêm flag --turbopack
      "build": "next build",
      "start": "next start"
    }
  }
  ```

- [ ] **Hoặc config trong `next.config.ts`** (alternative)
  ```typescript
  // Không cần config riêng — chỉ cần flag khi chạy là đủ
  // Turbopack chỉ áp dụng cho `dev`, không áp dụng cho `build` (chưa stable)
  ```

- [ ] **Kiểm tra Turbopack compatibility với các loader/plugin đang dùng**
  > Turbopack không hỗ trợ 100% webpack plugins. Kiểm tra:
  ```
  □ CSS Modules          → ✅ Supported
  □ Sass/SCSS            → ✅ Supported
  □ TypeScript           → ✅ Supported
  □ SVG imports          → ⚠️ Cần @svgr/webpack replacement
  □ Custom webpack plugins → ❌ Cần tìm alternative
  ```
  > 🔗 Xem danh sách đầy đủ: https://nextjs.org/docs/app/api-reference/turbopack

- [ ] **So sánh cold start time trước/sau Turbopack**
  ```bash
  # Đo thời gian dev server start
  time npm run dev
  ```
  > Ghi lại kết quả để báo cáo trong PR.

---

### 4.4 Static Route Indicator

- [ ] **Hiểu Static Route Indicator mới trong dev mode**
  > Next.js 15 hiển thị indicator góc dưới màn hình cho biết route đang xem là static hay dynamic — không cần config gì thêm.

- [ ] **Disable nếu không cần** (trong môi trường dev team lớn tránh nhầm lẫn)
  ```typescript
  // next.config.ts
  const nextConfig: NextConfig = {
    devIndicators: {
      buildActivityPosition: 'bottom-right',  // hoặc false để tắt
    },
  }
  ```

---

## 5. Kiểm tra sau Migration

### 5.1 Chạy Build và Test cơ bản

- [ ] **Chạy build production — không có lỗi**
  ```bash
  npm run build
  ```
  > ✅ Mục tiêu: build thành công, không có error. Warning có thể xem xét sau.

- [ ] **Phân tích build output**
  ```bash
  npm run build 2>&1 | tee build-output.txt
  # So sánh route sizes với build cũ
  # Chú ý các route đột nhiên tăng bundle size
  ```

- [ ] **Chạy development server với Turbopack và kiểm tra**
  ```bash
  npm run dev   # (đã bật --turbopack ở trên)
  ```
  > Click qua từng route chính, kiểm tra không có runtime error trên console.

- [ ] **Chạy test suite của dự án**
  ```bash
  npm run test          # Unit tests
  npm run test:e2e      # E2E tests (Cypress / Playwright)
  ```
  > Nếu có test fail — fix test trước khi tiếp tục. Không skip.

- [ ] **Kiểm tra TypeScript không có lỗi**
  ```bash
  npx tsc --noEmit
  ```
  > Đặc biệt kiểm tra các type liên quan đến `params: Promise<...>` mới.

---

### 5.2 Test Streaming & Performance với Turbopack

- [ ] **Test streaming hoạt động đúng với `<Suspense>`**
  > Mở browser DevTools → Network tab → Filter "Doc" → Reload trang có Suspense  
  > Quan sát: phải thấy response dạng chunked (streaming), không phải wait hết rồi mới nhận.

- [ ] **Kiểm tra PPR hoạt động** (nếu đã bật)
  ```bash
  # Build và kiểm tra .next/server/app directory
  npm run build
  ls -la .next/server/app/dashboard/   # Phải có file .prefetch.rsc
  ```

- [ ] **Test React Streaming với slow network**
  > Chrome DevTools → Network tab → Throttle: "Slow 3G"  
  > Verify: skeleton/loading UI xuất hiện ngay, nội dung stream dần vào — không blank screen.

- [ ] **So sánh dev server speed (webpack vs turbopack)**
  ```bash
  # Test webpack (tắt turbopack)
  time npx next dev &
  # Đo time đến khi "Ready"

  # Test turbopack
  time npx next dev --turbopack &
  # So sánh
  ```
  > Ghi lại kết quả để justify cho team việc dùng Turbopack.

- [ ] **Kiểm tra HMR (Hot Module Replacement) hoạt động nhanh**
  > Sửa một component nhỏ và đo thời gian refresh — với Turbopack phải < 100ms cho hầu hết cases.

---

### 5.3 Lighthouse Score & Core Web Vitals

- [ ] **Chạy Lighthouse trên trang chính (production build)**
  ```bash
  npm run build && npm run start
  # Mở http://localhost:3000
  # Chrome DevTools → Lighthouse → Run audit
  ```

- [ ] **Target scores sau migration (so với trước):**
  ```
  □ Performance:    >= điểm cũ (không được giảm)
  □ Accessibility:  >= 90
  □ Best Practices: >= 90
  □ SEO:            >= 90
  □ LCP:            < 2.5s
  □ FID/INP:        < 200ms
  □ CLS:            < 0.1
  ```

- [ ] **Chạy `@next/bundle-analyzer` để kiểm tra bundle size**
  ```bash
  npm install --save-dev @next/bundle-analyzer

  # next.config.ts
  import bundleAnalyzer from '@next/bundle-analyzer'
  const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === 'true' })
  export default withBundleAnalyzer(nextConfig)

  # Chạy
  ANALYZE=true npm run build
  ```
  > So sánh với bundle size trước migration. Đặc biệt chú ý nếu có package nào tăng đột biến.

- [ ] **Kiểm tra First Load JS per route**
  > Trong build output, kiểm tra cột "First Load JS" — Next.js 15 với React 19 thường nhỏ hơn.

---

### 5.4 Kiểm tra Middleware & Authentication

- [ ] **Test tất cả các route được bảo vệ bởi middleware**
  ```
  □ Truy cập route cần login khi chưa đăng nhập → redirect đúng không?
  □ Truy cập route sau khi đăng nhập → vào được không?
  □ Role-based access → đúng permission không?
  ```

- [ ] **Test cookie handling** (đặc biệt vì `cookies()` giờ là async)
  ```
  □ Set cookie trong Server Action → client nhận được không?
  □ Read cookie trong Middleware → đúng giá trị không?
  □ Delete cookie → cleared đúng không?
  ```

- [ ] **Test authentication flow end-to-end**
  ```
  □ Đăng ký tài khoản mới
  □ Đăng nhập
  □ Refresh page (session persistence)
  □ Đăng xuất
  □ Forgot password (nếu có)
  ```

---

### 5.5 Deploy lên Vercel

- [ ] **Push branch lên remote và tạo Preview Deployment**
  ```bash
  git push origin feat/migrate-nextjs-15
  # Vercel sẽ tự tạo preview deployment
  ```

- [ ] **Kiểm tra Vercel Build Logs** — không có error/warning bất thường
  > Dashboard Vercel → Deployments → Click vào deployment → Xem logs

- [ ] **Verify Vercel tự detect đúng Node.js version**
  > Vercel Settings → General → Node.js Version → Đặt thành `20.x`

- [ ] **Test Preview URL trên Vercel**
  ```
  □ Homepage load đúng
  □ API routes hoạt động
  □ Server Actions hoạt động
  □ Image optimization hoạt động (next/image)
  □ Environment variables đúng (Vercel có set chưa?)
  ```

- [ ] **Kiểm tra Vercel Analytics & Speed Insights** (nếu đang dùng)
  ```bash
  npm install @vercel/analytics @vercel/speed-insights
  ```
  ```tsx
  // app/layout.tsx
  import { Analytics } from '@vercel/analytics/react'
  import { SpeedInsights } from '@vercel/speed-insights/next'

  export default function RootLayout({ children }) {
    return (
      <html>
        <body>
          {children}
          <Analytics />
          <SpeedInsights />
        </body>
      </html>
    )
  }
  ```

- [ ] **Chạy thử Edge Functions / Middleware trên Vercel** (behavior có thể khác localhost)

- [ ] **Merge vào main và deploy production**
  ```bash
  git checkout main
  git merge feat/migrate-nextjs-15
  git push origin main
  # → Vercel tự deploy production
  ```

- [ ] **Monitor production 30 phút đầu sau deploy**
  ```
  □ Vercel Functions logs — không có error rate tăng đột biến
  □ Sentry / error tracking — không có error mới
  □ Database connections — không có connection pool issues
  □ Response times — stable hoặc tốt hơn
  ```

---

## Tổng kết Checklist nhanh

| Hạng mục | Ước tính thời gian | Độ ưu tiên |
|---|---|---|
| Chuẩn bị & backup | 30 phút | 🔴 Bắt buộc |
| Nâng cấp Node.js | 15 phút | 🔴 Bắt buộc |
| Cài đặt package + codemod | 30 phút | 🔴 Bắt buộc |
| Fix async APIs | 1–3 giờ | 🔴 Bắt buộc |
| Fix caching behavior | 1–2 giờ | 🔴 Bắt buộc |
| Fix Middleware | 30–60 phút | 🔴 Bắt buộc |
| Fix next.config.ts | 15 phút | 🟡 Cần thiết |
| Bật PPR | 1–2 giờ | 🟢 Tùy chọn |
| Bật Turbopack | 15 phút | 🟢 Tùy chọn |
| Test & Lighthouse | 1–2 giờ | 🔴 Bắt buộc |
| Deploy Vercel | 30 phút | 🔴 Bắt buộc |

---

## Tài nguyên tham khảo

| Link | Mô tả |
|---|---|
| https://nextjs.org/docs/app/building-your-application/upgrading/version-15 | Official upgrade guide |
| https://nextjs.org/blog/next-15 | Release blog |
| https://react.dev/blog/2024/12/05/react-19 | React 19 changelog |
| https://nextjs.org/docs/app/api-reference/turbopack | Turbopack compatibility |
| https://nextjs.org/docs/app/api-reference/next-config-js | next.config.ts reference |

---

> **💡 Tip cuối:** Sau khi migration thành công, xóa file `package.json.bak` và tag release mới:
> ```bash
> rm package.json.bak
> git tag v-post-nextjs15-migration
> git push origin --tags
> ```