Xây dựng dapp trên cardano từ con số không
About Lesson

Fullstacks framework Nextjs

1. Next.js Là Gì?

Next.js là một framework dựa trên React, cung cấp các tính năng mạnh mẽ để phát triển ứng dụng web hiệu suất cao, tối ưu SEO và dễ dàng mở rộng. Một số tính năng nổi bật bao gồm:

  • Server-Side Rendering (SSR): Render trang trên server, cải thiện SEO và tốc độ tải trang.
  • Static Site Generation (SSG): Tạo trang tĩnh tại thời điểm build, lý tưởng cho các trang nội dung cố định.
  • Incremental Static Regeneration (ISR): Cập nhật nội dung tĩnh mà không cần rebuild toàn bộ ứng dụng.
  • API Routes: Cho phép tạo API endpoints ngay trong dự án.
  • Server Components: Giảm tải JavaScript trên client, tăng hiệu suất.
  • Tối ưu hóa tài nguyên: Hỗ trợ tối ưu hình ảnh, font, và script.
  • Middleware: Xử lý request trước khi đến route.

Ưu điểm so với React thuần

  • Routing tích hợp: Không cần thư viện bổ sung như React Router.
  • Hiệu suất cao: Kết hợp SSR và SSG giúp giảm tải cho trình duyệt.
  • Full-stack development: Hỗ trợ xây dựng cả front-end và back-end trong cùng một dự án.
  • SEO thân thiện: Nội dung được render trước khi gửi đến client.

2. Thiết Lập Môi Trường và Khởi Tạo Dự Án

Yêu cầu môi trường

  • Node.js: Phiên bản 18 hoặc cao hơn.
  • IDE: VS Code hoặc bất kỳ IDE nào hỗ trợ JavaScript/TypeScript.
  • Tài khoản cloud: Vercel, Netlify, hoặc Cloudflare Pages để triển khai.

Cài đặt Next.js

Chạy lệnh sau để tạo dự án Next.js:

npx create-next-app@latest my-app
 

Lệnh này sẽ hỏi bạn một số tùy chọn:

  • TypeScript (-ts): Sử dụng TypeScript để kiểm tra kiểu dữ liệu.
  • Tailwind CSS (-tailwind): Tích hợp Tailwind CSS để tạo giao diện nhanh.
  • ESLint (-eslint): Kiểm tra lỗi code trong quá trình phát triển.

Sau khi cài đặt, di chuyển vào thư mục dự án và chạy:

cd my-app
npm run dev
 

Ứng dụng sẽ chạy trên http://localhost:3000. Để đổi port, bạn có thể chỉnh trong next.config.js:

module.exports = {
  port: 4000,
};
 

Hoặc chạy lệnh:

PORT=4000 npm run dev
 

Cấu trúc thư mục

Cấu trúc thư mục cơ bản của một dự án Next.js (sử dụng App Router trong Next.js 13+):

  • app/: Chứa các route và logic chính của ứng dụng.
  • components/: Các component React tái sử dụng.
  • public/: Tài nguyên tĩnh như hình ảnh, font.
  • styles/: File CSS toàn cục (ví dụ: global.css).
  • next.config.js: Cấu hình dự án.
  • package.json: Quản lý dependencies và scripts.
  • .env: Lưu biến môi trường.

Các file quan trọng trong thư mục app/:

  • page.tsx: Định nghĩa nội dung của một route.
  • layout.tsx: Định nghĩa bố cục chung cho các trang.
  • not-found.tsx: Trang hiển thị khi route không tồn tại.

3. Hệ Thống Routing Trong Next.js

