Một trong những quyết định cốt lõi mà nhà phát triển web phải đưa ra là nơi triển khai logic và kết xuất trong ứng dụng của họ. Điều này có thể gây khó khăn vì có rất nhiều cách để tạo một trang web.
Chúng tôi hiểu rõ không gian này nhờ công việc của mình trong Chrome khi trao đổi với các trang web lớn trong vài năm qua. Nói chung, chúng tôi khuyến khích nhà phát triển cân nhắc sử dụng phương pháp kết xuất phía máy chủ hoặc kết xuất tĩnh thay vì phương pháp gắn kết lại hoàn toàn.
Để hiểu rõ hơn về các cấu trúc mà chúng ta sẽ chọn khi đưa ra quyết định này, chúng ta cần có thuật ngữ nhất quán và một khung chung cho từng phương pháp. Sau đó, bạn có thể đánh giá tốt hơn những điểm đánh đổi của từng phương pháp kết xuất theo góc độ hiệu suất trang.
Thuật ngữ
Trước tiên, chúng ta sẽ xác định một số thuật ngữ sẽ sử dụng.
Kết xuất
- Kết xuất phía máy chủ (SSR)
- Kết xuất một ứng dụng trên máy chủ để gửi HTML thay vì JavaScript đến máy khách.
- Kết xuất phía máy khách (CSR)
- Kết xuất một ứng dụng trong trình duyệt, sử dụng JavaScript để sửa đổi DOM.
- Kết xuất trước
- Chạy một ứng dụng phía máy khách tại thời điểm xây dựng để ghi lại trạng thái ban đầu của ứng dụng dưới dạng HTML tĩnh.
- Hút nước
- Chạy tập lệnh phía máy khách để thêm trạng thái ứng dụng và khả năng tương tác vào HTML do máy chủ kết xuất. Quá trình kết hợp giả định rằng DOM không thay đổi.
- Bù nước
- Mặc dù thường được dùng để chỉ cùng một việc như truyền dữ liệu, nhưng truyền dữ liệu lại ngụ ý việc cập nhật DOM thường xuyên bằng trạng thái mới nhất, kể cả sau lần truyền dữ liệu ban đầu.
Hiệu suất
- Thời gian cho byte đầu tiên (TTFB)
- Thời gian từ khi nhấp vào một đường liên kết cho đến khi byte nội dung đầu tiên tải trên trang mới.
- Hiển thị nội dung đầu tiên (FCP)
- Thời gian nội dung được yêu cầu (nội dung bài viết, v.v.) xuất hiện.
- Lượt tương tác đến nội dung hiển thị tiếp theo (INP)
- Một chỉ số đại diện đánh giá xem một trang có phản hồi nhanh chóng và nhất quán đối với thông tin đầu vào của người dùng hay không.
- Tổng thời gian chặn (TBT)
- Một chỉ số proxy cho INP giúp tính toán thời gian luồng chính bị chặn trong quá trình tải trang.
Hiển thị phía máy chủ
Kết xuất phía máy chủ tạo ra toàn bộ HTML cho một trang trên máy chủ để phản hồi hoạt động điều hướng. Điều này giúp tránh các chuyến đi khứ hồi bổ sung để tìm nạp dữ liệu và tạo mẫu trên máy khách, vì trình kết xuất sẽ xử lý các chuyến đi này trước khi trình duyệt nhận được phản hồi.
Tính năng hiển thị phía máy chủ thường tạo ra FCP nhanh. Việc chạy logic trang và kết xuất trên máy chủ giúp bạn tránh gửi nhiều JavaScript đến máy khách. Điều này giúp giảm TTBT của một trang, từ đó cũng có thể dẫn đến INP thấp hơn, vì luồng chính không bị chặn thường xuyên trong quá trình tải trang. Khi luồng chính ít bị chặn hơn, các lượt tương tác của người dùng sẽ có nhiều cơ hội chạy sớm hơn.
Điều này rất hợp lý vì với tính năng kết xuất phía máy chủ, bạn thực sự chỉ gửi văn bản và đường liên kết đến trình duyệt của người dùng. Cách tiếp cận này có thể hoạt động hiệu quả trong nhiều điều kiện về thiết bị và mạng, đồng thời mở ra các hoạt động tối ưu hoá trình duyệt thú vị, chẳng hạn như phân tích cú pháp tài liệu phát trực tuyến.
 
  Với tính năng kết xuất phía máy chủ, người dùng ít có khả năng phải chờ JavaScript liên kết với CPU chạy trước khi có thể sử dụng trang web của bạn. Ngay cả khi bạn không thể tránh JavaScript của bên thứ ba, việc sử dụng tính năng hiển thị phía máy chủ để giảm chi phí JavaScript của bên thứ nhất có thể giúp bạn có thêm ngân sách cho phần còn lại. Tuy nhiên, có một điểm đánh đổi tiềm ẩn với phương pháp này: việc tạo trang trên máy chủ mất thời gian, điều này có thể làm tăng TTFB của trang.
