[Next.js Tutorial] Layouts: Hướng dẫn chi tiết để tối ưu giao diện Website

Bạn đã bao giờ thấy mình lặp đi lặp lại việc copy-paste Header, Navbar hay Footer vào mọi trang trong ứng dụng của mình chưa? Đó chính là lúc Layouts trong Next.js tỏa sáng như một "vị cứu tinh". Layouts không chỉ là một tính năng, chúng là nền tảng cho một kiến trúc web vững chắc, dễ bảo trì và siêu hiệu quả.

Nextjs Layouts

Trong bài viết này, chúng ta sẽ cùng nhau mổ xẻ mọi thứ về Layouts trong Next.js: từ khái niệm cốt lõi, sự khác biệt mang tính cách mạng giữa App RouterPages Router, cho đến các kỹ thuật nâng cao để xây dựng những giao diện phức tạp. Hãy sẵn sàng để nâng tầm kỹ năng Next.js của bạn! 🚀

Tại sao Layouts lại quan trọng đến vậy?

Trước khi đi vào cách triển khai, hãy hiểu tại sao bạn nên quan tâm đến Layouts.

  • Tái sử dụng (Don't Repeat Yourself - DRY): Đây là lợi ích rõ ràng nhất. Bạn chỉ cần định nghĩa các thành phần chung như header, footer, sidebar một lần duy nhất và áp dụng chúng cho nhiều trang.
  • Giao diện nhất quán: Đảm bảo mọi trang trong ứng dụng của bạn đều tuân theo một cấu trúc và phong cách chung, mang lại trải nghiệm người dùng liền mạch và chuyên nghiệp.
  • Tối ưu hiệu suất: Khi người dùng điều hướng giữa các trang chia sẻ cùng một layout, Next.js sẽ chỉ render lại phần nội dung của trang thay đổi. Layout sẽ không bị render lại, giúp giữ nguyên trạng thái (state) và tăng tốc độ chuyển trang đáng kể.
  • Quản lý code dễ dàng: Việc tách biệt logic của layout ra khỏi logic của trang giúp mã nguồn của bạn trở nên sạch sẽ, có tổ chức và dễ dàng bảo trì hay nâng cấp sau này.

Cuộc cách mạng Layouts với App Router (Next.js 13+)

Với sự ra đời của App Router, Next.js đã định nghĩa lại hoàn toàn cách chúng ta xây dựng layouts. Nó trở nên trực quan, mạnh mẽ và linh hoạt hơn bao giờ hết.

Website Template

1. layout.js - "Trái tim" của hệ thống

Trong App Router, layout được định nghĩa bằng cách tạo một file đặc biệt tên là layout.js (hoặc .tsx). File này sẽ tự động bao bọc tất cả các trang (page.js) và các layout con trong cùng một thư mục và các thư mục con của nó.

Root Layout

Mọi ứng dụng App Router đều bắt buộc phải có một Root Layout tại thư mục app/layout.js. Đây là layout cao nhất, nơi bạn định nghĩa các thẻ <html><body>.

Ví dụ về app/layout.js:

// app/layout.tsx
import './globals.css';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';

// Metadata - Tối ưu SEO một cách tuyệt vời
export const metadata = {
  title: 'My Awesome App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="vi">
      <body>
        <Navbar />
        <main>{children}</main> {/* children ở đây chính là nội dung của các trang (page.js) */}
        <Footer />
      </body>
    </html>
  );
}

💡 Điểm mấu chốt: Prop children chính là nơi nội dung của các trang hoặc các layout lồng nhau sẽ được render. Layout của bạn hoạt động như một cái khung, còn children là bức tranh bên trong.

2. Nested Layouts - Sức mạnh vượt trội

Đây là nơi App Router thực sự tỏa sáng. Bạn có thể tạo các layout lồng nhau một cách dễ dàng bằng cách thêm file layout.js vào các thư mục con. Layout con sẽ bao bọc các trang trong thư mục đó và được bao bọc bởi layout cha của nó.

Hãy tưởng tượng bạn có một trang quản trị (dashboard):

  • app/dashboard/layout.js: Layout này có thể chứa một thanh sidebar dành riêng cho trang quản trị.
  • app/dashboard/page.js: Trang tổng quan.
  • app/dashboard/settings/page.js: Trang cài đặt.

Ví dụ về app/dashboard/layout.js:

// app/dashboard/layout.tsx
import DashboardSidebar from '@/components/DashboardSidebar';

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <section className="dashboard-container">
      <DashboardSidebar />
      {/* children ở đây sẽ là page.js hoặc layout con khác trong thư mục dashboard */}
      <div className="dashboard-content">{children}</div>
    </section>
  );
}

Khi người dùng truy cập /dashboard/settings, cấu trúc component sẽ là:

  1. RootLayout (chứa Navbar và Footer)
  2. DashboardLayout (chứa DashboardSidebar)
  3. SettingsPage (nội dung trang cài đặt)

Điều tuyệt vời là khi bạn chuyển từ /dashboard sang /dashboard/settings, chỉ có nội dung trang thay đổi. Cả RootLayoutDashboardLayout đều được giữ nguyên, bảo toàn trạng thái và không cần render lại!

Cách tiếp cận "cổ điển" với Pages Router

Nếu bạn đang làm việc với một dự án cũ hơn sử dụng Pages Router, cách xử lý layout sẽ khác. Có hai mẫu phổ biến:

1. Layout cho từng trang (Per-Page Layouts)

Đây là cách linh hoạt nhất. Bạn định nghĩa một thuộc tính getLayout trên component trang của mình.

Bước 1: Tạo component Layout của bạn.