Next.js sử dụng App Router (từ Next.js 13) để quản lý routing dựa trên cấu trúc thư mục trong app/. Một số khái niệm cơ bản:

  • Page: Mỗi file page.tsx trong thư mục app/ tương ứng với một route. Ví dụ: app/about/page.tsx tạo route /about.

  • Layout: File layout.tsx định nghĩa bố cục chung cho một nhóm route. Ví dụ:

    export default function Layout({ children }: { children: React.ReactNode }) {
      return (
        <div>
          <header>My Header</header>
          {children}
          <footer>My Footer</footer>
        </div>
      );
    }
     
  • Dynamic Routing: Tạo route động bằng cách sử dụng thư mục với tên trong dấu ngoặc vuông, ví dụ: app/post/[id]/page.tsx. Để lấy tham số động:

    import { useParams } from "next/navigation";
    
    export default function PostPage() {
      const params = useParams();
      const { id } = params;
      return <div>Post ID: {id}</div>;
    }
     
  • Group Routing: Sử dụng dấu ngoặc tròn (group) để gộp các route mà không ảnh hưởng đến URL. Ví dụ: app/(auth)/login/page.tsx tạo route /login.

  • Private Folders: Sử dụng dấu gạch dưới _folder để ẩn thư mục khỏi routing.

4. Client Component vs Server Component

Client Component

  • Được đánh dấu bằng directive "use client".

  • Chạy trên trình duyệt, sử dụng các React hooks như useStateuseEffect.

  • Phù hợp cho các tác vụ tương tác với trình duyệt (ví dụ: windowlocalStorage).

  • Nhược điểm: Tăng kích thước JavaScript bundle, có thể ảnh hưởng đến SEO.

  • Ví dụ:

    "use client";
    import { useState, useEffect } from "react";
    
    export default function ClientComponent() {
      const [data, setData] = useState(null);
    
      useEffect(() => {
        fetch("/api/tasks")
          .then((res) => res.json())
          .then((data) => setData(data));
      }, []);
    
      return <div>{data ? JSON.stringify(data) : "Loading..."}</div>;
    }
     

Server Component

  • Mặc định trong Next.js (không cần directive).

  • Render trên server, không gửi JavaScript đến client.

  • Hỗ trợ async/await trực tiếp trong component.

  • Ưu điểm: Giảm tải cho client, cải thiện SEO và hiệu suất.

  • Ví dụ:

    async function fetchTasks() {
      const res = await fetch("http://localhost:3000/api/tasks");
      return res.json();
    }
    
    export default async function ServerComponent() {
      const tasks = await fetchTasks();
      return <div>{JSON.stringify(tasks)}</div>;
    }
     

5. Data Fetching Trong Next.js

  • Server Component: Sử dụng fetch để lấy dữ liệu trực tiếp trên server:

    async function getData() {
      const res = await fetch("https://api.example.com/data", {
        cache: "force-cache",
      });
      return res.json();
    }
    
    export default async function Page() {
      const data = await getData();
      return <div>{data.name}</div>;
    }
     
  • Client Component: Sử dụng thư viện như useSWR để fetch và revalidate dữ liệu:

     
    "use client";
    import useSWR from "swr";
    
    const fetcher = (url: string) => fetch(url).then((res) => res.json());
    
    export default function ClientPage() {
      const { data, error } = useSWR("/api/tasks", fetcher);
      if (error) return <div>Error loading data</div>;
      if (!data) return <div>Loading...</div>;
      return <div>{JSON.stringify(data)}</div>;
    }
     

6. Tạo API Routes

Next.js cho phép tạo API endpoints trong thư mục app/api/. Ví dụ, tạo API để quản lý danh sách công việc (tasks):

 
// app/api/tasks/route.ts
import { NextResponse } from "next/server";

const tasks = [
  { id: 1, title: "Task 1" },
  { id: 2, title: "Task 2" },
];

export async function GET() {
  return NextResponse.json(tasks);
}
 
  • Truy cập API tại: http://localhost:3000/api/tasks.
  • Hỗ trợ các phương thức HTTP: GETPOSTPUTDELETE, v.v.
  • Bảo mật: Sử dụng Middleware hoặc NextAuth để bảo vệ API.

