# 📋 Checklist Migration Next.js Pages Router → App Router

> **Phiên bản áp dụng:** Next.js 13.4+ đến 15.x  
> **Cập nhật lần cuối:** 2025  
> **Hướng dẫn sử dụng:** Tick từng checkbox khi hoàn thành. Ưu tiên theo thứ tự từ trên xuống dưới.

---

## 📊 Phần 1: Pre-Migration Audit

### 1.1 Kiểm Tra Dependencies & Environment

- [ ] Backup toàn bộ source code và tạo branch mới (`git checkout -b feat/migrate-app-router`)
- [ ] Kiểm tra phiên bản Node.js hiện tại (yêu cầu **Node.js >= 18.17.0** cho Next.js 15)
- [ ] Upgrade Next.js lên phiên bản mục tiêu: `npm install next@latest react@latest react-dom@latest`
- [ ] Kiểm tra và cập nhật `@types/react` và `@types/react-dom` nếu dùng TypeScript
- [ ] Rà soát toàn bộ `package.json` — liệt kê các thư viện **chưa hỗ trợ App Router**:
  - [ ] `next-auth` (yêu cầu v5+ cho App Router đầy đủ)
  - [ ] `next-i18next` (cần migrate sang `next-intl` hoặc cấu hình lại)
  - [ ] `next-redux-wrapper` (cần kiểm tra phiên bản tương thích)
  - [ ] Các thư viện dùng `withRouter` HOC
  - [ ] Các thư viện dùng Context của Pages Router
- [ ] Kiểm tra `next.config.js` — ghi chú các cấu hình cần điều chỉnh:
  - [ ] `redirects`, `rewrites`, `headers` (giữ nguyên, không đổi)
  - [ ] `i18n` routing (cần xử lý thủ công trong App Router)
  - [ ] `basePath` và `assetPrefix`
  - [ ] `experimental` flags đã deprecated
- [ ] Chạy `npx next info` để capture thông tin môi trường hiện tại
- [ ] Đảm bảo có **100% test coverage** hoặc ít nhất E2E tests cho các luồng quan trọng trước khi bắt đầu

### 1.2 Audit API Routes

- [ ] Liệt kê toàn bộ files trong `pages/api/` vào spreadsheet tracking
- [ ] Phân loại từng API route:
  - [ ] API routes đơn giản (GET/POST) → migrate sang `app/api/[route]/route.ts`
  - [ ] API routes dùng `req.cookies` → kiểm tra cách migrate với `cookies()` từ `next/headers`
  - [ ] API routes dùng `req.session` (next-iron-session, etc.) → plan migration riêng
  - [ ] API routes streaming responses → migrate sang Response với ReadableStream
  - [ ] API routes dùng middleware logic nội bộ
- [ ] Kiểm tra các API routes có dùng **custom config**:
  ```
  export const config = { api: { bodyParser: false } }
  ```
  → Cần migrate sang `export const config = { api: {...} }` format mới
- [ ] Xác định API routes được gọi từ `getServerSideProps` / `getStaticProps` (nên refactor thành direct function call)
- [ ] Kiểm tra CORS configuration trong API routes hiện tại

### 1.3 Audit Data Fetching Patterns

- [ ] Chạy lệnh search và đếm số lượng từng pattern:
  ```bash
  # Đếm getServerSideProps
  grep -r "getServerSideProps" ./pages --include="*.tsx" --include="*.ts" -l | wc -l
  
  # Đếm getStaticProps
  grep -r "getStaticProps" ./pages --include="*.tsx" --include="*.ts" -l | wc -l
  
  # Đếm getStaticPaths
  grep -r "getStaticPaths" ./pages --include="*.tsx" --include="*.ts" -l | wc -l
  
  # Đếm getInitialProps
  grep -r "getInitialProps" ./pages --include="*.tsx" --include="*.ts" -l | wc -l
  ```
- [ ] Liệt kê tất cả pages dùng `getServerSideProps` (SSR) — ưu tiên migrate sau cùng
- [ ] Liệt kê tất cả pages dùng `getStaticProps` không có revalidate (SSG thuần)
- [ ] Liệt kê tất cả pages dùng `getStaticProps` + `revalidate` (ISR) — cần hiểu caching mới
- [ ] Liệt kê tất cả pages dùng `getStaticPaths` — xác định fallback strategy
- [ ] Liệt kê tất cả components dùng `getInitialProps` (deprecated, ưu tiên xử lý sớm)
- [ ] Xác định các **client-side data fetching** patterns (SWR, React Query, fetch trong useEffect)

### 1.4 Audit Component Architecture

- [ ] Liệt kê tất cả components dùng **Browser APIs** (window, document, localStorage) → cần `"use client"`
- [ ] Liệt kê tất cả components dùng **React hooks** (useState, useEffect, useContext, etc.) → cần `"use client"`
- [ ] Liệt kê tất cả components dùng **Event handlers** (onClick, onChange, etc.) → cần `"use client"`
- [ ] Xác định các **Context Providers** hiện tại trong `_app.tsx` → cần wrap bằng Client Component
- [ ] Kiểm tra usage của `next/dynamic` → xem xét lại strategy trong App Router
- [ ] Audit `_app.tsx`: liệt kê tất cả global providers, global styles, layout logic
- [ ] Audit `_document.tsx`: liệt kê custom `<head>`, fonts, scripts, third-party tags
- [ ] Kiểm tra `middleware.ts` nếu có — App Router middleware có API tương tự nhưng cần verify

### 1.5 Audit Routing & Navigation