// components/DashboardLayout.js
export default function DashboardLayout({ children }) {
  return (
    <div>
      <DashboardSidebar />
      <main>{children}</main>
    </div>
  )
}

Bước 2: Chỉnh sửa file _app.js.

// pages/_app.js
export default function MyApp({ Component, pageProps }) {
  // Sử dụng layout được định nghĩa ở cấp độ trang, nếu có
  const getLayout = Component.getLayout || ((page) => page)

  return getLayout(<Component {...pageProps} />)
}

Bước 3: Áp dụng layout cho trang.

// pages/dashboard/index.js
import DashboardLayout from '@/components/DashboardLayout'

function DashboardPage() {
  return <h1>Welcome to the Dashboard!</h1>
}

// Gán layout cho trang này
DashboardPage.getLayout = function getLayout(page) {
  return <DashboardLayout>{page}</DashboardLayout>
}

export default DashboardPage

2. Layout chung trong _app.js

Nếu toàn bộ ứng dụng của bạn chỉ dùng chung một layout, bạn có thể bọc nó trực tiếp trong file pages/_app.js.

// pages/_app.js
import Layout from '../components/Layout'

export default function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

Cách này đơn giản nhưng kém linh hoạt vì khó có thể áp dụng các layout khác nhau cho các trang khác nhau.

Các khái niệm nâng cao & Tình huống thực tế

Layouts và Data Fetching

Trong App Router, Layouts mặc định là Server Components. Điều này có nghĩa là bạn có thể fetch dữ liệu trực tiếp bên trong chúng bằng async/await mà không cần useEffect hay getStaticProps.

// app/layout.tsx
async function getUserData() {
  // Giả sử hàm này lấy thông tin người dùng đã đăng nhập
  const user = await fetchUserDataFromAPI()
  return user
}

export default async function RootLayout({ children }) {
  const user = await getUserData()

  return (
    <html>
      <body>
        <Navbar user={user} /> {/* Truyền dữ liệu xuống component con */}
        <main>{children}</main>
      </body>
    </html>
  )
}

Layouts và State Management

Một trong những lợi ích lớn nhất của layout trong App Router là bảo toàn trạng thái. Vì layout không render lại khi chuyển trang, bất kỳ state nào (ví dụ: useState trong một Client Component) hoặc context provider nào được đặt trong layout sẽ được duy trì. Đây là điều lý tưởng cho các tính năng như giỏ hàng, trình phát nhạc, hoặc theme sáng/tối.

Conditional Layouts (Layout có điều kiện)

Làm thế nào để áp dụng các layout khác nhau cho các nhóm trang khác nhau (ví dụ: trang marketing và trang ứng dụng)? Route Groups trong App Router là câu trả lời. Bằng cách đặt một nhóm các route vào một thư mục có tên trong ngoặc đơn, ví dụ (marketing), thư mục này sẽ không ảnh hưởng đến URL nhưng cho phép bạn tạo một layout.js riêng cho nhóm đó.

  • app/(marketing)/layout.js: Layout cho trang chủ, trang giới thiệu, trang liên hệ.
  • app/(app)/layout.js: Layout cho các trang dashboard, settings khi người dùng đã đăng nhập.

Kết luận: Layouts và App Router là tương Lai

Layouts là một khái niệm nền tảng nhưng vô cùng quyền năng trong Next.js. Việc nắm vững cách sử dụng chúng, đặc biệt là với mô hình App Router, sẽ giúp bạn xây dựng các ứng dụng web không chỉ nhanh, mạnh mẽ mà còn có cấu trúc cực kỳ rõ ràng và dễ bảo trì.

Tính năngApp Router (app/layout.js)Pages Router (_app.js & getLayout)
Kiến trúcDựa trên file, lồng nhau tự độngTùy chỉnh trong _app.js hoặc trên từng trang
Lồng layoutHỗ trợ tự nhiên, rất mạnh mẽPhải tự triển khai thủ công, phức tạp hơn
Data FetchingĐơn giản với Server Components (async/await)Dùng getServerSideProps / getStaticProps
Bảo toàn StateTự động khi chuyển trang conKhó thực hiện một cách tự nhiên
Độ linh hoạtRất cao, dễ dàng mở rộngTốt với getLayout, nhưng code phức tạp hơn

Đó là toàn bộ nội dung bài viết, và giờ, bạn hãy bắt đầu kiến tạo những giao diện tuyệt vời với sức mạnh của Next.js Layouts ngay thôi nào!

Bài viết liên quan

[Next.js Tutorial] Cấu trúc thư mục và Routing cơ bản trong dự án

Làm thế nào để tổ chức dự án Next.js hiệu quả? Hướng dẫn chi tiết về cấu trúc thư mục và hệ thống routing cơ bản, giúp bạn xây dựng website Next.js nhanh chóng và chuẩn mực.

[Next.js Tutorial] Next.js là gì? Tại sao nên lựa chọn nó trong 2025?

Bạn có đang băn khoăn về Next.js? Bài viết này sẽ giải đáp Next.js là gì, tại sao nó lại quan trọng cho SEO và hiệu suất, và những lợi ích vượt trội khi sử dụng nó.

[Next.js Tutorial] Server Components vs. Client Components: Khi nào chọn cái nào?

So sánh chi tiết Server Components và Client Components trong Next.js. Tìm hiểu cách chúng hoạt động, ưu nhược điểm và khi nào nên sử dụng từng loại để tối ưu hiệu suất ứng dụng của bạn.

[Next.js Tutorial] Styling toàn tập: Lựa chọn nào là phù hợp nhất với bạn?

Hướng dẫn từng bước cách styling trong Next.js. Bài viết này bao gồm các best practices, tips tối ưu hiệu suất để giúp bạn xây dựng UI đẹp mắt và nhanh chóng.