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
https://next-lazy-hydrate-origin.vercel.app/
After
https://next-lazy-hydrate-optimized.vercel.app/
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ì:
- Đống HTML của landing page được gửi xuống Browser (Là kết quả của quá trình SSR)
- 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)
- 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.
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/
Ý 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ố đó?
Đỉ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