Việc kết xuất phía máy chủ có đủ cho ứng dụng của bạn hay không phần lớn phụ thuộc vào loại trải nghiệm mà bạn đang xây dựng. Có một cuộc tranh luận kéo dài về các ứng dụng chính xác của tính năng kết xuất phía máy chủ so với tính năng kết xuất phía ứng dụng, nhưng bạn luôn có thể chọn sử dụng tính năng kết xuất phía máy chủ cho một số trang và không sử dụng cho các trang khác. Một số trang web đã áp dụng thành công kỹ thuật kết xuất kết hợp. Ví dụ: Netflix kết xuất phía máy chủ các trang đích tương đối tĩnh, trong khi prefetching JavaScript cho các trang có nhiều lượt tương tác, giúp những trang được kết xuất phía máy khách có dung lượng lớn hơn này có nhiều khả năng tải nhanh hơn.
Với nhiều khung, thư viện và cấu trúc hiện đại, bạn có thể kết xuất cùng một ứng dụng trên cả máy khách và máy chủ. Bạn có thể sử dụng các kỹ thuật này để kết xuất phía máy chủ. Tuy nhiên, các cấu trúc mà quá trình kết xuất diễn ra cả trên máy chủ và trên ứng dụng là một lớp giải pháp riêng với các đặc điểm hiệu suất và sự đánh đổi rất khác nhau. Người dùng React có thể sử dụng API DOM phía máy chủ hoặc các giải pháp được xây dựng trên API này như Next.js để kết xuất phía máy chủ. Người dùng Vue có thể sử dụng hướng dẫn kết xuất phía máy chủ của Vue hoặc Nuxt. Angular có Universal.
Tuy nhiên, hầu hết các giải pháp phổ biến đều sử dụng một số hình thức bù nước, vì vậy, hãy lưu ý đến các phương pháp mà công cụ của bạn sử dụng.
Kết xuất tĩnh
Kết xuất tĩnh diễn ra tại thời điểm tạo. Phương pháp này mang đến FCP nhanh, cũng như TBT và INP thấp hơn, miễn là bạn giới hạn lượng JavaScript phía máy khách trên các trang của mình. Không giống như tính năng kết xuất phía máy chủ, tính năng này cũng đạt được TTFB nhanh một cách nhất quán, vì HTML cho một trang không cần được tạo động trên máy chủ. Nhìn chung, kết xuất tĩnh có nghĩa là tạo một tệp HTML riêng cho từng URL trước thời gian. Với các phản hồi HTML được tạo trước, bạn có thể triển khai các thành phần hiển thị tĩnh cho nhiều CDN để tận dụng tính năng lưu vào bộ nhớ đệm ở rìa.
 
  Các giải pháp kết xuất tĩnh có nhiều hình dạng và kích thước. Các công cụ như Gatsby được thiết kế để giúp nhà phát triển cảm thấy như ứng dụng của họ đang được kết xuất một cách linh động, chứ không phải được tạo dưới dạng một bước xây dựng. Các công cụ tạo trang web tĩnh như 11ty, Jekyll và Metalsmith tận dụng bản chất tĩnh của chúng, mang đến một phương pháp dựa trên mẫu hơn.