- [ ] Liệt kê tất cả dynamic routes (`[id]`, `[...slug]`, `[[...slug]]`)
- [ ] Kiểm tra các routes dùng `useRouter` để programmatic navigate
- [ ] Kiểm tra các `<Link>` components (Next.js 13 thay đổi API của Link)
- [ ] Xác định các routes có **nested layouts** logic trong `_app.tsx`
- [ ] Kiểm tra `router.query` usage → migrate sang `useSearchParams()` và `useParams()`
- [ ] Kiểm tra `router.pathname` usage → migrate sang `usePathname()`
- [ ] Liệt kê các **shallow routing** (`router.push({ query }, undefined, { shallow: true })`) → cần xử lý đặc biệt

---

## 🗂️ Phần 2: File Structure Changes

### 2.1 Khởi Tạo App Directory

- [ ] Tạo thư mục `app/` ở root của project (cùng cấp với `pages/`)
- [ ] Tạo file `app/layout.tsx` — **Root Layout bắt buộc** phải có:
  ```tsx
  // app/layout.tsx
  export default function RootLayout({
    children,
  }: {
    children: React.ReactNode
  }) {
    return (
      <html lang="vi">
        <body>{children}</body>
      </html>
    )
  }
  ```
- [ ] Di chuyển logic từ `pages/_document.tsx` vào `app/layout.tsx` (html, body attributes, fonts)
- [ ] Di chuyển global providers từ `pages/_app.tsx` vào `app/layout.tsx` (wrap trong Client Component)
- [ ] Di chuyển global CSS imports từ `pages/_app.tsx` vào `app/layout.tsx`
- [ ] Xóa `pages/_app.tsx` sau khi đã migrate xong tất cả logic
- [ ] Xóa `pages/_document.tsx` sau khi đã migrate xong tất cả logic

### 2.2 Cấu Trúc Thư Mục App Router

- [ ] Hiểu và áp dụng **App Router file conventions**:

  | File | Mục đích | Tương đương Pages Router |
  |------|----------|--------------------------|
  | `layout.tsx` | Shared UI, giữ state giữa navigations | `_app.tsx` (partial) |
  | `page.tsx` | UI của route, public accessible | `index.tsx` / `[page].tsx` |
  | `loading.tsx` | Loading UI (Suspense boundary tự động) | Manual loading state |
  | `error.tsx` | Error UI (Error boundary tự động) | Manual error handling |
  | `not-found.tsx` | 404 UI | `pages/404.tsx` |
  | `route.ts` | API endpoint | `pages/api/[route].ts` |
  | `template.tsx` | Re-render layout mỗi navigation | Không có tương đương |
  | `default.tsx` | Parallel routes fallback | Không có tương đương |
  | `global-error.tsx` | Root error boundary | Không có tương đương |

- [ ] Tạo cấu trúc thư mục mới theo mapping sau:

  ```
  # BEFORE (Pages Router)        # AFTER (App Router)
  pages/
  ├── index.tsx              →   app/page.tsx
  ├── about.tsx              →   app/about/page.tsx
  ├── blog/
  │   ├── index.tsx          →   app/blog/page.tsx
  │   └── [slug].tsx         →   app/blog/[slug]/page.tsx
  ├── dashboard/
  │   ├── index.tsx          →   app/dashboard/page.tsx
  │   └── settings.tsx       →   app/dashboard/settings/page.tsx
  └── api/
      └── users.ts           →   app/api/users/route.ts
  ```

- [ ] Migrate `pages/404.tsx` → `app/not-found.tsx`
- [ ] Migrate `pages/500.tsx` → `app/global-error.tsx`
- [ ] Migrate `pages/_error.tsx` → `app/error.tsx` (Client Component, cần `"use client"`)

### 2.3 Tổ Chức Nested Layouts

- [ ] Xác định các nhóm pages có **shared layout** (header, sidebar, etc.)
- [ ] Tạo `layout.tsx` riêng cho từng nhóm route:
  ```
  app/
  ├── layout.tsx              ← Root layout (html, body)
  ├── (marketing)/
  │   ├── layout.tsx          ← Marketing layout (public header)
  │   ├── page.tsx
  │   └── about/page.tsx
  └── (dashboard)/
      ├── layout.tsx          ← Dashboard layout (sidebar, nav)
      ├── page.tsx
      └── settings/page.tsx
  ```
- [ ] Sử dụng **Route Groups** `(folderName)` để tổ chức không ảnh hưởng URL
- [ ] Tạo `loading.tsx` cho các routes có data fetching async
- [ ] Tạo `error.tsx` cho các routes cần error boundary riêng
- [ ] Xem xét dùng **Parallel Routes** (`@slot`) cho dashboard layouts phức tạp
- [ ] Xem xét dùng **Intercepting Routes** nếu có modal routing patterns

### 2.4 Migrate API Routes

- [ ] Tạo thư mục `app/api/` tương ứng
- [ ] Rename từng API file theo convention mới:
  ```
  # BEFORE                          # AFTER
  pages/api/users.ts           →   app/api/users/route.ts
  pages/api/users/[id].ts      →   app/api/users/[id]/route.ts
  pages/api/auth/[...nextauth] →   app/api/auth/[...nextauth]/route.ts
  ```
- [ ] Đảm bảo mỗi `route.ts` export named functions đúng HTTP methods:
  ```ts
  // app/api/users/route.ts
  export async function GET(request: Request) {}
  export async function POST(request: Request) {}
  export async function PUT(request: Request) {}
  export async function DELETE(request: Request) {}
  export async function PATCH(request: Request) {}
  ```
