Optimize TTI và FID cho Nextjs một cách super đơn giản

    Bài gốc: https://thanhle.blog/blog/optimize-tti-và-fid-cho-nextjs-mot-cach-super-don-gian

Tại sao nên đọc bài này?

  • Như tít: “Optimize TTI và FID cho Nextjs một cách super đơn giản”
  • Islands Architectures cho Nextjs

Kết quả

Before

    Untitled.png

    https://next-lazy-hydrate-origin.vercel.app/

    Live check PageSpeed

After

    Untitled 1.png

    https://next-lazy-hydrate-optimized.vercel.app/

    Live check PageSpeed

Hydration là quá trình lãng phí tài nguyên

    Server side rendering với Hydration lãng phí tài nguyên như thế nào?

    Như bài viết trên, Hydration là quá trình khá LÃNG PHÍ TÀI NGUYÊN vì nó cần load code của component và render tới hai lần.

    Tương tượng chúng ta có một trang landing page khá là dài viết bằng Nextjs, hầu hết các component đều là tĩnh (Nghĩa là chỉ render ra HTML mà không có quá nhiều Interactive). Khi chúng ta “đập” vào phím Enter trên thanh URL thì:

  1. Đống HTML của landing page được gửi xuống Browser (Là kết quả của quá trình SSR)
  2. JavaScript được download xuống Browser, phân tích, rồi thực thi (Đa số tụi nó sẽ bao gồm text và khá giống như HTML ở bước 1)
  3. Sau khi JavaScript được download, và chạy xong, nó sẽ gắn đống event cần handle vào cây DOM hiện tại. Bây giờ thì cái web của mình với có thể gọi là… load đầy đủ

    Trong các bước trên, bước 2 và 3 khiến cho hai chỉ số TTI (Time To Interactive) và FID (First Input Delay) rất cao

Progressive Hydration

    Xin không dịch từ trên quá tiếng Việt vì dịch ra sẽ rất chuối 🍌

    Ok bây giờ thử optimize cái trang landing page dài ngoằng của chúng ta nhé. Bởi vì cái trang landing page đó hầu hết là Static Component nên hầu hết thời gian cho quá trình Hydrate Component là khá vô ích (bởi vì nó chỉ cần HTML, CSS là chạy được, méo cần JS nào ở đây cả). Vậy thì chỉ cần tắt hydrate cho những Component kiểu như vậy, hoặc là chỉ Hydrate khi nào có component nhảy vào màn hình (Viewport) của user.

    Untitled 2.png

    Cái này khá dễ để làm, cùng package react-hydration-on-demand là được

import withHydrationOnDemand from "react-hydration-on-demand";
import Card from "../Card";

// Hydrate when the component enters the viewport
const CardWithHydrationOnDemand = withHydrationOnDemand({ on: ["visible"] })(
    Card
);

export default class App extends React.Component {
    render() {
        return (
            <CardWithHydrationOnDemand
                title="my card"
                wrapperProps={{
                    className: "customClassName",
                    style: { display: "contents" },
                }}
            />
        );
    }
}

    Vậy là bây giờ, cái gạch đầu dòng thứ 3 đã được optimize - giảm thời gian JS phải chạy để hydrate cái landing page yếu dấu của chúng ta 🥰

Lazy load component rồi hydrate khi cần thiết

    Ở bước trên, ta có thể optimize executed time sử dụng react-hydration-on-demand nhưng nếu nhìn vào đống JS được gửi xuống bạn sẽ nhận ra

    JS cho các component vẫn phải được download và parsed, nó chỉ đơn giản là không hoặc chưa execute thôi. 💡 Có cách nào mà mình vẫn render được full HTML nhưng chỉ load JS và Hydrate Component đó khi cần thiết không?

    Trong quá trình tìm kiếm câu trả lời, thì đây là giải pháp mình thấy ưng ý nhất: https://www.patterns.dev/posts/islands-architecture/

    Untitled 3.png

    Ý tưởng thì khá đơn giản:

  • Render full trang ở quá trình SSR
  • Load tối thiểu JS, chỉ để listen trên cây DOM xem có event nào không
  • Nếu mà có event, thì load JS tương ứng rồi chạy nó thôi

    Giải pháp này thực sự optimize performance rất rất nhiều, bằng cách hy sinh một chút thời gian mỗi khi user có interactive gì đó. Với mình cái trade-off này là cực kì “lời” 🌟

    Nếu thử disable JS thì  TTI giảm hơn 7 lần. Sẽ làm sao nếu chúng ta chỉ cần giảm một nửa trong số đó?

    Nếu thử disable JS thì TTI giảm hơn 7 lần. Sẽ làm sao nếu chúng ta chỉ cần giảm một nửa trong số đó?

    Đỉnh của chóp! Giải pháp khá dễ hiểu tuy nhiên ở thời điểm hiện tại thì khá khó để làm. Tại sao?

  • Ở thời điểm hiện tại, React chỉ support hydrate full một app chứ không phải riêng từng component (Nếu v18 được hoàn thiện thì sẽ giải quyết được cái này). Thực ra cái package react-hydration-on-demand nó có một số trick để bỏ qua quá trình Hydrate
  • Với Nextjs, nếu component được define là dynamic mà nếu nó được render ở quá trình SSR, thì đống JS của nó cũng được gửi xuống Browser ngay khi page load luôn, chả có gì gọi là lazy ở đây cả

    Đọc thêm

    Why Progressive Hydration is Harder than You Think

    Vậy là mình quyết định viết một cái package có thể:

  • Bỏ qua quá trình Hydrate. Hầu hết là dựa theo thằng react-hydration-on-demand thôi 😃
  • Loại bỏ JS khi load page và khiến mình có thể tùy chỉnh khi nào thì load JS tương ứng

    Làm sao mình làm được á hả? Xem thử ở đây nè (khá ngắn)

    Còn đây là kết quả

    https://user-images.githubusercontent.com/9281080/172079813-a49db8c0-c64d-4589-941d-bf027b22433a.mov

Cách dùng

    Install

npm install next-lazy-hydrate
yarn add next-lazy-hydrate

    Usage

import lazyHydrate from 'next-lazy-hydrate';

// Static component
const WhyUs = lazyHydrate(() => import('../components/whyus'));

// Lazy hydrate when users hover the component
const Footer = lazyHydrate(
  () => import('../components/footer', { on: ['hover'] })
);

const HomePage = () => {
  return (
    <div>
      <AboveTheFoldComponent />
      {/* ----The Fold---- */}
      <WhyUs />
      <Footer />
    </div>
  );
};

    Document

    https://github.com/thanhlmm/next-lazy-hydrate

    API khi sử dụng package khá đơn giản, hy vọng nó giúp các đồng-coder khác Optimize TTI và FID cho Nextjs một cách super đơn giản

    Nhớ Star ⭐ cho tui nha!

    Bài gốc: https://thanhle.blog/blog/optimize-tti-và-fid-cho-nextjs-mot-cach-super-don-gian

Nguồn: Viblo

Bình luận
Vui lòng đăng nhập để bình luận
Một số bài viết liên quan