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ả.
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 Router và Pages 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.
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>
và <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ònchildren
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à:
RootLayout
(chứa Navbar và Footer)DashboardLayout
(chứaDashboardSidebar
)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ả RootLayout
và DashboardLayout
đề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ăng | App Router (app/layout.js ) | Pages Router (_app.js & getLayout ) |
---|---|---|
Kiến trúc | Dựa trên file, lồng nhau tự động | Tùy chỉnh trong _app.js hoặc trên từng trang |
Lồng layout | Hỗ 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 State | Tự động khi chuyển trang con | Khó thực hiện một cách tự nhiên |
Độ linh hoạt | Rất cao, dễ dàng mở rộng | Tố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!