7. Triển Khai Ứng Dụng Next.js

  • Build dự án:
    npm run build
     
  • Chạy production:
    npm run start
     
  • Triển khai lên Vercel:
    1. Đăng ký tài khoản trên Vercel.
    2. Liên kết dự án với repository Git (GitHub, GitLab, Bitbucket).
    3. Chạy lệnh:
      vercel
       
    4. Vercel tự động build và deploy ứng dụng.
  • Các nền tảng khác: Netlify, Cloudflare Pages, hoặc server riêng (cần cấu hình next start).

8. Kết Luận

Next.js là một công cụ mạnh mẽ để xây dựng ứng dụng full-stack với hiệu suất cao, SEO tốt và khả năng mở rộng linh hoạt. Bằng cách tận dụng Server Components, API Routes, và các tính năng như SSR/SSG, bạn có thể tạo ra các ứng dụng hiện đại, tối ưu cho cả người dùng và nhà phát triển.

Hãy bắt đầu với Next.js bằng cách tạo một dự án mẫu, khám phá các tính năng routing, và thử triển khai lên Vercel để trải nghiệm quy trình phát triển full-stack hoàn chỉnh!

Bài Tập

📝 Bài tập 1: Tạo dự án Next.js cơ bản

Đề bài

Tạo một ứng dụng Next.js cơ bản hiển thị trang chủ với tiêu đề “Chào mừng đến với Next.js”.

Yêu cầu

  • Sử dụng lệnh tạo dự án Next.js.
  • Tạo trang chủ (page.tsx) hiển thị tiêu đề.
  • Chạy ứng dụng và kiểm tra trên trình duyệt.
  • Đảm bảo sử dụng TypeScript.
Cách giải
  1. Khởi tạo dự án:
    • Cài Node.js (phiên bản 18 trở lên) và npm.
    • Chạy lệnh: npx create-next-app@latest my-next-app.
    • Chọn các tùy chọn: TypeScript, App Router, và các cấu hình mặc định khác.
    • Di chuyển vào thư mục: cd my-next-app và chạy: npm install.
  2. Tạo trang chủ:
    • Sửa file app/page.tsx để hiển thị tiêu đề.
  3. Chạy ứng dụng:
    • Chạy: npm run dev và kiểm tra tại http://localhost:3000.

Đáp án

Tạo dự án:

npx create-next-app@latest my-next-app
 

Sửa file app/page.tsx:

export default function Home() {
  return (
    <div>
      <h1>Chào mừng đến với Next.js</h1>
    </div>
  );
}
 

Chạy npm run dev, truy cập http://localhost:3000 để thấy tiêu đề “Chào mừng đến với Next.js”.


📝 Bài tập 2: Tạo layout chung cho ứng dụng

Đề bài

Tạo một layout chung cho ứng dụng Next.js, bao gồm header và footer áp dụng cho tất cả các trang.

Yêu cầu

  • Tạo file layout.tsx trong thư mục app.
  • Thêm header với tiêu đề “My App” và footer với nội dung “© 2025 My App”.
  • Đảm bảo các trang con kế thừa layout này.
  • Áp dụng CSS cơ bản để định dạng.
Cách giải
  1. Tạo layout:
    • Sửa file app/layout.tsx để định nghĩa layout chung.
    • Thêm thẻ <header> và <footer>, sử dụng {children} để render các trang con.
  2. Thêm CSS:
    • Sử dụng file app/globals.css để định dạng header và footer.
  3. Kiểm tra:
    • Đảm bảo trang chủ (page.tsx) hiển thị trong layout.

Đáp án

Sửa file app/layout.tsx:

import "./globals.css";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="vi">
      <body>
        <header>
          <h1>My App</h1>
        </header>
        <main>{children}</main>
        <footer>© 2025 My App</footer>
      </body>
    </html>
  );
}
 

Sửa file app/globals.css:

header {
  background-color: #333;
  color: white;
  padding: 10px;
  text-align: center;
}

footer {
  background-color: #333;
  color: white;
  padding: 10px;
  text-align: center;
  position: fixed;
  bottom: 0;
  width: 100%;
}

main {
  padding: 20px;
}
 

Chạy npm run dev, truy cập http://localhost:3000 để thấy layout với header và footer bao quanh nội dung trang chủ.