Một trong những nhược điểm của quá trình kết xuất tĩnh là phải tạo các tệp HTML riêng lẻ cho mọi URL có thể. Điều này có thể gây khó khăn hoặc thậm chí là không thể khi bạn cần dự đoán trước những URL đó và đối với các trang web có số lượng lớn trang riêng biệt.
Người dùng React có thể đã quen thuộc với Gatsby, Next.js static export hoặc Navi. Tất cả những công cụ này đều giúp bạn dễ dàng tạo các trang từ các thành phần. Tuy nhiên, phương thức kết xuất tĩnh và kết xuất trước hoạt động khác nhau: các trang được kết xuất tĩnh có tính tương tác mà không cần thực thi nhiều JavaScript phía ứng dụng, trong khi phương thức kết xuất trước cải thiện FCP của một Ứng dụng một trang phải được khởi động trên ứng dụng để các trang thực sự có tính tương tác.
Nếu bạn không chắc chắn liệu một giải pháp nhất định có phải là kết xuất tĩnh hay kết xuất trước, hãy thử tắt JavaScript và tải trang mà bạn muốn kiểm tra. Đối với các trang được kết xuất tĩnh, hầu hết các tính năng tương tác vẫn tồn tại mà không cần JavaScript. Các trang được kết xuất trước vẫn có thể có một số tính năng cơ bản như đường liên kết khi JavaScript bị vô hiệu hoá, nhưng hầu hết trang đều không hoạt động.
Một thử nghiệm hữu ích khác là sử dụng tính năng điều tiết mạng trong Công cụ của Chrome cho nhà phát triển và xem có bao nhiêu JavaScript được tải xuống trước khi một trang trở nên có thể tương tác. Quá trình kết xuất trước thường cần nhiều JavaScript hơn để trở nên tương tác và JavaScript đó có xu hướng phức tạp hơn phương pháp nâng cao từng bước được dùng trong quá trình kết xuất tĩnh.
Kết xuất phía máy chủ so với kết xuất tĩnh
Kết xuất phía máy chủ không phải là giải pháp tốt nhất cho mọi thứ, vì bản chất động của nó có thể gây ra chi phí đáng kể cho hoạt động điện toán. Nhiều giải pháp kết xuất phía máy chủ không xoá sớm, trì hoãn TTFB hoặc tăng gấp đôi dữ liệu được gửi (ví dụ: các trạng thái nội tuyến do JavaScript sử dụng trên máy khách). Trong React, renderToString() có thể chậm vì đây là một quy trình đồng bộ và đơn luồng.
Các API DOM máy chủ React mới hơn hỗ trợ truyền phát trực tuyến, có thể nhận được phần đầu của phản hồi HTML đến trình duyệt sớm hơn trong khi phần còn lại vẫn đang được tạo trên máy chủ.
Việc hiển thị phía máy chủ "đúng cách" có thể liên quan đến việc tìm hoặc xây dựng một giải pháp cho hoạt động lưu vào bộ nhớ đệm của thành phần, quản lý mức tiêu thụ bộ nhớ, sử dụng kỹ thuật ghi nhớ và các vấn đề khác. Bạn thường xử lý hoặc tạo lại cùng một ứng dụng hai lần, một lần trên máy khách và một lần trên máy chủ. Việc kết xuất phía máy chủ hiển thị nội dung sớm hơn không nhất thiết giúp bạn giảm bớt công việc. Nếu bạn có nhiều việc phải làm trên ứng dụng sau khi nhận được phản hồi HTML do máy chủ tạo trên ứng dụng, thì điều này vẫn có thể dẫn đến TBT và INP cao hơn cho trang web của bạn.
Kết xuất phía máy chủ tạo ra HTML theo yêu cầu cho từng URL, nhưng có thể chậm hơn so với việc chỉ phân phát nội dung được kết xuất tĩnh. Nếu bạn có thể bỏ thêm công sức, thì việc kết xuất phía máy chủ cộng với lưu vào bộ nhớ đệm HTML có thể giảm đáng kể thời gian kết xuất phía máy chủ. Ưu điểm của việc kết xuất phía máy chủ là khả năng kéo thêm dữ liệu "trực tiếp" và phản hồi một tập hợp yêu cầu đầy đủ hơn so với khả năng kết xuất tĩnh. Các trang cần được cá nhân hoá là một ví dụ cụ thể về loại yêu cầu không hoạt động hiệu quả với tính năng kết xuất tĩnh.
Kết xuất phía máy chủ cũng có thể đưa ra những quyết định thú vị khi xây dựng một PWA. Bạn nên sử dụng tính năng lưu vào bộ nhớ đệm service worker toàn trang hay kết xuất phía máy chủ từng phần nội dung?
Kết xuất phía máy khách
Kết xuất phía máy khách có nghĩa là kết xuất các trang ngay trong trình duyệt bằng JavaScript. Tất cả logic, hoạt động tìm nạp dữ liệu, tạo mẫu và định tuyến đều được xử lý trên ứng dụng chứ không phải trên máy chủ. Kết quả hiệu quả là có nhiều dữ liệu được truyền đến thiết bị của người dùng từ máy chủ và điều này đi kèm với một số điểm đánh đổi.
Việc kết xuất phía máy khách có thể khó thực hiện và duy trì tốc độ nhanh cho thiết bị di động.
Bằng cách nỗ lực một chút để duy trì ngân sách JavaScript eo hẹp và mang lại giá trị trong ít chuyến khứ hồi nhất có thể, bạn có thể khiến quá trình hiển thị phía máy khách gần như sao chép hiệu suất của quá trình hiển thị thuần tuý phía máy chủ. Bạn có thể giúp trình phân tích cú pháp hoạt động nhanh hơn bằng cách phân phối các tập lệnh và dữ liệu quan trọng bằng <link rel=preload>. Bạn cũng nên cân nhắc sử dụng các mẫu như PRPL để đảm bảo các thao tác điều hướng ban đầu và tiếp theo diễn ra ngay lập tức.
 
  Nhược điểm chính của việc kết xuất phía máy khách là lượng JavaScript cần thiết có xu hướng tăng lên khi ứng dụng phát triển, điều này có thể ảnh hưởng đến INP của trang. Điều này trở nên đặc biệt khó khăn khi bạn thêm các thư viện JavaScript, polyfill và mã của bên thứ ba mới. Những thành phần này cạnh tranh về sức mạnh xử lý và thường phải được xử lý trước khi nội dung của trang có thể hiển thị.