- [ ] Verify không còn file nào trong `pages/api/` sau khi migrate xong

---

## 🔧 Phần 3: Code Refactoring List

### 3.1 Migrate `getServerSideProps` → Server Component

- [ ] **Pattern cơ bản**: Xóa wrapper function, fetch trực tiếp trong component:

  ```tsx
  // ❌ BEFORE - Pages Router
  export async function getServerSideProps(context) {
    const { params, query, req, res } = context
    const data = await fetchData(params.id)
    return { props: { data } }
  }
  export default function Page({ data }) {
    return <div>{data.title}</div>
  }
  
  // ✅ AFTER - App Router (Server Component mặc định)
  export default async function Page({
    params,
    searchParams,
  }: {
    params: { id: string }
    searchParams: { [key: string]: string }
  }) {
    const data = await fetchData(params.id)
    return <div>{data.title}</div>
  }
  ```

- [ ] Migrate `context.params` → `props.params` (async trong Next.js 15)
- [ ] Migrate `context.query` → `props.searchParams` (async trong Next.js 15)
- [ ] Migrate `context.req.cookies` → `import { cookies } from 'next/headers'; const cookieStore = await cookies()`
- [ ] Migrate `context.req.headers` → `import { headers } from 'next/headers'; const headersList = await headers()`
- [ ] Migrate `context.res.setHeader` → trả về `Response` với headers hoặc dùng `next/headers`
- [ ] Migrate `context.res.redirect` → `import { redirect } from 'next/navigation'; redirect('/path')`
- [ ] Migrate `context.locale` (i18n) → xử lý thủ công qua middleware hoặc thư viện `next-intl`
- [ ] Xử lý **authentication checks** trong `getServerSideProps`:
  ```tsx
  // ✅ AFTER - App Router
  import { redirect } from 'next/navigation'
  
  export default async function ProtectedPage() {
    const session = await getSession() // server-side auth check
    if (!session) redirect('/login')
    return <div>Protected content</div>
  }
  ```
- [ ] Kiểm tra lại từng trường hợp `notFound: true` → dùng `import { notFound } from 'next/navigation'; notFound()`

### 3.2 Migrate `getStaticProps` → Server Component với Caching

- [ ] **Pattern SSG cơ bản** (không revalidate):

  ```tsx
  // ❌ BEFORE
  export async function getStaticProps() {
    const data = await fetchData()
    return { props: { data } }
  }
  
  // ✅ AFTER - mặc định cache = 'force-cache' trong Next.js 13/14
  // ⚠️ Next.js 15: mặc định NO CACHE, phải explicit
  export default async function Page() {
    const data = await fetch('https://api.example.com/data', {
      cache: 'force-cache', // Explicit trong Next.js 15
    }).then(r => r.json())
    return <div>{data.title}</div>
  }
  ```

- [ ] **Pattern ISR** (có revalidate):

  ```tsx
  // ❌ BEFORE
  export async function getStaticProps() {
    const data = await fetchData()
    return { props: { data }, revalidate: 60 }
  }
  
  // ✅ AFTER - Option 1: fetch với next.revalidate
  const data = await fetch('https://api.example.com/data', {
    next: { revalidate: 60 },
  }).then(r => r.json())
  
  // ✅ AFTER - Option 2: Route Segment Config
  export const revalidate = 60
  ```

- [ ] Migrate tất cả `revalidate` values sang Route Segment Config hoặc fetch options
- [ ] Kiểm tra **ISR on-demand revalidation**: `res.revalidate('/path')` → `revalidatePath('/path')` hoặc `revalidateTag('tag-name')`
- [ ] Xem xét dùng `unstable_cache` cho các non-fetch data sources (database queries, etc.)

### 3.3 Migrate `getStaticPaths` → `generateStaticParams`

- [ ] **Pattern cơ bản**:

  ```tsx
  // ❌ BEFORE
  export async function getStaticPaths() {
    const posts = await getPosts()
    return {
      paths: posts.map(post => ({ params: { slug: post.slug } })),
      fallback: false, // hoặc 'blocking' hoặc true
    }
  }
  
  // ✅ AFTER
  export async function generateStaticParams() {
    const posts = await getPosts()
    return posts.map(post => ({ slug: post.slug }))
    // Không cần khai báo fallback ở đây
  }
  ```

- [ ] Migrate `fallback: false` → thêm vào page: `export const dynamicParams = false`
- [ ] Migrate `fallback: 'blocking'` → mặc định (dynamicParams = true, render on-demand)
- [ ] Migrate `fallback: true` → dùng `loading.tsx` (Suspense boundary)
- [ ] Kiểm tra **nested dynamic routes** với `generateStaticParams`:
  ```tsx
  // app/blog/[category]/[slug]/page.tsx
  export async function generateStaticParams() {
    return [
      { category: 'tech', slug: 'nextjs-15' },
      { category: 'design', slug: 'ui-patterns' },
    ]
  }
  ```

### 3.4 Migrate `useRouter` → New Hooks

- [ ] Thêm `"use client"` directive cho mọi component dùng navigation hooks
- [ ] **Migrate imports**:

  ```tsx
  // ❌ BEFORE
  import { useRouter } from 'next/router'
  
  // ✅ AFTER
  import { useRouter, usePathname, useSearchParams, useParams } from 'next/navigation'
  ```

- [ ] Migrate `router.pathname` → `usePathname()`:
  ```tsx
  // ❌ BEFORE
  const router = useRouter()
  const isActive = router.pathname === '/about'
  
  // ✅ AFTER
  const pathname = usePathname()
  const isActive = pathname === '/about'
  ```

