DEV Community

Phúc Nguyễn
Phúc Nguyễn

Posted on

Có thể bạn chưa biết (Phần 1)

Trong những năm tháng gần đây xu hướng của lập trình frontend thường nghiêng về phía lập trình viên ReactJS. Với sự ra đời cải tiến của NextJS 13, xu hướng này càng trở nên phổ biến hơn bao giờ hết. Với những tính năng mở rộng đặc biệt về SSR, SSG giúp tối ưu hoá SEO, hiệu suất và trải nghiệm người dùng. Một loạt những tính năng mới đó giúp các lập trình viên dễ dàng triển khai các ứng dụng web phức tạp với ít công sức hơn.

Ngày tôi mới bước chân vào Framework này, tôi tự hỏi liệu nó có gì đặc biệt hơn ReactJS mà lại thu hút các lập trình viên chuyển đổi xu hướng đến vậy. Tôi bắt đầu mày mò tìm hiểu, nhiều người nói rằng NextJS khác gì ReactJS đâu, code ReactJS là code được NextJS mà, nhưng càng tìm hiểu sâu vào nó tôi càng nhận ra Next không chỉ là một Framework đơn giản để chạy trên ứng dụng React.

Ngày hôm nay tôi viết bài viết này để chia sẻ cho mọi người thấy được những sự khác biệt đôi khi đến từ những điều rất đơn giản mà vô tình mọi người không để ý.

Phần 1 - Những thẻ đặc biệt trong NextJS

1 <Head>

Thẻ Head trong NextJS cho phép bạn chèn các metadata vào phần <head> của trang html, bao gồm tiêu đề, mô tả. liên kết đến các file CSS/JS và các thẻ metadata khác. Điều này rất hữu ích với SEO và trải nghiệm người dùng, đặc biệt trong các case điều chỉnh cụ thể cho từng trang.

import Head from 'next/head'

function IndexPage() {
  return (
    <div>
      <Head>
        <title>My page title</title>
      </Head>
      <p>Hello world!</p>
    </div>
  )
}

export default IndexPage
Enter fullscreen mode Exit fullscreen mode

2. <Link>

Có thể bạn đã quá quen với việc sử dụng thẻ <a> để tạo liên kết nội bộ giữa các trang trong ứng dụng. Tuy nhiên, khi làm việc với NextJS, bạn nên sử dụng thẻ <Link>. Thẻ này có một điểm đặc biệt, được đội ngũ Next phát triển để tối ưu hóa tính năng client-side navigation. Điều này giúp người dùng điều hướng nhanh chóng giữa các trang mà không cần tải lại toàn bộ trang.

Khi bạn sử dụng thẻ <Link> để liên kết đến trang B trong khi đang ở trang A, mặc định thẻ <Link> sẽ sử dụng cơ chế prefetching, tự động tải trước nội dung của trang B. Khi người dùng click vào liên kết, trang B đã gần như sẵn sàng để hiển thị. Tuy nhiên, cơ chế này cũng có những hạn chế đối với hiệu suất của trang web.

Khi một trang có nhiều thẻ <Link>, điều này có thể dẫn đến việc tải trước quá nhiều trang không cần thiết, gây ra hiệu ứng làm chậm tốc độ tải trang, ảnh hưởng đến trải nghiệm người dùng.

May mắn thay, chúng ta có thể tùy chỉnh cơ chế prefetching này một cách thủ công bằng cách sử dụng thuộc tính prefetch={false} trong thẻ <Link>:

<Link href="/about" prefetch={false}>
  <a>Go to About Page</a>
</Link>
Enter fullscreen mode Exit fullscreen mode

Ngoài ra, có một số tùy chọn rất hay mà bạn có thể sử dụng với thẻ <Link> trong NextJS:

  • scroll: Tùy chọn này cho phép bạn quyết định liệu trang có tự động cuộn lên đầu khi người dùng điều hướng sang trang mới hay không. Điều này hữu ích khi bạn muốn giữ nguyên vị trí hiện tại của người dùng tại trang cũ, thay vì bắt đầu lại từ đầu trang mới.

  • shallow: Tùy chọn này giúp bạn điều hướng giữa các trang mà không cần tải lại dữ liệu hoặc làm mới hoàn toàn toàn bộ trang. Khi sử dụng shallow={true}, NextJS chỉ thay đổi URL mà không làm ảnh hưởng đến toàn bộ UI của trang web. Điều này giúp cải thiện hiệu suất, đặc biệt trong các ứng dụng có trạng thái người dùng cần được giữ nguyên khi điều hướng giữa các trang.

Ví dụ cụ thể về shallow

import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';