📝 Bài tập 3: Tạo trang About với Dynamic Routing

Đề bài

Tạo một trang “About” với dynamic routing để hiển thị thông tin chi tiết dựa trên ID.

Yêu cầu

  • Tạo một trang /about/[id] hiển thị thông tin dựa trên ID từ URL.
  • Sử dụng useParams để lấy ID.
  • Hiển thị thông báo lỗi nếu ID không hợp lệ.
  • Áp dụng CSS để trang trông đẹp mắt.
Cách giải
  1. Tạo dynamic route:
    • Tạo thư mục app/about/[id] và file page.tsx trong đó.
    • Sử dụng useParams để lấy ID từ URL.
  2. Xử lý logic:
    • Dùng mảng dữ liệu giả lập để tìm thông tin theo ID.
    • Nếu không tìm thấy ID, hiển thị thông báo lỗi.
  3. Thêm CSS:
    • Sử dụng inline CSS hoặc file CSS riêng để định dạng.

Đáp án

Tạo file app/about/[id]/page.tsx:

"use client";
import { useParams } from "next/navigation";

export default function AboutPage() {
  const params = useParams();
  const id = params.id;

  const data = [
    { id: "1", content: "Thông tin về mục 1" },
    { id: "2", content: "Thông tin về mục 2" },
  ];

  const item = data.find((item) => item.id === id);

  return (
    <div style={{ padding: "20px", textAlign: "center" }}>
      {item ? (
        <div>
          <h2>Chi tiết About</h2>
          <p>ID: {id}</p>
          <p>{item.content}</p>
        </div>
      ) : (
        <p style={{ color: "red" }}>Không tìm thấy thông tin cho ID: {id}</p>
      )}
    </div>
  );
}
 

Chạy npm run dev, truy cập http://localhost:3000/about/1 để thấy thông tin chi tiết, hoặc http://localhost:3000/about/999 để thấy thông báo lỗi.


📝 Bài tập 4: Tạo API Route trong Next.js

Đề bài

Tạo một API route để trả về danh sách tasks từ server.

Yêu cầu

  • Tạo API route tại /api/tasks trả về danh sách tasks ở định dạng JSON.
  • Tạo trang hiển thị danh sách tasks bằng cách fetch dữ liệu từ API route.
  • Sử dụng useEffect và useState để quản lý dữ liệu ở client-side.
  • Định dạng danh sách bằng CSS.
Cách giải
  1. Tạo API route:
    • Tạo file app/api/tasks/route.ts để định nghĩa API trả về danh sách tasks.
  2. Tạo trang hiển thị:
    • Sửa file app/page.tsx để fetch dữ liệu từ /api/tasks và hiển thị danh sách.
    • Sử dụng useEffect để gọi API khi trang được tải.
  3. Thêm CSS:
    • Dùng globals.css để định dạng danh sách.

Đáp án

Tạo file app/api/tasks/route.ts:

import { NextResponse } from "next/server";

const tasks = [
  { id: 1, title: "Task 1", description: "Mô tả task 1" },
  { id: 2, title: "Task 2", description: "Mô tả task 2" },
];

export async function GET() {
  return NextResponse.json(tasks);
}
 

Sửa file app/page.tsx:

"use client";
import { useState, useEffect } from "react";