- [ ] Migrate `router.query` → `useSearchParams()` (cho query params) + `useParams()` (cho dynamic segments):
  ```tsx
  // ❌ BEFORE
  const { id, tab } = router.query
  
  // ✅ AFTER
  const params = useParams()        // { id: '123' }
  const searchParams = useSearchParams()  // tab=settings
  const id = params.id
  const tab = searchParams.get('tab')
  ```

- [ ] Migrate `router.push('/path')` → `router.push('/path')` (giữ nguyên, import từ `next/navigation`)
- [ ] Migrate `router.replace('/path')` → `router.replace('/path')` (giữ nguyên)
- [ ] Migrate `router.back()` → `router.back()` (giữ nguyên)
- [ ] Migrate `router.prefetch('/path')` → Link component tự prefetch, hoặc `router.prefetch` vẫn available
- [ ] Migrate **shallow routing**:
  ```tsx
  // ❌ BEFORE - Pages Router
  router.push({ query: { tab: 'settings' } }, undefined, { shallow: true })
  
  // ✅ AFTER - App Router
  // Dùng useSearchParams và manual URL update
  const router = useRouter()
  const searchParams = useSearchParams()
  const createQueryString = (name: string, value: string) => {
    const params = new URLSearchParams(searchParams.toString())
    params.set(name, value)
    return params.toString()
  }
  router.push(pathname + '?' + createQueryString('tab', 'settings'))
  ```

- [ ] Migrate `router.events` (onRouteChangeStart, etc.) → dùng custom solution hoặc layout patterns:
  ```tsx
  // ⚠️ router.events KHÔNG CÒN trong App Router
  // Alternative: dùng useEffect + usePathname để track navigation
  const pathname = usePathname()
  useEffect(() => {
    // Route changed
    analytics.trackPageView(pathname)
  }, [pathname])
  ```

### 3.5 Migrate `<Link>` Component

- [ ] Cập nhật tất cả `<Link>` components — **xóa `<a>` tag bên trong**:

  ```tsx
  // ❌ BEFORE - Pages Router (Next.js < 13)
  import Link from 'next/link'
  <Link href="/about">
    <a>About</a>
  </Link>
  
  // ✅ AFTER - App Router (Next.js >= 13)
  import Link from 'next/link'
  <Link href="/about">About</Link>
  ```

- [ ] Kiểm tra `<Link legacyBehavior>` nếu có (cần xóa bỏ prop này)
- [ ] Migrate `<Link passHref>` + custom component:
  ```tsx
  // ❌ BEFORE
  <Link href="/about" passHref>
    <CustomButton>About</CustomButton>
  </Link>
  
  // ✅ AFTER - dùng forwardRef hoặc rethink approach
  // Option 1: wrap trong Link trực tiếp nếu CustomButton render <a>
  // Option 2: dùng useRouter trong CustomButton
  ```
- [ ] Verify `<Link>` prefetch behavior — mặc định vẫn prefetch khi in viewport
- [ ] Kiểm tra `<Link scroll={false}>` — vẫn hoạt động trong App Router
- [ ] Kiểm tra `<Link replace>` — vẫn hoạt động trong App Router

### 3.6 Migrate `<Image>` Component

- [ ] Kiểm tra tất cả `<Image>` imports (đã dùng `next/image` là OK)
- [ ] Verify `layout` prop đã bị **xóa hoàn toàn** (deprecated từ Next.js 13):
  ```tsx
  // ❌ BEFORE - Next.js 12
  <Image src="..." layout="fill" objectFit="cover" />
  <Image src="..." layout="responsive" />
  <Image src="..." layout="fixed" width={100} height={100} />
  
  // ✅ AFTER - Next.js 13+
  <Image src="..." fill style={{ objectFit: 'cover' }} />
  <Image src="..." width={500} height={300} style={{ width: '100%', height: 'auto' }} />
  <Image src="..." width={100} height={100} />
  ```
- [ ] Xóa tất cả `objectFit` và `objectPosition` props → chuyển vào `style`
- [ ] Kiểm tra `layout="intrinsic"` → thay bằng explicit `width` và `height`
- [ ] Verify `domains` trong `next.config.js` → migrate sang `remotePatterns`:
  ```js
  // ❌ BEFORE
  images: { domains: ['example.com'] }
  
  // ✅ AFTER
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: 'example.com', pathname: '/images/**' }
    ]
  }
  ```
- [ ] Kiểm tra `<Image>` trong Server Components — hoạt động bình thường
- [ ] Verify `priority` prop cho above-the-fold images (LCP optimization)

### 3.7 Migrate Metadata (Head → Metadata API)

- [ ] Xóa tất cả `import Head from 'next/head'` và `<Head>` usage
- [ ] Migrate sang **Metadata API** (Server Components only):

  ```tsx
  // ❌ BEFORE - Pages Router
  import Head from 'next/head'
  export default function Page() {
    return (
      <>
        <Head>
          <title>Page Title</title>
          <meta name="description" content="Page description" />
          <meta property="og:title" content="OG Title" />
        </Head>
        <main>...</main>
      </>
    )
  }
  
  // ✅ AFTER - Static Metadata
  import type { Metadata } from 'next'
  export const metadata: Metadata = {
    title: 'Page Title',
    description: 'Page description',
    openGraph: { title: 'OG Title' },
  }
  export default function Page() {
    return <main>...</main>
  }
  
  // ✅ AFTER - Dynamic Metadata
  export async function generateMetadata({
    params,
  }: {
    params: { id: string }
  }): Promise<Metadata> {
    const product = await getProduct(params.id)
    return {
      title: product.name,
      description: product.description,
    }
  }
  ```