function BlogPage() {
  const router = useRouter();
  const { category } = router.query; // Lấy giá trị query từ URL
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    // Giả sử bạn gọi API hoặc tải dữ liệu bài viết theo danh mục
    if (category) {
      console.log(`Fetching posts for category: ${category}`);
      // Gọi API để lấy các bài viết theo danh mục
      fetch(`/api/posts?category=${category}`)
        .then((res) => res.json())
        .then((data) => setPosts(data));
    }
  }, [category]); // Chạy lại khi 'category' thay đổi

  return (
    <div>
      <h1>Blog - {category}</h1>
      <div>
        {posts.length > 0 ? (
          posts.map((post) => <div key={post.id}>{post.title}</div>)
        ) : (
          <p>No posts available.</p>
        )}
      </div>
    </div>
  );
}

export default BlogPage;

Enter fullscreen mode Exit fullscreen mode

Giải thích về cách hoạt động trong ví dụ trên

Cập nhật URL mà không reload trang:

Khi người dùng nhấp vào "Tech" hoặc "Design" trong thành phần MyComponent, URL sẽ thay đổi thành /blog?category=tech hoặc /blog?category=design. Tuy nhiên, vì chúng ta sử dụng tùy chọn shallow={true} trong thẻ <Link>, trang không bị tải lại, chỉ có URL trong thanh địa chỉ thay đổi.

Điều này có nghĩa là NextJS sẽ cập nhật URL mà không reload lại toàn bộ trang. Mặc dù URL thay đổi và có thể người dùng đang ở một "trang" khác, nhưng trang hiện tại vẫn giữ nguyên mà không phải tải lại từ đầu. Điều này giúp cải thiện hiệu suất và mang lại trải nghiệm người dùng mượt mà hơn.

Cập nhật nội dung theo danh mục:

Trong ví dụ trên, chúng ta sử dụng useEffect để theo dõi sự thay đổi của category trong URL, giá trị của category được lấy thông qua router.query từ NextJS Router.

Khi URL thay đổi, cụ thể là khi category thay đổi (ví dụ: từ tech sang design), useEffect sẽ gọi lại API để tải lại dữ liệu mới theo danh mục hiện tại. API sẽ trả về danh sách các bài viết liên quan đến danh mục đó, và chúng ta sẽ cập nhật state posts với các bài viết mới.

Điều này có nghĩa là nội dung của trang sẽ được cập nhật mà không cần phải tải lại toàn bộ trang. Các bài viết sẽ được tải và hiển thị lại chỉ với phần dữ liệu mới mà không làm tải lại toàn bộ giao diện trang. Điều này mang lại hiệu suất tối ưu và cải thiện trải nghiệm người dùng, vì người dùng không phải chờ đợi trang tải lại hoàn toàn mỗi khi điều hướng giữa các danh mục.

Tóm tắt:

  • Cập nhật URL mà không reload trang: Với shallow={true}, NextJS thay đổi URL trong thanh địa chỉ mà không tải lại toàn bộ trang.
  • Cập nhật nội dung theo danh mục: useEffect theo dõi sự thay đổi của category trong URL, gọi lại API và cập nhật dữ liệu mới mà không làm reload trang.

locale

Tùy chọn locale trong thẻ <Link> cho phép bạn chỉ định một locale cụ thể cho liên kết. Điều này cực kỳ hữu ích khi bạn phát triển một ứng dụng đa ngôn ngữ và muốn cho phép người dùng điều hướng đến các phiên bản ngôn ngữ khác nhau của cùng một trang.

Khi bạn có một ứng dụng NextJS hỗ trợ nhiều ngôn ngữ, bạn có thể sử dụng thuộc tính locale để điều hướng đến phiên bản ngôn ngữ tương ứng của trang hiện tại. Ví dụ, bạn có thể tạo một liên kết đến trang "About" nhưng với ngôn ngữ khác, như tiếng Pháp hoặc tiếng Tây Ban Nha, thay vì tiếng Anh mặc định.

Ví dụ:

Giả sử bạn có một trang "About" và muốn tạo một liên kết dẫn đến phiên bản tiếng Pháp của trang này, bạn có thể sử dụng locale="fr" trong thẻ <Link> như sau:

<Link href="/about" locale="fr">
  <a>Go to About Page (French)</a>
</Link>
Enter fullscreen mode Exit fullscreen mode

3. <Image>

Thẻ <Image> trong Next.js được thiết kế để tối ưu hóa việc hiển thị hình ảnh. Thay vì sử dụng thẻ <img> thông thường, bạn có thể sử dụng <Image> để Next.js tự động xử lý việc tải ảnh một cách thông minh, giảm thiểu dung lượng tải về, tối ưu hóa kích thước hình ảnh theo từng thiết bị, và hỗ trợ lazy loading.