export default function Home() {
  const [tasks, setTasks] = useState([]);

  useEffect(() => {
    async function fetchTasks() {
      const response = await fetch("/api/tasks");
      const data = await response.json();
      setTasks(data);
    }
    fetchTasks();
  }, []);

  return (
    <div style={{ padding: "20px" }}>
      <h1>Danh sách Tasks</h1>
      <ul style={{ listStyle: "none", padding: 0 }}>
        {tasks.map((task: any) => (
          <li
            key={task.id}
            style={{
              margin: "10px 0",
              padding: "10px",
              border: "1px solid #ccc",
            }}
          >
            <h3>{task.title}</h3>
            <p>{task.description}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
 

Chạy npm run dev, truy cập http://localhost:3000 để thấy danh sách tasks, và http://localhost:3000/api/tasks để kiểm tra API trả về JSON.


📝 Bài tập 5: Phân biệt Server-side và Client-side trong Next.js

Đề bài

Tạo một trang hiển thị thông tin task chi tiết, với logic xử lý ở cả server-side và client-side, và so sánh sự khác biệt.

Yêu cầu

  • Tạo trang /tasks/[id] hiển thị chi tiết task theo ID.
  • Triển khai logic server-side (dùng getServerSideProps hoặc API route) và client-side (dùng useEffect).
  • Hiển thị console log để kiểm tra logic chạy ở server hay client.
  • Đưa ra ưu/nhược điểm của mỗi cách.
Cách giải
  1. Server-side:
    • Tạo API route /api/tasks/[id] để trả về chi tiết task.
    • Tạo trang /tasks/[id] dùng server-side để fetch dữ liệu từ API.
  2. Client-side:
    • Tạo trang /tasks/[id] dùng useEffect để fetch dữ liệu từ API.
  3. Console log:
    • Thêm console.log trong API route (server) và useEffect (client).
  4. So sánh:
    • Server-side: Bảo mật hơn, tốt cho SEO, nhưng tải chậm hơn.
    • Client-side: Nhanh hơn khi chuyển trang, nhưng logic lộ trên trình duyệt.

Đáp án

Tạo file app/api/tasks/[id]/route.ts:

import { NextResponse } from "next/server";

const tasks = [
  { id: "1", title: "Task 1", description: "Mô tả task 1" },
  { id: "2", title: "Task 2", description: "Mô tả task 2" },
];

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  console.log("Server-side: Fetching task with ID:", params.id);
  const task = tasks.find((t) => t.id === params.id);
  if (!task) {
    return NextResponse.json({ error: "Task not found" }, { status: 404 });
  }
  return NextResponse.json(task);
}
 

Client-side: Tạo file app/tasks/[id]/page.tsx:

"use client";
import { useState, useEffect } from "react";
import { useParams } from "next/navigation";

export default function TaskDetail() {
  const params = useParams();
  const id = params.id;
  const [task, setTask] = useState(null);

  useEffect(() => {
    console.log("Client-side: Fetching task with ID:", id);
    async function fetchTask() {
      const response = await fetch(`/api/tasks/${id}`);
      const data = await response.json();
      setTask(data);
    }
    fetchTask();
  }, [id]);

  return (
    <div style={{ padding: "20px", textAlign: "center" }}>
      {task && !task.error ? (
        <div>
          <h2>{task.title}</h2>
          <p>{task.description}</p>
        </div>
      ) : (
        <p style={{ color: "red" }}>Không tìm thấy task với ID: {id}</p>
      )}
    </div>
  );
}
 

Server-side: Tạo file app/tasks/[id]/server/page.tsx:

import { notFound } from "next/navigation";

async function fetchTask(id: string) {
  console.log("Server-side: Fetching task with ID:", id);
  const response = await fetch(`http://localhost:3000/api/tasks/${id}`);
  const data = await response.json();
  return data;
}

export default async function TaskDetail({
  params,
}: {
  params: { id: string };
}) {
  const task = await fetchTask(params.id);

  if (task.error) {
    notFound();
  }

  return (
    <div style={{ padding: "20px", textAlign: "center" }}>
      <h2>{task.title}</h2>
      <p>{task.description}</p>
    </div>
  );
}
 

So sánh:

  • Server-side: Logic chạy trên server, console log xuất hiện trong terminal dự án. Ưu điểm: Bảo mật, tốt cho SEO. Nhược điểm: Tải chậm hơn do request server.
  • Client-side: Logic chạy trên trình duyệt, console log xuất hiện trong tab Console của trình duyệt. Ưu điểm: Nhanh khi chuyển trang. Nhược điểm: Logic lộ, không tốt cho SEO nếu không tối ưu.

Chạy npm run dev, truy cập http://localhost:3000/tasks/1 (client-side) và http://localhost:3000/tasks/1/server (server-side) để kiểm tra.