Chuyển đến nội dung chính

Bài 8: Next.js & App Router

Next.js là gì, tại sao dùng Next.js. App Router vs Pages Router. File-based routing, layouts, templates, loading, error boundaries. Project structure chuẩn.

💻 Lập trình — Bài 8 Bài 8: Next.js & App Router

React & Next.js: Từ Cơ bản đến Nâng cao

Phần 3: Next.js Fundamentals

xdev.asia

1. Next.js là gì?

Next.js là React framework cho production, cung cấp SSR, SSG, file-based routing, API routes, và nhiều tính năng khác out-of-the-box. Được phát triển bởi Vercel.

FeatureReact (Vite)Next.js
RenderingClient-side onlySSR, SSG, ISR, Streaming
RoutingReact Router (manual)File-based (built-in)
SEOPoor (SPA)Excellent (SSR/SSG)
APISeparate backendRoute Handlers (built-in)
Image/FontManual optimizationnext/image, next/font
DeploymentAny static hostVercel, Docker, any Node.js host

2. Setup

npx create-next-app@latest my-app --typescript --tailwind --app --src-dir
cd my-app
npm run dev

3. App Router — File-based Routing

src/app/
├── layout.tsx          ← Root layout (required)
├── page.tsx            ← Home page (/)
├── loading.tsx         ← Loading UI
├── error.tsx           ← Error UI
├── not-found.tsx       ← 404 page
├── globals.css
│
├── about/
│   └── page.tsx        ← /about
│
├── blog/
│   ├── page.tsx        ← /blog
│   └── [slug]/
│       └── page.tsx    ← /blog/:slug (dynamic)
│
├── dashboard/
│   ├── layout.tsx      ← Nested layout
│   ├── page.tsx        ← /dashboard
│   ├── settings/
│   │   └── page.tsx    ← /dashboard/settings
│   └── [...catchAll]/
│       └── page.tsx    ← /dashboard/anything/else
│
└── (marketing)/        ← Route group (no URL segment)
    ├── layout.tsx
    ├── pricing/
    │   └── page.tsx    ← /pricing
    └── features/
        └── page.tsx    ← /features

4. Layouts

// src/app/layout.tsx — Root Layout
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: { default: 'My App', template: '%s | My App' },
  description: 'My awesome Next.js application',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="vi">
      <body className={inter.className}>
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

// src/app/dashboard/layout.tsx — Nested Layout
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex">
      <Sidebar />
      <div className="flex-1">{children}</div>
    </div>
  );
}

5. Pages

// src/app/page.tsx — Home page
export default function HomePage() {
  return <h1>Welcome to My App</h1>;
}

// src/app/blog/[slug]/page.tsx — Dynamic route
interface Props {
  params: Promise<{ slug: string }>;
}

export default async function BlogPost({ params }: Props) {
  const { slug } = await params;
  const post = await getPost(slug);

  if (!post) notFound(); // → not-found.tsx

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

// Generate static params for SSG
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map(post => ({ slug: post.slug }));
}

6. Loading & Error UI

// src/app/dashboard/loading.tsx
export default function Loading() {
  return <div className="animate-pulse">Loading dashboard...</div>;
}

// src/app/dashboard/error.tsx
'use client'; // Error components must be Client Components

import { useEffect } from 'react';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error(error);
  }, [error]);

  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

7. Navigation

import Link from 'next/link';
import { useRouter, usePathname } from 'next/navigation';

// Link component (prefetching built-in)
<Link href="/about">About</Link>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
<Link href="/dashboard" prefetch={false}>Dashboard</Link>

// Programmatic navigation
'use client';
function SearchForm() {
  const router = useRouter();
  const pathname = usePathname();

  const handleSubmit = (query: string) => {
    router.push(`/search?q=${query}`);
    // router.replace('/dashboard'); // Replace history
    // router.back(); // Go back
    // router.refresh(); // Re-fetch server components
  };
}

Bài tiếp theo: Server Components & Client Components — RSC, "use client", boundaries, và composition patterns.