- [ ] Migrate `<title>` → `metadata.title` (hoặc `title.template` cho nested routes)
- [ ] Migrate `<meta name="description">` → `metadata.description`
- [ ] Migrate Open Graph tags → `metadata.openGraph`
- [ ] Migrate Twitter Card tags → `metadata.twitter`
- [ ] Migrate `<link rel="canonical">` → `metadata.alternates.canonical`
- [ ] Migrate `robots.txt` logic → `metadata.robots` hoặc `app/robots.ts`
- [ ] Migrate `sitemap.xml` → `app/sitemap.ts`
- [ ] Xem xét dùng `metadata.title.template` cho consistent title pattern

### 3.8 Migrate `getInitialProps`

- [ ] Tìm tất cả `getInitialProps` usage (đây là **deprecated pattern**, ưu tiên xử lý trước):
  ```bash
  grep -r "getInitialProps" ./pages --include="*.tsx" -l
  ```
- [ ] Với pages thông thường → migrate sang Server Component data fetching
- [ ] Với `_app.tsx` → migrate logic vào `layout.tsx` hoặc riêng từng page
- [ ] Với `_document.tsx` → migrate cần thiết vào `layout.tsx`
- [ ] Lưu ý: `getInitialProps` trong `_app.tsx` sẽ **opt-out toàn bộ app** khỏi Automatic Static Optimization

### 3.9 Tạo Client Components Wrapper

- [ ] Tạo pattern "Client Component Wrapper" cho các providers:
  ```tsx
  // components/providers.tsx
  'use client'
  
  import { ThemeProvider } from 'next-themes'
  import { SessionProvider } from 'next-auth/react'
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
  
  export function Providers({ children }: { children: React.ReactNode }) {
    const queryClient = new QueryClient()
    return (
      <SessionProvider>
        <QueryClientProvider client={queryClient}>
          <ThemeProvider attribute="class">
            {children}
          </ThemeProvider>
        </QueryClientProvider>
      </SessionProvider>
    )
  }
  
  // app/layout.tsx (Server Component)
  import { Providers } from '@/components/providers'
  export default function RootLayout({ children }) {
    return (
      <html><body>
        <Providers>{children}</Providers>
      </body></html>
    )
  }
  ```
- [ ] Xác định **ranh giới** Server/Client Components: đẩy `"use client"` xuống sâu nhất có thể
- [ ] Tránh import Server Components vào Client Components trực tiếp — dùng `children` prop pattern

---

## ⚠️ Phần 4: Breaking Changes Caching Next.js 15

> **⚡ QUAN TRỌNG:** Next.js 15 đảo ngược hoàn toàn default caching behavior so với Next.js 13/14. Đây là nguồn gây bug phổ biến nhất khi migration.

### 4.1 Hiểu Sự Thay Đổi Caching

- [ ] Nắm rõ bảng so sánh **default caching behavior**:

  | Tính năng | Next.js 13/14 | Next.js 15 |
  |-----------|---------------|------------|
  | `fetch()` requests | `cache: 'force-cache'` (**cached**) | `cache: 'no-store'` (**NOT cached**) |
  | Route Handlers GET | Cached | **NOT cached** |
  | Client Router Cache | Cached indefinitely | 30s (pages), 5min (layouts) |
  | `params` & `searchParams` | Synchronous | **Asynchronous** (Breaking!) |