Những trải nghiệm sử dụng tính năng kết xuất phía máy khách và dựa vào các gói JavaScript lớn nên cân nhắc việc phân chia mã một cách triệt để để giảm TBT và INP trong quá trình tải trang, cũng như tải JavaScript một cách trì hoãn để chỉ phân phát những gì người dùng cần, khi họ cần. Đối với những trải nghiệm có ít hoặc không có tính tương tác, việc kết xuất phía máy chủ có thể là một giải pháp có khả năng mở rộng hơn cho những vấn đề này.
Đối với những người tạo ứng dụng một trang, việc xác định các phần cốt lõi của giao diện người dùng được hầu hết các trang chia sẻ cho phép bạn áp dụng kỹ thuật lưu vào bộ nhớ đệm của giao diện ứng dụng. Khi kết hợp với trình chạy dịch vụ, điều này có thể cải thiện đáng kể hiệu suất cảm nhận trong các lượt truy cập lặp lại, vì trang có thể tải HTML và các phần phụ thuộc của giao diện ứng dụng từ CacheStorage rất nhanh.
Quá trình bù nước kết hợp chế độ kết xuất phía máy chủ và phía máy khách
Hydration (Kết hợp dữ liệu) là một phương pháp giúp giảm thiểu sự đánh đổi giữa việc kết xuất phía máy khách và phía máy chủ bằng cách thực hiện cả hai. Các yêu cầu điều hướng, chẳng hạn như tải hoặc tải lại toàn bộ trang, được xử lý bởi một máy chủ kết xuất ứng dụng thành HTML. Sau đó, JavaScript và dữ liệu dùng để kết xuất sẽ được nhúng vào tài liệu kết quả. Khi được thực hiện cẩn thận, điều này sẽ đạt được FCP nhanh như kết xuất phía máy chủ, sau đó "chọn" bằng cách kết xuất lại trên máy khách.
Đây là một giải pháp hiệu quả, nhưng có thể có những hạn chế đáng kể về hiệu suất.
Nhược điểm chính của việc kết xuất phía máy chủ bằng tính năng bù nước là có thể ảnh hưởng tiêu cực đáng kể đến TBT và INP, ngay cả khi cải thiện FCP. Các trang được kết xuất phía máy chủ có thể xuất hiện ở trạng thái đã tải và có thể tương tác, nhưng thực tế là không thể phản hồi dữ liệu đầu vào cho đến khi các tập lệnh phía máy khách cho các thành phần được thực thi và trình xử lý sự kiện đã được đính kèm. Trên thiết bị di động, việc này có thể mất vài phút, gây nhầm lẫn và khó chịu cho người dùng.
Vấn đề về việc khôi phục trạng thái: một ứng dụng có giá của hai ứng dụng
Để JavaScript phía máy khách tiếp quản chính xác nơi máy chủ dừng lại mà không cần yêu cầu lại tất cả dữ liệu mà máy chủ đã kết xuất HTML, hầu hết các giải pháp kết xuất phía máy chủ đều chuyển đổi tuần tự phản hồi từ các phần phụ thuộc dữ liệu của giao diện người dùng dưới dạng thẻ tập lệnh trong tài liệu. Vì thao tác này sao chép nhiều HTML, nên quá trình khôi phục có thể gây ra nhiều vấn đề hơn là chỉ tương tác bị trì hoãn.
 
  Máy chủ đang trả về nội dung mô tả về giao diện người dùng của ứng dụng để phản hồi một yêu cầu điều hướng, nhưng cũng đang trả về dữ liệu nguồn dùng để tạo giao diện người dùng đó và một bản sao hoàn chỉnh của quá trình triển khai giao diện người dùng, sau đó khởi động trên máy khách. Giao diện người dùng sẽ không tương tác cho đến khi bundle.js hoàn tất quá trình tải và thực thi.