Next JS hỗ trợ cache ảnh rất thông minh. Khi ảnh của bạn được yêu cầu lần đầu tiên, nó sẽ load ảnh và lưu vào bộ nhớ cache, vì vậy các lần yêu cầu tải ảnh tiếp theo sẽ nhanh hơn rất nhiều.

Ví dụ ảnh của mình trong public có tên là:

Image description

Tuy nhiên, sau khi được load thì nó sẽ có thêm một cái đuôi:

Image description

  • Đuôi c1575e8a là một mã hash được sinh ra khi Next.js tối ưu hóa ảnh (hoặc các tài nguyên tĩnh khác như CSS, JS). Mã hash này là duy nhất cho mỗi ảnh và thay đổi nếu ảnh thay đổi.
  • Mã hash này giúp ngăn ngừa vấn đề cache cũ trong trình duyệt: nếu ảnh thay đổi (ví dụ: bạn cập nhật hoặc thay đổi ảnh), mã hash sẽ thay đổi và trình duyệt sẽ tải lại ảnh mới thay vì sử dụng ảnh cũ từ bộ nhớ cache.
  • Thư mục /_next/static/ là nơi Next.js lưu trữ các tệp static sau khi chúng đã được tối ưu hóa. Khi bạn sử dụng Next.js để tối ưu hóa hình ảnh, ảnh đó sẽ được di chuyển vào thư mục này sau khi được xử lý.

Ngoài ra còn có một số props hay dùng:

  • priority: Đánh dấu ảnh là ưu tiên tải trước các ảnh khác. Cái này có thể ảnh hưởng đến việc cache, vì Next.js sẽ chắc chắn tải ảnh đó đầu tiên.
  • quality: Đặt chất lượng ảnh từ 1-100. Giá trị mặc định là 75, và bạn có thể giảm chất lượng để tiết kiệm băng thông, nhưng vẫn giữ ảnh ở độ phân giải đủ tốt.
  • placeholderblurDataURL: Những tính năng này giúp tạo hiệu ứng placeholder khi ảnh chưa được tải hoàn toàn. Tuy nhiên, ảnh thực tế sẽ được cache và tải nhanh hơn sau khi đã được tối ưu. Lưu ý thuộc tính này chỉ áp dụng được với những ảnh có đuôi .jpg, .png, hoặc .webp.
  • fill: Khi sử dụng fill={true}, Next.js sẽ tự động tính toán kích thước ảnh dựa trên không gian có sẵn và làm cho ảnh phù hợp với bố cục. Điều này vẫn hoạt động với tính năng cache ảnh của Next.js.
  • unoptimized: Trong Next.js, thuộc tính unoptimized không phải là một thuộc tính bắt buộc trong mọi trường hợp, và bạn không phải lúc nào cũng cần phải thêm nó vào thẻ <Image>. Tuy nhiên, có một số trường hợp mà bạn có thể cần phải sử dụng unoptimized={true} hoặc có thể gặp phải yêu cầu thêm thuộc tính này khi tải ảnh.

Hai thẻ cuối này thì ít dùng hơn nhưng mình cũng thấy nó đặc biệt nên thêm vào


4. <Script>

Thẻ <Script> không chỉ giúp bạn chèn các script vào trang mà còn hỗ trợ tối ưu hóa tải và thực thi các script bên ngoài. Điều này giúp bạn kiểm soát tốt hơn việc tải tài nguyên bên ngoài, giảm thiểu thời gian chờ đợi và cải thiện hiệu suất tổng thể.


5. <ErrorBoundary>

Bạn có thể sử dụng một Error Boundary trong React để bắt lỗi JavaScript trong ứng dụng của bạn và hiển thị một giao diện thay thế hoặc thông báo lỗi cho người dùng khi xảy ra sự cố.

import React, { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, errorInfo: null };
  }

  static getDerivedStateFromError(error) {
    // Cập nhật state để render giao diện lỗi thay vì giao diện bình thường
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // Ghi lại lỗi vào một dịch vụ log lỗi (như Sentry, LogRocket, v.v.)
    console.log('Error caught:', error, info);
    this.setState({ errorInfo: info });
  }

  render() {
    if (this.state.hasError) {
      // Bạn có thể render bất kỳ giao diện thay thế nào khi gặp lỗi
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.errorInfo && this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }

    return this.props.children; 
  }
}

export default ErrorBoundary;

Enter fullscreen mode Exit fullscreen mode

Như vậy tôi vừa giới thiệu cho bạn những thẻ đặc biệt trong NextJS mà tôi đã tìm hiểu. Hi vọng bài viết sẽ bổ sung cho bạn thêm kiến thức về lập trình. Mong rằng những phần tiếp theo sẽ vẫn được các bạn ủng hộ, chúng ta sẽ cùng nhau khám phá thêm những điều mới mẻ hơn nữa. Hẹn gặp lại

Top comments (0)