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ể khó khăn vì có rất nhiều cách để tạo trang web.
Chúng tôi hiểu rõ về không gian này nhờ công việc của mình trong Chrome khi trò chuyện với các trang web lớn trong vài năm qua. Nói chung, nhà phát triển nên cân nhắc việc kết xuất phía máy chủ hoặc kết xuất tĩnh thay vì phương pháp kết xuất lại đầy đủ.
Để 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 hiểu rõ từng phương pháp và sử dụng một cách nhất quán các thuật ngữ khi nói về các phương pháp đó. Sự khác biệt giữa các phương pháp kết xuất giúp minh hoạ những đánh đổi của việc kết xuất trên web từ 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 ứ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 ứng dụng trong trình duyệt, sử dụng JavaScript để sửa đổi DOM.
- Bù nước
- "Khởi động" các thành phần hiển thị JavaScript trên máy khách để các thành phần này sử dụng lại cây DOM và dữ liệu của HTML do máy chủ hiển thị.
- Kết xuất trước
- Chạy ứng dụng phía máy khách tại thời điểm tạo bản dựng để ghi lại trạng thái ban đầu của ứng dụng dưới dạng HTML tĩnh.
Hiệu suất
- Thời gian tải byte đầu tiên (TTFB)
- Thời gian từ khi nhấp vào một đường liên kết đến khi tải byte nội dung đầu tiên trên trang mới.
- Hiển thị nội dung đầu tiên (FCP)
- Thời điểm nội dung được yêu cầu (nội dung bài viết, v.v.) hiển thị.
- Lượt tương tác đến nội dung hiển thị tiếp theo (INP)
- Một chỉ số đại diện giúp đánh giá xem một trang có phản hồi nhanh và nhất quán với hoạt động đầu vào của người dùng hay không.
- Tổng thời gian chặn (TBT)
- Chỉ số proxy cho INP tính toán thời lượng luồng chính bị chặn trong quá trình tải trang.
Hiển thị phía máy chủ
Tính năng kết xuất phía máy chủ tạo HTML đầy đủ cho một trang trên máy chủ để phản hồi thao tác điều hướng. Điều này giúp tránh các lượt truy cập lại để tìm nạp dữ liệu và tạo mẫu trên ứng dụng, vì trình kết xuất sẽ xử lý các lượt truy cập đó 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 ứng dụng. Điều này giúp giảm TBT của trang, đồng thời có thể làm giảm INP 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 bị chặn ít thường xuyên hơn, các hoạt động tương tác của người dùng có nhiều cơ hội chạy sớm hơn. Điều này là 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. Phương pháp này có thể hoạt động tốt trong nhiều điều kiện thiết bị và mạng, đồng thời mở ra các tính năng tối ưu hoá trình duyệt thú vị như phân tích cú pháp tài liệu 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 được 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ủ sẽ 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ụ thuộc phần lớn vào loại trải nghiệm mà bạn đang xây dựng. Đã có một cuộc tranh luận lâu dài về việc áp dụng chính xác tính năng kết xuất phía máy chủ so với kết xuất phía máy khách, 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 các kỹ thuật kết xuất kết hợp. Ví dụ: máy chủ Netflix hiển thị các trang đích tương đối tĩnh, đồng thời prefetching JS cho các trang có nhiều lượt tương tác, giúp các trang do ứng dụng hiển thị nặng hơn này có nhiều cơ hội tải nhanh hơn.
Nhiều khung, thư viện và cấu trúc hiện đại cho phép bạn hiển thị 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 trong đó quá trình kết xuất diễn ra cả trên máy chủ và trên máy khách 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 máy chủ hoặc các giải pháp được xây dựng dựa trên API đó như Next.js để hiển thị 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 hydrat hoá, 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
Hoạt động kết xuất tĩnh diễn ra tại thời điểm tạo bản dựng. Phương pháp này mang lại FCP nhanh và cũng có TBT và INP thấp hơn, miễn là bạn giới hạn lượng JS phía máy khách trên các trang của mình. Không giống như chế độ kết xuất phía máy chủ, chế độ này cũng đạt được TTFB nhanh một cách nhất quán vì HTML cho một trang không nhất thiết phải được tạo động trên máy chủ. Nhìn chung, tính năng kết xuất tĩnh có nghĩa là tạo một tệp HTML riêng cho mỗi URL trước. Với các phản hồi HTML được tạo trước, bạn có thể triển khai các bản kết xuất tĩnh cho nhiều CDN để tận dụng tính năng lưu vào bộ nhớ đệm cạnh.
Giải pháp kết xuất tĩnh có đủ 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 ứng dụng của họ đang được hiển thị 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, cung cấp phương pháp dựa trên mẫu hơn.
Một trong những nhược điểm của tính năng 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ể. Việc này có thể khó khăn hoặc thậm chí không khả thi khi bạn không thể dự đoán trước những URL đó hoặc đối với những 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, tính năng xuất tĩnh Next.js hoặc Navi. Tất cả đều giúp bạn tạo trang từ các thành phần một cách thuận tiện. Tuy nhiên, 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ó thể tương tác mà không cần thực thi nhiều JavaScript phía máy khách, trong khi kết xuất trước cải thiện FCP của Ứng dụng một trang phải được khởi động trên máy khách để các trang thực sự tương tác.
Nếu bạn không chắc một giải pháp nhất định là kết xuất tĩnh hay kết xuất trước, hãy thử tắt JavaScript và tải trang bạn muốn kiểm thử. Đố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 có thể vẫn có một số tính năng cơ bản như các đường liên kết bị tắt JavaScript, 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 hạn chế băng thông mạng trong Công cụ của Chrome cho nhà phát triển và xem lượng JavaScript tải xuống trước khi một trang trở nên tương tác. Tính năng kết xuất trước thường cần nhiều JavaScript hơn để có thể tương tác và JavaScript đó có xu hướng phức tạp hơn phương pháp nâng cao dần dùng trong tính năng 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 trường hợp, vì tính chất động của giải pháp này có thể gây ra chi phí hao tổn đáng kể về điện toán. Nhiều giải pháp kết xuất phía máy chủ không xả sớm, trì hoãn TTFB hoặc tăng gấp đôi dữ liệu đang được gửi (ví dụ: các trạng thái nội tuyến mà JavaScript sử dụng trên máy khách). Trong React, renderToString()
có thể bị chậm vì là đồng bộ và đơn luồng.
Các API DOM máy chủ React mới hơn hỗ trợ tính năng truyền trực tuyến, nhờ đó có thể gửi phần đầu tiên 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ủ.
Để có được kết xuất phía máy chủ "chính xác", bạn có thể tìm hoặc xây dựng giải pháp cho việc lưu thành phần vào bộ nhớ đệm, quản lý mức sử dụng bộ nhớ, sử dụng các 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 công việc trên ứng dụng sau khi phản hồi HTML do máy chủ tạo đến ứng dụng, thì điều này vẫn có thể làm tăng TBT và INP cho trang web của bạn.
Tính năng kết xuất phía máy chủ tạo HTML theo yêu cầu cho mỗi URL, nhưng có thể chậm hơn so với việc chỉ phân phát nội dung kết xuất tĩnh. Nếu bạn có thể thực hiện thêm một số thao tác, thì tính năng kết xuất phía máy chủ cùng với tính năng lưu HTML vào bộ nhớ đệm có thể giúp giảm đáng kể thời gian kết xuất phía máy chủ. Ưu điểm của tính năng kết xuất phía máy chủ là khả năng lấy nhiều dữ liệu "trực tiếp" hơn và phản hồi một tập hợp yêu cầu đầy đủ hơn so với tính năng kết xuất tĩnh. Các trang cần cá nhân hoá là ví dụ cụ thể về loại yêu cầu không hoạt động tốt với tính năng kết xuất tĩnh.
Tính năng kết xuất phía máy chủ cũng có thể đưa ra các quyết định thú vị khi xây dựng PWA: bạn nên sử dụng tính năng lưu vào bộ nhớ đệm trình chạy dịch vụ toàn trang hay chỉ kết xuất từng phần nội dung trên máy chủ?
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 trực tiếp trong trình duyệt bằng JavaScript. Tất cả logic, tính năng tìm nạp dữ liệu, tạo mẫu và định tuyến đều được xử lý trên máy khách thay vì trên máy chủ. Kết quả hiệu quả là máy chủ sẽ truyền nhiều dữ liệu hơn đến thiết bị của người dùng, và điều này đi kèm với một loạt các yếu tố đánh đổi riêng.
Rất khó để tạo và duy trì tính năng kết xuất phía máy khách nhanh cho thiết bị di động.
Chỉ cần một chút nỗ lực để duy trì ngân sách JavaScript chặt chẽ và phân phối giá trị trong ít lượt đi và về nhất có thể, bạn có thể khiến quá trình kết xuất phía máy khách gần như tái tạo hiệu suất của quá trình kết xuất 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 rằng thao tác điều hướng ban đầu và sau đó diễn ra tức thì.
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. Việc này trở nên đặc biệt khó khăn khi thêm các thư viện JavaScript mới, polyfill và mã của bên thứ ba. Các thành phần này cạnh tranh với nhau 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 phân tách mã một cách mạnh mẽ để giảm TBT và INP trong quá trình tải trang, cũng như tải JavaScript từng phần để chỉ phân phát những gì người dùng cần, khi cần. Đối với những trải nghiệm có ít hoặc không có tính tương tác, tính năng kết xuất phía máy chủ có thể là giải pháp có thể mở rộng hơn cho những vấn đề này.
Đối với những người xây dựng ứ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 mà hầu hết các trang đều chia sẻ cho phép bạn áp dụng kỹ thuật lưu vào bộ nhớ đệm vỏ ứ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 nhận thấy được trong các lượt truy cập lặp lại, vì trang có thể tải HTML vỏ ứng dụng và các phần phụ thuộc từ CacheStorage
rất nhanh.
Quá trình tái tạo kết hợp chế độ hiển thị phía máy chủ và phía máy khách
Kết xuất lại là một phương pháp cố gắng làm mượt các yếu tố đánh đổi giữa kết xuất phía máy khách và kết xuất 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 như tải trang đầy đủ hoặc tải lại được xử lý bằng một máy chủ hiển thị ứng dụng thành HTML, sau đó JavaScript và dữ liệu dùng để hiển thị được nhúng vào tài liệu thu được. Khi được thực hiện cẩn thận, phương thức này sẽ đạt được FCP nhanh như kết xuất phía máy chủ, sau đó "nhận lại" 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ể gây ra những hạn chế đáng kể về hiệu suất.
Nhược điểm chính của tính năng kết xuất phía máy chủ với tính năng tái hydrat hoá là tính năng này 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 là đã tải và có thể tương tác, nhưng thực sự 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, khiến người dùng bối rối và khó chịu.
Vấn đề về việc cấp nước: một ứng dụng có giá bằng hai ứng dụng
Để JavaScript phía máy khách "nhậ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ủ đã hiển thị HTML, hầu hết các giải pháp kết xuất phía máy chủ sẽ 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ì việc này sẽ sao chép nhiều HTML, nên việc tái tạo có thể gây ra nhiều vấn đề hơn là chỉ làm chậm hoạt động tương tác.
Máy chủ đang trả về nội dung mô tả giao diện người dùng của ứng dụng để phản hồi yêu cầu điều hướng, nhưng cũng trả về dữ liệu nguồn dùng để tạo giao diện người dùng đó và một bản sao đầy đủ của quá trình triển khai giao diện người dùng, sau đó khởi động trên ứng dụng. Giao diện người dùng sẽ không tương tác cho đến khi bundle.js
tải xong 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à tái tạo 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à tác động của vấn đề này đến trải nghiệm người dùng, khi một trang có vẻ đã sẵn sàng nhưng không có tính năng tương tác nào hoạt động.
Tuy nhiên, bạn vẫn có thể kết xuất phía máy chủ bằng cách kết xuất lại. Trong ngắn hạn, việc chỉ sử dụng tính năng kết xuất phía máy chủ cho nội dung có thể lưu vào bộ nhớ đệm có thể làm giảm TTTFB, mang lại kết quả tương tự như khi kết xuất trước. Việc tái hydrat hoá từng bước, tăng dần hoặc một phần có thể là chìa khoá để giúp kỹ thuật này hoạt động hiệu quả hơn trong tương lai.
Hiển thị luồng phía máy chủ và tái tạo dần dần
Tính năng 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.
Tính năng Kết xuất trực tuyến phía máy chủ cho phép bạn gửi HTML theo các phần mà trình duyệt có thể hiển thị 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, giúp 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 lực ngược được xử lý tốt.
Bạn cũng nên cân nhắc việc tái hydrat hoá dần dần và React đã triển khai tính năng này. Với phương pháp này, các phần riêng lẻ của ứng dụng do máy chủ hiển thị 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 chạy 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 để tạo trang tương tác, vì nó cho phép bạn trì hoãn việc nâng cấp phía máy khách của các phần có mức độ ưu tiên thấp trên 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 các hoạt động đó.
Tính năng tái tạo dần dầ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 tái tạo kết xuất phía máy chủ: cây DOM do máy chủ kết xuất bị huỷ bỏ 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 cần dữ liệu chưa sẵn sàng, thường là Promise
chưa được phân giải.
Bù nước một phần
Việc tái hydrat hoá một phần đã được chứng minh là khó triển khai. Phương pháp này là một phần mở rộng của tính năng tái hydrat hoá dần dần, giúp phân tích từng phần của trang (thành phần, thành phần hiển thị hoặc cây) và xác định những phần có ít tính tương tác hoặc không có tính phản ứng. Đối với mỗi phần chủ yếu là tĩnh này, mã JavaScript tương ứng sẽ được chuyển đổi thành các tệp 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 như bằng 0.
Phương pháp hydrat hoá một phần cũng có những vấn đề và sự đánh đổi riêng. Điều này đặt ra một số thách thức thú vị đối với việc lưu vào bộ nhớ đệm và đ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 ba hình dạng
Nếu trình chạy dịch vụ là một lựa chọn dành cho bạn, hãy cân nhắc kết xuất tam cấu trúc. Đây là một kỹ thuật cho phép bạn sử dụng tính năng kết xuất trực tuyến phía máy chủ cho các thao tác điều hướng ban đầu hoặc không phải JS, sau đó yêu cầu worker dịch vụ thực hiện việc kết xuất HTML cho các thao tác điều hướng sau khi đượ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 được cập nhật, đồng thời cho phép điều hướng kiểu SPA để hiển thị các thành phần hiển thị mới trong cùng một phiên. Phương pháp 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à một lựa chọn phổ biến để mang lại 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 thông tin có thể hiểu JavaScript, nhưng thường có hạn chế về cách trình thu thập thông tin 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 thử nghiệm và hao tổn. Gần đây, 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 bạn 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ột trang xuất hiện trước 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 thực thi JavaScript 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 phương pháp kết xuất, hãy đo lường và tìm hiểu nút thắt cổ chai của bạn. Hãy cân nhắc xem chế độ kết xuất tĩnh hay kết xuất phía máy chủ có thể giúp bạn thực hiện hầu hết các bước hay không. Bạn có thể chủ yếu phân phối HTML với JavaScript tối thiểu để có trải nghiệm tương tác. Dưới đây là một biểu đồ thông tin hữu ích cho thấy quang phổ máy chủ-máy khách:
Ghi công
Cảm ơn mọi người đã gửi bài đánh giá và nguồn cảm hứng:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson và Sebastian Markbåge