Các chỉ số hiệu suất được thu thập từ các trang web thực tế bằng cách sử dụng tính năng kết xuất phía máy chủ và kết xuất lại cho thấy rằng đây hiếm khi là lựa chọn tốt nhất. Lý do quan trọng nhất là ảnh hưởng của chỉ số này đến trải nghiệm người dùng, khi một trang trông có vẻ đã sẵn sàng nhưng không có tính năng tương tác nào hoạt động.
 
  Có hy vọng cho việc kết xuất phía máy chủ bằng cơ chế gắn kết lại. Trong thời gian ngắn, chỉ sử dụng tính năng kết xuất phía máy chủ cho nội dung có khả năng lưu vào bộ nhớ đệm cao có thể giảm TTFB, tạo ra kết quả tương tự như kết xuất trước. Việc tăng dần, tăng từng bước hoặc tăng một phần có thể là chìa khoá để giúp kỹ thuật này khả thi hơn trong tương lai.
Truyền trực tuyến tính năng hiển thị phía máy chủ và khôi phục dần dần
Kết xuất phía máy chủ đã có một số điểm phát triển trong vài năm qua.
Kết xuất phía máy chủ theo luồng cho phép bạn gửi HTML theo các đoạn mà trình duyệt có thể kết xuất dần khi nhận được. Điều này có thể giúp người dùng nhận được mã đánh dấu nhanh hơn, từ đó tăng tốc FCP. Trong React, các luồng không đồng bộ trong renderToPipeableStream(), so với renderToString() đồng bộ, có nghĩa là áp suất ngược được xử lý tốt.
Bạn cũng nên cân nhắc quá trình kết hợp lại tăng dần (React đã triển khai quá trình này). Với phương pháp này, từng phần của ứng dụng được kết xuất phía máy chủ sẽ được "khởi động" theo thời gian, thay vì phương pháp phổ biến hiện tại là khởi tạo toàn bộ ứng dụng cùng một lúc. Điều này có thể giúp giảm lượng JavaScript cần thiết để làm cho các trang có thể tương tác, vì nó cho phép bạn hoãn nâng cấp phía máy khách của các phần có mức độ ưu tiên thấp của trang để ngăn chặn việc chặn luồng chính, cho phép các hoạt động tương tác của người dùng diễn ra sớm hơn sau khi người dùng bắt đầu.
Quá trình kết hợp lại từng phần cũng có thể giúp bạn tránh được một trong những sai lầm phổ biến nhất khi kết hợp lại quá trình kết xuất phía máy chủ: cây DOM được kết xuất phía máy chủ bị huỷ rồi được tạo lại ngay lập tức, thường là do quá trình kết xuất đồng bộ ban đầu phía máy khách yêu cầu dữ liệu chưa sẵn sàng, thường là một Promise chưa được phân giải.
Bù nước một phần
Việc triển khai quá trình kết hợp lại một phần đã được chứng minh là khó thực hiện. Phương pháp này là một phần mở rộng của quá trình kết hợp lại từng phần, phân tích từng phần của trang (thành phần, khung hiển thị hoặc cây) và xác định những phần có ít hoặc không có tính tương tác. Đối với mỗi phần tĩnh này, mã JavaScript tương ứng sẽ được chuyển đổi thành các tham chiếu không hoạt động và các tính năng trang trí, giảm mức sử dụng tài nguyên phía máy khách xuống gần bằng 0.
Phương pháp kết hợp này cũng có những vấn đề và hạn chế riêng. Điều này đặt ra một số thách thức thú vị cho việc lưu vào bộ nhớ đệm và chế độ điều hướng phía máy khách có nghĩa là chúng ta không thể giả định rằng HTML do máy chủ kết xuất cho các phần không hoạt động của ứng dụng có sẵn mà không cần tải toàn bộ trang.
Kết xuất Trisomorphic
Nếu service worker là một lựa chọn cho bạn, hãy cân nhắc việc kết xuất trisomorphic. Kỹ thuật này cho phép bạn sử dụng tính năng kết xuất phía máy chủ truyền trực tuyến cho các thao tác điều hướng ban đầu hoặc không phải JavaScript, sau đó để trình chạy dịch vụ đảm nhận việc kết xuất HTML cho các thao tác điều hướng sau khi trình chạy dịch vụ được cài đặt. Điều này có thể giúp các thành phần và mẫu được lưu vào bộ nhớ đệm luôn mới nhất, đồng thời cho phép điều hướng theo kiểu SPA để hiển thị các khung hiển thị mới trong cùng một phiên. Cách tiếp cận này hoạt động hiệu quả nhất khi bạn có thể chia sẻ cùng một mã định tuyến và tạo mẫu giữa máy chủ, trang ứng dụng và worker dịch vụ.
 
  Những điều cần cân nhắc về SEO
