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

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ả

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

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)
  1. 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)
  1. 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.
notion image
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/
notion image
Ý 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
 
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ả
 

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
next-lazy-hydrate
thanhlmmUpdated Mar 29, 2023
 
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!
 

Loading Comments...

Follow me @thanhledev

Xin lỗi các bạn vì thời gian qua mình không dành thời gian viết nhiều. Dạo này mình khá bận cho dự án https://getnimbus.io. Check it out 🥳