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ọ. Việc này có thể khó khăn vì có rất nhiều cách để xây dựng một trang web.
Chúng tôi hiểu rõ hơn về không gian này dựa trên quá trình làm việc trong Chrome, trao đổi với các trang web lớn trong vài năm qua. Nói chung, các nhà phát triển nê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 bằng phương pháp bù nước toàn bộ.
Để hiểu rõ hơn về cấu trúc mà chúng tôi sẽ chọn khi đưa ra quyết định này, chúng tôi cần hiểu rõ về từng phương pháp và thuật ngữ nhất quán để sử dụng khi đề cập đến những phương pháp này. Sự khác biệt giữa các phương pháp kết xuất giúp minh hoạ sự đánh đổi của việc hiển thị trên web so với quan điểm hiệu suất trang.
Thuật ngữ
Kết xuất
- Hiển thị phía máy chủ (SSR)
- Hiển thị một ứng dụng phía máy khách hoặc ứng dụng toàn cầu sang HTML trên máy chủ.
- Hiển thị phía máy khách (CSR)
- Hiển thị ứng dụng trong trình duyệt, sử dụng JavaScript để sửa đổi DOM.
- Bù nước
- "Tăng" khung hiển thị JavaScript trên máy khách để sử dụng lại cây và dữ liệu DOM của HTML do máy chủ kết xuất.
- 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 để nắm được 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 cho byte đầu tiên (TTFB)
- Thời gian tính từ lúc nhấp vào một đường liên kết cho đến lúc 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 điểm 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 với nội dung hiển thị tiếp theo (INP)
- Một chỉ số đại diện giúp đánh giá liệu một trang có phản hồi nhanh chóng một cách 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)
- Một chỉ số proxy cho INP tính toán thời gian luồng chính bị chặn trong quá trình tải trang.
Kết xuất phía máy chủ
Tính năng hiển thị phía máy chủ sẽ tạo HTML đầy đủ 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 phát sinh thêm các lượt trọn vòng để 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 lượt xem này trước khi trình duyệt nhận được phản hồi.
Quá trình kết xuất phía máy chủ thường tạo ra FCP nhanh. Việc chạy logic trang và hiển thị trên máy chủ giúp bạn tránh phải gửi nhiều JavaScript đến máy khách. Điều này giúp giảm TBT của trang. Điều này 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 hoạt động 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ì khi hiển thị phía máy chủ, bạn thực sự chỉ gửi văn bản và các đườ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 hiệu quả với 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á thú vị cho trình duyệt như phân tích cú pháp tài liệu truyền trực tuyến.
Với tính năng hiển thị phía máy chủ, người dùng ít có khả năng phải chờ JavaScript bị ràng buộc bởi CPU chạy trước khi có thể sử dụng trang web của bạn. Ngay cả khi không thể tránh được JS 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ó nhiều ngân sách hơn cho phần còn lại. Tuy nhiên, có một phương pháp đánh đổi tiềm năng với phương pháp này: việc tạo trang trên máy chủ sẽ mất thời gian và điều này có thể làm tăng TTFB của trang.
Việc tính năng kết xuất phía máy chủ có đủ cho ứng dụng của bạn hay không chủ yếu phụ thuộc vào loại trải nghiệm bạn đang xây dựng. Đã có nhiều tranh cãi đã lan truyền từ lâu về việc ứng dụng đúng của kết xuất phía máy chủ và kết xuất phía máy khách. Tuy nhiên, bạn luôn có thể chọn sử dụng phương thức kết xuất phía máy chủ cho một số trang, chứ không phải 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ụ: máy chủ Netflix kết xuất các trang đích tương đối tĩnh, trong khi tìm nạp trước JS cho các trang nhiều tương tác, nhờ đó, các trang do ứng dụng hiển thị nặng hơn này có 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ả ứng dụng 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à máy khách đều là các lớp giải pháp riêng với đặ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 các API đó, chẳng hạn 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ủ hoặc Nuxt của Vue. Angular có hàm Universal. Tuy nhiên, hầu hết các giải pháp phổ biến đều sử dụng một dạng hydrat hoá, vì vậy, hãy lưu ý phương pháp mà công cụ của bạn sử dụng.
Kết xuất tĩnh
Quá trình kết xuất tĩnh diễn ra trong thời gian xây dựng. Phương pháp này mang lại FCP nhanh, TBT và INP thấp hơn, miễn là bạn giới hạn số lượng JS 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ủ, phương thức này cũng đạt được TTFB nhanh một cách nhất quán, vì HTML cho một trang không phải được tạo động trên máy chủ. Nhìn chung, kết xuất tĩnh có nghĩa là tạo ra một tệp HTML riêng cho từng URL. Với phản hồi HTML được tạo trước, bạn có thể triển khai tính năng kết xuất tĩnh cho nhiều CDN để tận dụng bộ nhớ đệm ở cạnh.
Các giải pháp hiển thị tĩnh có đủ mọi hình dạng và kích thước. Các công cụ như Gatsby được thiết kế để giúp các nhà phát triển cảm thấy như ứng dụng của họ đang được kết xuất động chứ không được tạo ở 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 tính chất tĩnh của chúng để cung cấp phương pháp tiếp cận dựa trên mẫu hơn.
Một trong những nhược điểm của phương thức kết xuất tĩnh là phải tạo tệp HTML riêng lẻ cho mọi URL có thể có. Điều 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 nào sẽ xuất hiện trước hoặc đối với các trang web có số lượng lớn các 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ả đều giúp việc tạo trang từ các thành phần trở nên thuận tiện. Tuy nhiên, tính năng kết xuất tĩnh và kết xuất trước hoạt động theo cách khác nhau: các trang kết xuất tĩnh sẽ tương tác mà không cần thực thi nhiều JavaScript phía máy khách, trong khi tính năng kết xuất trước sẽ cải thiện FCP của một Ứng dụng trang đơn mà 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 chắn liệu giải pháp đã cho là hiển thị tĩnh hay kết xuất trước, hãy thử vô hiệu hoá JavaScript và tải trang mà bạn muốn kiểm tra. Đối với các trang hiển thị 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ư đường liên kết đã tắt JavaScript, nhưng hầu hết trang đều ở dạng tĩnh.
Một quy trình kiểm thử hữu ích khác là sử dụng quy trình điều tiết 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 có tính tương tác. Quá trình 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 so với phương pháp nâng cao tăng dần dùng trong 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 linh động của tính năng này có thể gây ra chi phí điện toán đáng kể. Nhiều giải pháp hiển thị phía máy chủ không đẩy sớm, trì hoãn TTFB hoặc gửi gấp đôi dữ liệu đang được gửi (ví dụ: trạng thái cùng dòng được JavaScript sử dụng trên máy khách). Trong React, renderToString()
có thể bị chậm vì có tính đồng bộ và đơn luồng.
API DOM của máy chủ React mới hơn hỗ trợ truyền trực tuyến, có thể nhận được phần đầu của phản hồi HTML cho 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ủ.
Để quá trình kết xuất phía máy chủ "phù hợp" diễn ra, bạn có thể phải tìm hoặc xây dựng một giải pháp để lưu thành phần vào bộ nhớ đệm, quản lý mức sử dụng 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 ứng dụng và một lần trên máy chủ. Việc kết xuất phía máy chủ việc hiển thị nội dung sớm không nhất thiết khiến bạn phải làm nhiều việc hơn. Nếu bạn phải làm nhiều việc với ứng dụng sau khi phản hồi HTML do máy chủ tạo đến ứng dụng, điều này vẫn có thể dẫn đến TBT và INP cao hơn cho trang web của bạn.
Tính năng 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 kết xuất tĩnh. Nếu bạn có thể thực hiện thêm các công việc khó khăn, thì tính năng hiển thị phía máy chủ cộng với lưu HTML vào bộ nhớ đệm có thể làm 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 nhóm yêu cầu hoàn chỉnh hơn so với phương thức 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 chế độ hiển thị tĩnh.
Tính năng 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 PWA: nên sử dụng tính năng lưu vào bộ nhớ đệm service worker toàn trang hay chỉ kết xuất từng phần nội dung riêng lẻ?
Kết xuất phía máy khách
Kết xuất phía máy khách nghĩa là kết xuất các trang trực tiếp trong trình duyệt bằng JavaScript. Mọi logic, tìm nạp dữ liệu, tạo mẫu và định tuyến đều được xử lý trên ứng dụng thay vì trên máy chủ. Kết quả hiệu quả là nhiều dữ liệu hơn được truyền đến thiết bị của người dùng từ máy chủ, đi kèm với tập hợp phương thức đánh đổi riêng.
Tính năng hiển thị phía máy khách có thể khó thực hiện và duy trì tốc độ nhanh đối với thiết bị di động.
Chỉ mất một chút công sức để duy trì ngân sách JavaScript chặt chẽ và mang lại giá trị trong ít lượt khứ hồi nhất có thể, bạn có thể kết xuất phía máy khách để gần như tái tạo hiệu suất của kết xuất phía máy chủ thuần tuý. 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 thông qua <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 các thao tác điều hướng ban đầu và tiếp theo đều 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 mới, đoạn mã polyfill và mã của bên thứ ba. Các thư viện này cạnh tranh về khả năng xử lý và thường phải được xử lý trước khi nội dung của một trang có thể hiển thị.
Với những trải nghiệm sử dụng tính năng hiển thị phía máy khách và dựa vào các gói JavaScript lớn, bạn nên cân nhắc việc phân tách mã linh hoạt để giảm TBT và INP trong quá trình tải trang, cũng như JavaScript tải từng phần để chỉ phân phát những gì người dùng cần, khi cần thiết. Đối với các 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à một giải pháp dễ mở rộng hơn cho những vấn đề này.
Đối với người tạo các ứng dụng trang đơn, việc xác định các phần cốt lõi của giao diện người dùng được chia sẻ bởi hầu hết các trang cho phép bạn áp dụng kỹ thuật lưu vào bộ nhớ đệm shell ứ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 trong các lượt truy cập lặp lại, vì trang có thể tải HTML shell ứng dụng và các phần phụ thuộc từ CacheStorage
rất nhanh.
Quá trình khắc phục nước kết hợp giữa hiển thị phía máy chủ và phía máy khách
Khôi phục nước là một phương pháp cố gắng cân bằng sự đánh đổi giữa hiển thị 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 như tải hoặc tải lại toàn bộ trang được xử lý bởi một máy chủ hiển thị ứng dụng dưới dạng HTML, sau đó JavaScript và dữ liệu dùng để kết xuất sẽ được nhúng vào tài liệu thu được. Sau khi thực hiện cẩn thận, quá trình 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 quá trình kết xuất phía máy chủ bằng phương thức cấp nước là có thể tác động tiêu cực đáng kể đến TBT và INP, ngay cả khi cải thiện FCP. Các trang kết xuất phía máy chủ có thể trông giống như đã tải và tương tác được, nhưng không thể phản hồi dữ liệu đầu vào cho đến khi 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, quá trình này có thể mất vài phút, gây bối rối và khó chịu cho người dùng.
Vấn đề về tái nước: 1 ứng dụng giá 2 ứng dụng
Để JavaScript phía máy khách "lấy" chính xác vị trí 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 của nó, hầu hết các giải pháp hiển thị 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ì quá trình này trùng lặp rất nhiều HTML, nên việc bù nước có thể gây ra nhiều vấn đề hơn là chỉ làm chậm khả năng tương tác.
Máy chủ 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 trả về dữ liệu nguồn dùng để soạn 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 đó sẽ khởi động trên ứng dụng. Giao diện người dùng chỉ có thể tương tác khi bundle.js
tải và thực thi xong.
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 hiển thị và khôi phục phía máy chủ cho thấy rằng 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 yếu tố này đối với 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 trên đó hoạt động.
Tuy nhiên, vẫn có khả năng kết xuất phía máy chủ bằng tính năng bù nước. Trong thời gian ngắ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 ở mức cao có thể làm giảm TTFB, tạo ra kết quả tương tự như kết xuất trước. Việc bù nước dần dần, tăng dần hoặc một phần có thể là chìa khoá để giúp kỹ thuật này trở nên khả thi hơn trong tương lai.
Truyền trực tuyến quá trình kết xuất phía máy chủ và khôi phục dần dần
Tính năng hiển thị phía máy chủ đã có một số bước phát triển trong vài năm qua.
Tính năng Truyền trực tuyến phía máy chủ cho phép bạn gửi HTML theo từng phần mà trình duyệt có thể hiển thị dần khi nhận được. Việc này có thể giúp người dùng nhận được mã đánh dấu nhanh hơn, nhờ đó tăng FCP. Trong phản hồi, các luồng không đồng bộ trong renderToPipeableStream()
so với renderToString()
đồng bộ có nghĩa là backpressure được xử lý tốt.
Bạn cũng nên cân nhắc việc bù nước tăng dần và React đã triển khai tính năng này. Với phương pháp này, từng phần của ứng dụng kết xuất từ 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 động 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 để các trang có khả năng tương tác, vì tính năng này cho phép bạn trì hoãn việc nâng cấp phía máy khách các phần có mức độ ưu tiên thấp của trang để ngăn các phần đó chặn luồng chính, cho phép người dùng tương tác diễn ra sớm hơn sau khi người dùng bắt đầu.
Quá trình bù nước tích luỹ cũng có thể giúp bạn tránh một trong những sai lầm phổ biến nhất về việc bù nước khi kết xuất phía máy chủ: cây DOM do máy chủ kết xuất bị huỷ rồi sau đó được tạo lại ngay lập tức, thường là do dữ liệu yêu cầu phía máy khách hiển thị đồng bộ ban đầu chưa hoàn toàn sẵn sàng, thường là Promise
chưa được giải quyết.
Bù nước một phần
Phương pháp bù nước một phần cho thấy rất khó thực hiện. Phương pháp này là phần mở rộng của quá trình bù nước tăng dần giúp phân tích từng phần của trang (các thành phần, khung hiển thị hoặc cây) và xác định các phần có ít tương tác hoặc không có phản ứng. Đối với mỗi phần hầu như tĩnh này, mã JavaScript tương ứng sẽ được chuyển thành các tham chiếu tĩnh và tính năng trang trí, giúp giảm dấu vết phía máy khách xuống gần bằng 0.
Phương pháp bổ sung nước một phần cũng có những vấn đề và yếu tố ảnh hưởng riêng. Điều này đặt ra một số thách thức thú vị khi lưu vào bộ nhớ đệm và điều hướng phía máy khách có nghĩa là chúng tôi không thể giả định rằng HTML do máy chủ kết xuất cho các phần tĩnh của ứng dụng sẽ hoạt động mà không cần tải toàn bộ trang.
Kết xuất ba hình
Nếu bạn có thể sử dụng trình chạy dịch vụ, hãy cân nhắc sử dụng phương thức kết xuất trisomorphic. Đây là một kỹ thuật 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 thành phần điều hướng ban đầu hoặc không phải JS, sau đó yêu cầu trình chạy dịch vụ tiếp nhận việc kết xuất HTML cho các thành phần điều hướng sau khi 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ập nhật, đồng thời bật các thao tác điều hướng kiểu SPA để kết xuất các khung hiển thị mới trong cùng một phiên. Phương pháp này 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 khách và trình chạy dịch vụ.
Những điều cần cân nhắc về SEO
Khi chọn chiến lược hiển thị 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 "tìm kiếm hoàn chỉnh" mà trình thu thập dữ liệu có thể diễn giải. Trình thu thập dữ liệu có thể hiểu được JavaScript, nhưng thường có các hạn chế về cách kết xuất. Tính năng kết xuất phía máy khách có thể hoạt động, nhưng thường cần kiểm thử và hao tổn thêm. Gần đây hơn, tính năng kết xuất động cũng đã trở thành một lựa chọn đáng cân nhắc nếu kiến trúc của bạn phụ thuộc nhiều vào JavaScript phía máy khách.
Khi nghi ngờ, công cụ kiểm tra tính thân thiện với thiết bị di động là một cách hay để kiểm tra xem phương pháp mà bạn chọn có đáp ứng những điều bạn mong đợi hay không. API này cho thấy bản xem trước trực quan về cách trình thu thập dữ liệu của Google nhìn thấy một trang bất kỳ, nội dung HTML chuyển đổi tuần tự mà công cụ này tìm thấy sau khi thực thi JavaScript và mọi lỗi gặp phải trong quá trình hiển thị.
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 xem nút thắt cổ chai của bạn là gì. Hãy cân nhắc xem kết xuất tĩnh hoặc kết xuất phía máy chủ có thể giúp bạn đạt được hiệu quả tối đa hay không. Bạn có thể chủ yếu gửi HTML có JavaScript tối thiểu để có được trải nghiệm tương tác. Dưới đây là một bản đồ hoạ thông tin hữu ích cho thấy nhiều máy chủ-ứng dụng:
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