1. Mở đầu
Sau một thời gian tập trung vào ReactJS với phương pháp Client Side Rendering (CSR), tôi bắt đầu tìm hiểu sâu hơn về Server Side Rendering (SSR) sử dụng ReactJS, đặc biệt là thông qua NextJS. NextJS là một framework mạnh mẽ hỗ trợ xây dựng ứng dụng SSR với ReactJS một cách hiệu quả. Bài viết này ghi lại quá trình tìm hiểu ban đầu của tôi về NextJS, chia sẻ những kinh nghiệm và kiến thức thu thập được, với hy vọng giúp bạn tiết kiệm thời gian khi chuyển từ CSR sang SSR với NextJS. Lưu ý rằng, đây không phải là một bài hướng dẫn chuyên sâu, mà là một bản ghi chép quá trình học tập. Để hiểu rõ hơn các nội dung được đề cập, bạn nên:
- Nắm vững kiến thức cơ bản về Client Side Rendering và Server Side Rendering.
- Có kiến thức nền tảng về ReactJS.
2. Tổng Quan Về NextJS
Ngay trên trang chủ của NextJS, bạn sẽ dễ dàng nhận thấy định nghĩa và ứng dụng chính của nó.
Về bản chất, NextJS cung cấp các lợi ích của SSR, bao gồm:
- Hiệu năng được cải thiện: So với các ứng dụng CSR truyền thống.
- Khả năng SEO tốt hơn: Điều mà CSR thường gặp khó khăn, đặc biệt là trong việc chia sẻ bài viết trên mạng xã hội.
Trang chủ NextJS cũng giới thiệu nhiều website nổi tiếng sử dụng NextJS để xây dựng các sản phẩm đa dạng, từ tài chính, tin tức đến thương mại điện tử. Trong bài viết này, tôi sẽ chia sẻ những gì tôi đã tìm hiểu, tập trung vào việc áp dụng các kỹ năng quen thuộc từ ReactJS sang môi trường NextJS.
a. Khởi Tạo Dự Án NextJS
Tương tự như create-react-app trong ReactJS, NextJS cung cấp công cụ create-next-app. Bạn có thể xem hướng dẫn cài đặt chi tiết tại trang chủ NextJS. Sau khi cài đặt thành công, bạn có thể tạo một dự án NextJS mới bằng lệnh:
npx create-next-app learn-nextjs
Cấu trúc thư mục cơ bản của một dự án NextJS sẽ như sau:
learn-nextjs/
├── node_modules/
├── pages/
├── public/
├── styles/
├── next.config.js
├── package.json
└── ...
Trong đó, các thư mục và file quan trọng cần lưu ý bao gồm:
components: Nơi chứa các component của ứng dụng.pages: Nơi định nghĩa các trang (route) của ứng dụng. Các file JavaScript trong thư mục này sẽ tự động được ánh xạ tới các route tương ứng.public: Chứa các file static như ảnh, fonts, và các tài nguyên khác.next.config.js: File cấu hình cho ứng dụng NextJS.
b. Sử Dụng SCSS Trong NextJS
ReactJS và NextJS đều tập trung vào việc xây dựng giao diện người dùng (UI). Do đó, việc styling các component là một trong những ưu tiên hàng đầu của tôi. Trong các dự án ReactJS, tôi thường sử dụng SCSS, và create-react-app đã hỗ trợ sẵn điều này. Tuy nhiên, để sử dụng SCSS trong NextJS, bạn cần cài đặt thêm package @zeit/next-sass (hiện tại package này đã không còn được maintain, thay vào đó bạn có thể sử dụng sass và cấu hình webpack):
npm install sass
Sau đó, tạo hoặc chỉnh sửa file next.config.js để thêm cấu hình Webpack:
const path = require('path');
module.exports = {
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
},
}
Lưu ý: Bạn cần import ít nhất một file .scss vào các file trong thư mục /pages. Nếu không, bạn có thể gặp phải một lỗi khá lạ là khi bạn vào trang không được import file .scss đầu tiên khi truy cập ứng dụng thì sẽ không bấm chuyển trang được.
c. Quản Lý File Static
Một yếu tố quan trọng khác trong xây dựng UI là quản lý các file static, đặc biệt là hình ảnh. Như đã đề cập ở trên, chúng ta có một thư mục public để lưu trữ các file static. Đối với hình ảnh, bạn nên tạo một thư mục con images bên trong public và lưu trữ ảnh ở đó. Bạn có thể tham khảo cách sử dụng ảnh trong component như sau:
function MyImage() {
return <img src="/images/my-image.png" alt="Hình ảnh minh họa trong ứng dụng NextJS" />;
}
d. Tối Ưu SEO Với next/head
Để hỗ trợ SEO và chia sẻ nội dung trên mạng xã hội, NextJS cung cấp component next/head. Cách sử dụng như sau:
import Head from 'next/head';
function IndexPage() {
return (
<div>
<Head>
<title>Trang chủ của bạn</title>
<meta name="description" content="Mô tả trang web của bạn." />
<meta property="og:title" content="Tiêu đề khi chia sẻ trên Facebook" />
<meta property="og:description" content="Mô tả khi chia sẻ trên Facebook" />
<meta property="og:image" content="/images/my-image.png" />
</Head>
<h1>Xin chào thế giới!</h1>
</div>
);
}
export default IndexPage;
Về cơ bản, nội dung bạn khai báo trong component <Head> sẽ được thêm vào phần <head> của trang HTML trả về cho người dùng. Điều này cho phép bạn tùy chỉnh các thông tin quan trọng như title, meta description, og:image, og:title, … để tối ưu hóa khả năng hiển thị khi chia sẻ trên các trang mạng xã hội như Facebook.
e. Truy Xuất Dữ Liệu Từ Server (getStaticProps, getServerSideProps)
Để có được các thông tin cần thiết cho phần <Head> (ví dụ: og:image, og:title), chúng ta cần lấy dữ liệu từ server. Ví dụ, khi bạn sao chép liên kết một bài viết và dán vào bình luận trên Facebook, Facebook sẽ hiển thị một bản xem trước với hình ảnh và tiêu đề. Để làm được điều này, trang web của bạn cần cung cấp các thông tin đó thông qua các thẻ meta.
NextJS cung cấp hai hàm chính để fetch dữ liệu từ server: getStaticProps và getServerSideProps.
getStaticProps: Được sử dụng để lấy dữ liệu tại thời điểm build (build time). Hàm này chỉ chạy trên server và cho phép bạn fetch dữ liệu từ bên ngoài (ví dụ: database, API) và truyền nó vào component của bạn dưới dạng props. Thích hợp cho các trang có nội dung không thay đổi thường xuyên.
export async function getStaticProps() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return {
props: {
posts,
},
};
}
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default Blog;
getServerSideProps: Được sử dụng để lấy dữ liệu trên mỗi request (request time). Hàm này cũng chỉ chạy trên server, nhưng nó sẽ được gọi mỗi khi người dùng truy cập trang. Thích hợp cho các trang có nội dung thay đổi thường xuyên hoặc cần thông tin xác thực người dùng.
export async function getServerSideProps(context) {
const res = await fetch(`https://api.example.com/posts`);
const posts = await res.json();
return {
props: {
posts,
},
};
}
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
export default Blog;
Lưu ý: Cả hai hàm getStaticProps và getServerSideProps chỉ có thể được sử dụng trong các file bên trong thư mục pages.
f. Định Tuyến (Routing) Trong NextJS
NextJS tự động tạo các router dựa trên cấu trúc file trong thư mục pages. Ví dụ:
pages/index.jstương ứng với URL gốc/.pages/post/index.jstương ứng với URL/post.pages/post/create.jstương ứng với URL/post/create.
Tuy nhiên, đối với các URL động như /post/:postId, chúng ta cần sử dụng dynamic routes. Để tạo dynamic routes, bạn cần đặt tên file trong thư mục pages bằng cách sử dụng dấu ngoặc vuông []. Ví dụ:
pages/post/[postId].js
Để truy cập giá trị của postId, bạn có thể sử dụng hook useRouter từ next/router:
import { useRouter } from 'next/router';
function Post() {
const router = useRouter();
const { postId } = router.query;
return <p>Bài viết: {postId}</p>;
}
export default Post;
g. Điều Hướng Trang Với next/link
NextJS cung cấp component next/link để điều hướng giữa các trang, tương tự như <Link> trong react-router. Cú pháp sử dụng như sau:
import Link from 'next/link';
function Home() {
return (
<Link href="/about">
<a>Về chúng tôi</a>
</Link>
);
}
export default Home;
Component <Link> yêu cầu một thuộc tính href trỏ đến đường dẫn cần thiết và phải chứa một thẻ con (ví dụ: <a>, <button>, <span>, …).
Ngoài ra, next/link còn có thuộc tính as để rewrite URL. Ví dụ, nếu bạn muốn sử dụng URL có dạng /post/:postId ở phía client, bạn có thể sử dụng next/link như sau:
import Link from 'next/link';
function Home() {
return (
<Link href="/post/[postId]" as="/post/1">
<a>Đến trang chi tiết bài viết</a>
</Link>
);
}
export default Home;
Trong ví dụ trên, href chỉ định đường dẫn thực tế của trang (trong trường hợp này là /post/[postId]), còn as chỉ định URL mà người dùng sẽ thấy trên trình duyệt (trong trường hợp này là /post/1).
3. Kết luận
Bài viết này đã trở nên khá dài, vì vậy tôi sẽ tạm dừng ở đây. Trong phần tiếp theo, tôi sẽ chia sẻ thêm những điều khác mà tôi đã tìm hiểu được về NextJS. Nếu bạn phát hiện bất kỳ sai sót nào hoặc có bất kỳ câu hỏi nào, vui lòng để lại bình luận bên dưới. Cảm ơn bạn đã đọc bài viết.