Khi chọn chiến lược kết xuất web, các nhóm thường cân nhắc tác động của SEO. Kết xuất phía máy chủ là lựa chọn phổ biến để mang đến trải nghiệm "hoàn chỉnh" mà trình thu thập thông tin có thể diễn giải. Trình thu thập dữ liệu có thể hiểu JavaScript, nhưng thường có các hạn chế đối với cách chúng hiển thị. Quá trình kết xuất phía máy khách có thể hoạt động, nhưng thường cần thêm bước kiểm thử và chi phí. Gần đây hơn, kết xuất động cũng trở thành một lựa chọn đáng cân nhắc nếu cấu trúc của bạn phụ thuộc nhiều vào JavaScript phía máy khách.
Khi không chắc chắn, công cụ kiểm tra tính thân thiện với thiết bị di động là một cách tuyệt vời để kiểm tra xem phương pháp bạn chọn có mang lại kết quả như mong đợi hay không. Công cụ này cho thấy bản xem trước trực quan về cách mọi trang xuất hiện đối với trình thu thập dữ liệu của Google, nội dung HTML được chuyển đổi tuần tự mà trình thu thập dữ liệu tìm thấy sau khi JavaScript được thực thi và mọi lỗi gặp phải trong quá trình kết xuất.
 
  Kết luận
Khi quyết định cách kết xuất, hãy đo lường và tìm hiểu những điểm nghẽn của bạn. Hãy cân nhắc xem liệu chế độ kết xuất tĩnh hay kết xuất phía máy chủ có thể giúp bạn đạt được hầu hết mục tiêu hay không. Bạn có thể chỉ cần gửi HTML với JavaScript tối thiểu để có được trải nghiệm tương tác. Sau đây là một biểu đồ thông tin hữu ích cho thấy phổ máy chủ-máy khách:
 
  Ghi công {:#credits}
Cảm ơn mọi người đã đánh giá và truyền cảm hứng:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson và Sebastian Markbåge.
 
 
        
        