- [ ] Đọc và hiểu [Next.js 15 Upgrade Guide](https://nextjs.org/docs/app/building-your-application/upgrading/version-15) trước khi bắt đầu

### 4.2 Migrate `fetch()` Caching

- [ ] **Audit tất cả `fetch()` calls** trong Server Components:
  ```bash
  grep -r "fetch(" ./app --include="*.tsx" --include="*.ts" -n
  ```
- [ ] Với data cần **cache** (static-like data) → thêm explicit `cache: 'force-cache'`:
  ```tsx
  // ✅ Next.js 15 - Explicit cache
  const data = await fetch('https://api.example.com/config', {
    cache: 'force-cache',
  })
  ```
- [ ] Với data cần **ISR** → thêm `next: { revalidate: seconds }`:
  ```tsx
  // ✅ Next.js 15 - ISR with explicit revalidation
  const data = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 }, // revalidate mỗi 1 giờ
  })
  ```
- [ ] Với data **luôn fresh** (dynamic) → thêm explicit `cache: 'no-store'` (đây là default trong v15 nhưng nên explicit):
  ```tsx
  // ✅ Next.js 15 - No cache (dynamic data)
  const data = await fetch('https://api.example.com/realtime', {
    cache: 'no-store',
  })
  ```
- [ ] Với data cần **tag-based revalidation**:
  ```tsx
  const data = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600, tags: ['posts'] },
  })
  // Trigger revalidation: revalidateTag('posts')
  ```
- [ ] **KHÔNG** để bất kỳ `fetch()` nào không có explicit cache option trong Next.js 15

### 4.3 Migrate Route Handlers Caching

- [ ] Kiểm tra tất cả `GET` Route Handlers (`route.ts`) đã có trong app:
  ```tsx
  // ❌ Next.js 13/14: GET Route Handler tự động cached
  // ✅ Next.js 15: KHÔNG cached, phải opt-in
  
  // Nếu muốn cache Route Handler trong Next.js 15:
  export const dynamic = 'force-static'
  export async function GET() {
    const data = await getData()
    return Response.json(data)
  }
  ```
- [ ] Với Route Handlers trả về **dynamic data** → không cần làm gì (no-store là default)
- [ ] Với Route Handlers trả về **static/cacheable data** → thêm `export const dynamic = 'force-static'`
- [ ] Với Route Handlers cần **ISR** → thêm `export const revalidate = 60`

### 4.4 Migrate Async `params` và `searchParams` (Next.js 15)

- [ ] **BREAKING CHANGE**: `params` và `searchParams` là **Promise** trong Next.js 15:

  ```tsx
  // ❌ BEFORE (Next.js 13/14) - Synchronous
  export default function Page({
    params,
    searchParams,
  }: {
    params: { slug: string }
    searchParams: { query: string }
  }) {
    const { slug } = params           // synchronous access
    const { query } = searchParams    // synchronous access
    return <div>{slug}</div>
  }
  
  // ✅ AFTER (Next.js 15) - Asynchronous
  export default async function Page({
    params,
    searchParams,
  }: {
    params: Promise<{ slug: string }>
    searchParams: Promise<{ query: string }>
  }) {
    const { slug } = await params           // must await
    const { query } = await searchParams    // must await
    return <div>{slug}</div>
  }
  ```

- [ ] Chạy **Next.js 15 codemod** để tự động migrate params:
  ```bash
  npx @next/codemod@canary next-async-request-api .
  ```
- [ ] Sau khi chạy codemod, **review manual** từng file đã được transform
- [ ] Migrate tất cả `layout.tsx` files (layout cũng nhận `params`):
  ```tsx
  // ✅ Next.js 15 - Layout với async params
  export default async function Layout({
    children,
    params,
  }: {
    children: React.ReactNode
    params: Promise<{ lang: string }>
  }) {
    const { lang } = await params
    return <div lang={lang}>{children}</div>
  }
  ```
- [ ] Migrate `generateMetadata` function:
  ```tsx
  // ✅ Next.js 15
  export async function generateMetadata({
    params,
    searchParams,
  }: {
    params: Promise<{ id: string }>
    searchParams: Promise<{ [key: string]: string }>
  }): Promise<Metadata> {
    const { id } = await params
    return { title: `Product ${id}` }
  }
  ```
- [ ] Migrate Route Handlers:
  ```tsx
  // ✅ Next.js 15 - Route Handler với async params
  export async function GET(
    request: Request,
    { params }: { params: Promise<{ id: string }> }
  ) {
    const { id } = await params
    return Response.json({ id })
  }
  ```

### 4.5 Migrate `cookies()` và `headers()` (Next.js 15)

- [ ] `cookies()` và `headers()` trở thành **async** trong Next.js 15:

  ```tsx
  // ❌ BEFORE (Next.js 13/14)
  import { cookies, headers } from 'next/headers'
  
  export default function Page() {
    const cookieStore = cookies()          // synchronous
    const headersList = headers()          // synchronous
    const token = cookieStore.get('token')
  }
  
  // ✅ AFTER (Next.js 15)
  import { cookies, headers } from 'next/headers'
  
  export default async function Page() {
    const cookieStore = await cookies()    // must await
    const headersList = await headers()   // must await
    const token = cookieStore.get('token')
  }
  ```

- [ ] Chạy codemod cho cookies và headers:
  ```bash
  npx @next/codemod@canary next-async-request-api .
  ```
- [ ] Kiểm tra tất cả middleware files dùng `cookies()` và `headers()`
- [ ] Kiểm tra tất cả Server Actions dùng `cookies()` và `headers()`

### 4.6 Client Router Cache Changes

- [ ] Hiểu behavior mới của Client Router Cache trong Next.js 15:
  - **Pages (page.tsx)**: cache 30 giây (trước đây: vô thời hạn cho đến khi reload)
  - **Layouts**: cache 5 phút (không đổi về cơ bản)
- [ ] Kiểm tra các pages phụ thuộc vào **stale cache** behavior cũ:
  - Pages hiển thị real-time data mà trước đây đúng do mỗi navigate là fresh request
  - Pages dùng form submission và expect data refresh khi navigate back
- [ ] Nếu cần **opt-out cache cho một route**:
  ```tsx
  export const dynamic = 'force-dynamic'
  // hoặc
  export const revalidate = 0
  ```
- [ ] Nếu cần **force refresh** programmatically:
  ```tsx
  import { useRouter } from 'next/navigation'
  const router = useRouter()
  router.refresh() // invalidate current route cache
  ```

### 4.7 `unstable_cache` và `use cache` (Next.js 15)

- [ ] Biết rằng **`use cache`** directive là tính năng mới experimental trong Next.js 15:
  ```tsx
  // Caching một function (experimental)
  async function getUser(id: string) {
    'use cache'
    return await db.user.findUnique({ where: { id } })
  }
  
  // Caching một component (experimental)
  async function UserCard({ id }: { id: string }) {
    'use cache'
    const user = await getUser(id)
    return <div>{user.name}</div>
  }
  ```
- [ ] Nếu dùng `unstable_cache` từ Next.js 14 → vẫn hoạt động, nhưng xem xét migrate sang `use cache`
- [ ] Kiểm tra `cacheTag()` và `cacheLife()` nếu dùng `use cache` directive:
  ```tsx
  import { cacheTag, cacheLife } from 'next/cache'
  
  async function getProducts() {
    'use cache'
    cacheTag('products')
    cacheLife('hours') // preset: seconds, minutes, hours, days, weeks, max
    return await db.products.findMany()
  }
  ```
- [ ] Enable `use cache` trong `next.config.ts`:
  ```ts
  const nextConfig = {
    experimental: {
      dynamicIO: true, // enables 'use cache'
    },
  }
  ```

### 4.8 Các Breaking Changes Khác trong Next.js 15

- [ ] **React 19 compatibility**: Kiểm tra tất cả third-party components tương thích React 19
- [ ] **`after()` API**: API mới để execute code sau khi response đã được gửi (logging, analytics):
  ```tsx
  import { after } from 'next/server'
  export default async function Page() {
    after(() => {
      // Chạy sau khi response gửi xong
      analytics.track('page_view')
    })
    return <main>...</main>
  }
  ```
- [ ] **Turbopack stable**: Có thể bật `--turbopack` flag cho dev server (cần verify tương thích)
- [ ] **`next/form`**: Component mới cho progressive enhancement forms:
  ```tsx
  import Form from 'next/form'
  <Form action="/search">
    <input name="query" />
    <button type="submit">Search</button>
  </Form>
  ```
- [ ] Kiểm tra `instrumentation.ts` nếu có (API được stabilize trong Next.js 15)
- [ ] Verify `TypeScript config` — Next.js 15 yêu cầu TypeScript >= 4.7

---

## 🧪 Phần 5: Testing Checklist Sau Migration

### 5.1 Setup Testing Environment

- [ ] Cập nhật test dependencies:
  ```bash
  npm install --save-dev @testing-library/react@latest jest@latest
  npm install --save-dev @testing-library/jest-dom@latest
  ```
- [ ] Cập nhật `jest.config.js` để support App Router:
  ```js
  // jest.config.js
  const nextJest = require('next/jest')
  const createJestConfig = nextJest({ dir: './' })
  module.exports = createJestConfig({
    testEnvironment: 'jest-environment-jsdom',
    setupFilesAfterFramework: ['<rootDir>/jest.setup.js'],
  })
  ```
- [ ] Verify Playwright hoặc Cypress config vẫn hoạt động với routes mới
- [ ] Đảm bảo CI/CD pipeline chạy test trước khi deploy

### 5.2 Unit Tests — Components

- [ ] Chạy toàn bộ unit tests hiện tại: `npm run test`
- [ ] Kiểm tra các test fail do **Server Components** (không thể test như Client Components):
  ```tsx
  // Server Components: test output HTML hoặc dùng integration test
  // Client Components: test như thông thường với React Testing Library
  ```
- [ ] Verify tất cả **Client Components** có `"use client"` vẫn pass unit tests
- [ ] Test các **Provider components** mới tạo trong Phần 3.9
- [ ] Test **error boundaries** (`error.tsx`) hoạt động đúng
- [ ] Test **loading states** (`loading.tsx`) render đúng
- [ ] Kiểm tra snapshot tests — cập nhật snapshots nếu cần: `npm run test -- -u`

### 5.3 Integration Tests — Data Fetching

- [ ] Test từng page đã migrate `getServerSideProps` → Server Component:
  - [ ] Data được fetch và hiển thị đúng
  - [ ] Error states được handle đúng
  - [ ] Authentication/authorization checks hoạt động
- [ ] Test từng page đã migrate `getStaticProps` → Server Component:
  - [ ] Static data được render đúng
  - [ ] ISR revalidation hoạt động (dùng `revalidateTag` hoặc `revalidatePath`)
- [ ] Test `generateStaticParams` — verify đúng số lượng static pages được generate
- [ ] Test **caching behavior** trong Next.js 15:
  - [ ] Verify `cache: 'force-cache'` data được cache
  - [ ] Verify `cache: 'no-store'` data luôn fresh
  - [ ] Verify `next: { revalidate: N }` hoạt động đúng thời gian
- [ ] Test **async params** trong Next.js 15 — verify không có undefined errors

### 5.4 API Routes Tests

- [ ] Test từng migrated API Route (`route.ts`):
  - [ ] `GET` endpoint trả về đúng data và status code
  - [ ] `POST` endpoint nhận và xử lý request body đúng
  - [ ] `PUT`/`PATCH` endpoint cập nhật đúng
  - [ ] `DELETE` endpoint xóa đúng
  - [ ] Error responses có đúng status codes (400, 401, 403, 404, 500)
- [ ] Test **authentication middleware** trong API routes
- [ ] Test **CORS headers** nếu có
- [ ] Test **streaming responses** nếu có
- [ ] Verify không có route nào bị miss (so sánh với danh sách audit ban đầu)

### 5.5 Navigation & Routing Tests

- [ ] Test tất cả **`<Link>`** components:
  - [ ] Click navigate đến đúng page
  - [ ] `prefetch` hoạt động (kiểm tra Network tab)
  - [ ] Active states hiển thị đúng với `usePathname()`
- [ ] Test **programmatic navigation** với `useRouter`:
  - [ ] `router.push()` navigate đúng
  - [ ] `router.replace()` không thêm vào history
  - [ ] `router.back()` hoạt động
- [ ] Test **dynamic routes**:
  - [ ] `[param]` routes render đúng với từng param
  - [ ] `[...slug]` catch-all routes hoạt động
  - [ ] `[[...slug]]` optional catch-all hoạt động
- [ ] Test **search params**:
  - [ ] `useSearchParams()` đọc đúng query params
  - [ ] URL update với query params không gây full page reload
- [ ] Test **redirect** logic (`redirect()` từ `next/navigation`)
- [ ] Test **not-found** — trả về 404 khi đúng điều kiện
- [ ] Test **browser back/forward** buttons hoạt động đúng với Client Router Cache

### 5.6 Layout & UI Tests

- [ ] Verify **Root Layout** render đúng html/body structure
- [ ] Test **nested layouts** — shared UI giữ nguyên khi navigate giữa child routes
- [ ] Test **layout không re-render** khi navigate giữa same-layout routes (performance check)
- [ ] Verify **metadata** render đúng:
  - [ ] `<title>` đúng cho từng page
  - [ ] `<meta description>` đúng
  - [ ] Open Graph tags đúng
  - [ ] Canonical URLs đúng
- [ ] Test **loading states** (`loading.tsx`) — xuất hiện trong quá trình data fetching
- [ ] Test **error states** (`error.tsx`) — hiển thị khi có lỗi, có nút retry
- [ ] Test **global error** (`global-error.tsx`) — fallback cho root layout errors
- [ ] Verify **fonts** render đúng (nếu dùng `next/font`)

### 5.7 Performance Tests

- [ ] Chạy **Lighthouse audit** trên tất cả critical pages:
  - [ ] Performance score >= 90 (hoặc không giảm so với trước migration)
  - [ ] LCP (Largest Contentful Paint) không tăng đáng kể
  - [ ] CLS (Cumulative Layout Shift) < 0.1
  - [ ] FID/INP được cải thiện hoặc giữ nguyên
- [ ] Kiểm tra **bundle size**:
  ```bash
  npm run build
  # Review .next/analyze/ nếu có @next/bundle-analyzer
  ```
- [ ] Verify **Server Components** không gửi unnecessary JavaScript xuống client
- [ ] Kiểm tra **Turbopack** build time (nếu đã enable):
  ```bash
  npm run dev -- --turbopack
  ```
- [ ] Test **streaming** và **Suspense** boundaries — nội dung quan trọng hiển thị nhanh, phần còn lại stream sau

### 5.8 Authentication & Authorization Tests

- [ ] Test **protected routes** — redirect đúng khi không có session
- [ ] Test **authentication flow** end-to-end (login → redirect → protected page)
- [ ] Test **logout** — session cleared, redirect về public page
- [ ] Test **session persistence** giữa page navigations
- [ ] Verify **middleware authentication** hoạt động đúng
- [ ] Test **role-based access control** nếu có

### 5.9 E2E Tests (Playwright / Cypress)

- [ ] Cập nhật tất cả selectors và routes trong E2E test files
- [ ] Test **critical user journeys** end-to-end:
  - [ ] Đăng ký / Đăng nhập
  - [ ] Core business flow (mua hàng, submit form, etc.)
  - [ ] Đăng xuất
- [ ] Test **form submissions** với Server Actions (nếu đã migrate)
- [ ] Test **real-time features** nếu có (WebSocket, SSE)
- [ ] Test **mobile responsiveness** qua Playwright viewport settings
- [ ] Test **accessibility** (a11y) với axe-playwright hoặc cypress-axe
- [ ] Chạy E2E tests trên **staging environment** trước khi deploy production

### 5.10 Final Pre-Deploy Checklist

- [ ] Chạy **full production build** thành công không có errors:
  ```bash
  npm run build
  ```
- [ ] Kiểm tra không có **TypeScript errors**:
  ```bash
  npx tsc --noEmit
  ```
- [ ] Kiểm tra không có **ESLint errors**:
  ```bash
  npm run lint
  ```
- [ ] Verify **environment variables** đã được set đúng cho production
- [ ] Test **production build locally**:
  ```bash
  npm run build && npm run start
  ```
- [ ] Kiểm tra **`pages/`** directory có thể xóa an toàn (tất cả đã migrate xong)
- [ ] Review **Next.js build output** (`next build` output) — verify routes được classify đúng (Static/Dynamic/ISR)
- [ ] Kiểm tra **Error Monitoring** (Sentry, etc.) vẫn hoạt động sau migration
- [ ] Verify **Analytics** tracking vẫn đúng (pageview events, etc.)
- [ ] Chuẩn bị **rollback plan** — tag Git release trước khi deploy
- [ ] Deploy lên **staging** và chạy smoke tests
- [ ] Monitoring **error rates** trong 24 giờ đầu sau khi deploy production

---

## 📝 Notes & Resources

### Codemods Hữu Ích

```bash
# Migrate built-in Next.js codemods
npx @next/codemod@latest built-in-next-font .
npx @next/codemod@latest new-link .
npx @next/codemod@latest next-image-to-legacy-image .
npx @next/codemod@latest next-async-request-api .  # Next.js 15

# Chạy tất cả codemods cho Next.js 15
npx @next/codemod@canary upgrade latest
```

### Tài Liệu Tham Khảo

| Tài liệu | Link |
|----------|------|
| App Router Migration Guide | https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration |
| Next.js 15 Upgrade Guide | https://nextjs.org/docs/app/building-your-application/upgrading/version-15 |
| Caching in Next.js | https://nextjs.org/docs/app/building-your-application/caching |
| Server Components | https://nextjs.org/docs/app/building-your-application/rendering/server-components |

### Tracking Progress

| Phần | Tổng tasks | Hoàn thành | % |
|------|-----------|------------|---|
| 1. Pre-Migration Audit | ~35 | 0 | 0% |
| 2. File Structure | ~20 | 0 | 0% |
| 3. Code Refactoring | ~55 | 0 | 0% |
| 4. Breaking Changes | ~30 | 0 | 0% |
| 5. Testing | ~45 | 0 | 0% |
| **Tổng** | **~185** | **0** | **0%** |

---

> ✅ **Migration hoàn thành khi:** Tất cả checkboxes đã được tick, production build thành công, E2E tests pass, và monitoring không phát hiện regression trong 48 giờ đầu sau deploy.