Cải thiện SEO cho ứng dụng React

Cải thiện SEO cho ứng dụng React
Photo by Werner Moser from Pixabay

React là một thư viện giúp việc lập trình frontend dễ dàng và hiệu quả hơn. Thế nhưng thư viện rất nổi tiếng này lại có những vấn đề với các cỗ máy tìm kiếm. Trong bài viết này, tôi sẽ trình bày những hiểu biết của mình về những vấn đề đó và một số phương pháp có thể áp dụng để vượt qua khó khăn.

Một số khái niệm liên quan đến SEO

Google index các trang web như thế nào?

Khi nói đến máy tìm kiếm, có lẽ ai cũng nghĩ ngay đến Google. Thời điểm hiện tại Google vẫn là máy tìm kiếm được yêu thích nhất, chiếm hơn 90% lượng tìm kiếm trên Internet. Vì vậy khi nói đến SEO (Search Engine Optimization) thì mặc định sẽ là tối ưu cho máy tìm kiếm của Google.

Nghe nói Bing đang tích hợp ChatGPT vào. Có lẽ thứ hạng của các cỗ máy tìm kiếm sẽ được cập nhật sớm thôi 👍. Lúc đó các kỹ thuật SEO sẽ thay đổi rất nhiều, và có lẽ tôi sẽ có một bài viết khác.

Để làm được điều đó, trước hết cần phải hiểu được cách máy tìm kiếm của Google làm việc. Điều này là rất khó, thậm chí chính kỹ sư của Google cũng chưa chắc đã có thể hiểu được. Thế nhưng, biết được một phần thông tin cũng là tốt hơn không biết gì.

Dưới đây là một sơ đồ lấy từ chính chủ Google mô tả quá trình máy tìm kiếm của Google index các trang web. Nó đã được đơn giản hóa rất nhiều so với thực tế.

indexing
Nguồn: Google Search Central

Các bước Google index các trang web có thể mô tả như dưới đây:

  1. Googlebot có một hàng đợi chứa các URL cần index
  2. Crawler sẽ lấy URl từ hàng đợi, thực hiện truy vấn để lấy HTML
  3. Sau khi phân tích HTML nhận được, Googlebot sẽ quyết định liệu có cần tải JavaScript và thực thi hay không. Nếu có, URL được đưa vào hàng đợi render.
  4. Một xử lý render khác sẽ tải JavaScript và render trang web, sau đó đưa HTML thu được lại cho bộ xử lý index
  5. Bộ xử lý trích xuất các URL từ thẻ <a> của HTML và đưa vào hàng đợi
  6. Nội dung của trang được đưa vào Google index

Lưu ý rằng, có hai bước riêng biệt nhau: xử lýrender (thực thi JavaScript). Lý do tồn tại hai bước độc lập này là vì việc tải và thực thi JavaScript rất tốn kém về tài nguyên và thời gian, trong bối cảnh Googlebot phải index 130 nghìn tỉ (130 và 12 số 0) trang web.

Với số lượng trang web quá lớn như vậy, khi Googlebot crawl một trang, nó sẽ ngay lập tức phân tích HTML và đưa việc thực thi JavaScript vào hàng đợi. Theo Google, một trang web sẽ chờ trong hàng render khoảng vài giây, thế nhưng không có gì đảm bảo cả.

Ngoài ra còn một khái niệm gọi là crawl budget. Việc crawl của Google bị giới hạn bởi băng thông, thời gian và tài nguyên máy chủ của Google. Vì vậy với mỗi trang web sẽ chỉ có một budget (ngân sách) để index các trang. Nếu một website có quá nhiều trang với nội dung rất lớn (ví dụ các trang thương mại điện tử), và các trang này lại dùng nhiều JavaScript để render nội dung, thì khả năng rất cao là Google không thể index được toàn bộ nội dung của website.

Google có tài liệu về crawl budget cho các website lớn ở đây.

Các thông số Performance

Theo Google, để họ có thể cung cấp kết quả tìm kiếm cho người dùng nhanh chóng và chính xác, một website tốt nên có những đặc điểm sau:

  • Người dùng có thể xem nội dung trang web mà không cần phải chờ quá lâu
  • Phản hồi lại thao tác của người dùng nhanh chóng
  • Tải trang nhanh
  • Không nên tải dữ liệu không cần thiết và thực thi những code dư thừa

Những đặc điểm này hoàn toàn có thể đo đạc theo những thông số cụ thể như sau:

  • Time to First Byte (TTFB): Thời gian kể từ khi người dùng bấm vào một link (mở trang web) tới khi nhận được những byte dữ liệu đầu tiên
  • Largest Contentful Paint (LCP): Thời gian để nội dung được hiển thị. Cũng theo Google, thời gian này nên dưới 2.5 giây.
  • Time to Interactive (TTI): Thời gian để trang web có thể tương tác (nghĩa là người dùng có thể thao tác như cuộn hay click chuột).
  • Bundle Size: Khối lượng dữ liệu được tải và thực thi trước khi trang web có thể hiển thị đầy đủ.

Google có hẳn một loạt các thông số gọi là Core Web Vitals. Những thông số này liên quan đến trải nghiệm người dùng, và được dùng để xếp hạng các trang web. Một trang web phải chờ lâu hơn, nó sẽ bị Google xếp hạng thấp hơn.

Những vấn đề của React với SEO

React được phát triển để có thể tạo ra các ứng dụng web tương tác tốt hơn. Ngày nay, nó là một trong những thư viện JavaScript phổ biến nhất để lập trình frontend. Thế nhưng, chính những tính năng khiến người ta yêu thích React lại khiến nó gặp một vài vấn đề với các cỗ máy tìm kiếm.

Với các ứng dụng React, một lượng lớn HTML và CSS sẽ được chuyển vào JavaScript. React không trực tiếp làm việc với UI, mà thay vào đó nó quản lý state của UI. Thư viện này có cơ chế tự động cập nhật DOM theo sự thay đổi của state (tham khảo thêm ở đây).

Điều này với các lập trình viên thì rất tốt, nhưng tác dụng phụ của nó là người dùng (và cả những cỗ máy tìm kiếm) sẽ phải tốn thêm thời gian để tải và thực thi JavaScript trước khi có thể xem được nội dung. Đây là vấn đề với SEO.

Dưới đây là những vấn đề cụ thể hơn khi SEO một trang web viết bằng React.

Nội dung ban đầu hoàn toàn trống

Các ứng dụng React phụ thuộc hoàn toàn và JavaScript và nó sẽ là vấn đề với máy tìm kiếm. Các ứng dụng React thường sử dụng app shell model, có nghĩa là HTML ban đầu của trang web hoàn toàn không chứa thông tin nào cả. Người dùng và cả máy tìm kiếm sẽ phải thực thi JavaScript mới có thể nhìn thấy được nội dung.

Khi Google tải HTML của trang web về, HTML ban đầu sẽ hoàn toàn trống. Google chỉ có thể index trang web sau khi render, tức là phải chờ JavaScript được thực thi xong. Khi một website có nhiều trang, điều này sẽ khiến việc index bị chậm đi khá nhiều.

Hiệu suất và trải nghiệm người dùng

Tải và thực thi JavaScript luôn tốn thời gian. Đặc biệt với những ứng dụng sử dụng React, file bundle JavaScript thường có khối lượng rất lớn, do phải đóng gói toàn bộ code cùng các thư viện liên quan. Dù có nhiều công cụ khác nhau để chia nhỏ file bundle ra, nhưng về cơ bản khối lượng của chúng vẫn là quá lớn.

Một vấn đề nữa là các file bundle sẽ được dùng chung cho tất cả các trang, do đó tỉ lệ code được sử dụng cho từng trang sẽ ở mức thấp. Ngoài ra, JavaScript còn có thể gọi truy vấn để lấy thêm thông tin trước khi hiển thị, và người dùng phải chờ quá trình đó kết thúc mới xem được nội dung trang web. Điều này ít nhiều ảnh hưởng đến trải nghiệm người dùng.

Metadata

Thẻ <meta> là những thẻ rất quan trọng để Google cũng như nhiều dịch vụ khác hiển thị phần preview trang web, bao gồm tiêu đề, thumbnail và một số mô tả ngắn gọn. Để hiển thị những thông tin này, thông thường Google cũng như các dịch vụ SNS đều dựa vào thông tin ở các thẻ <meta> trong phần <head>. Không có dịch vụ nào chờ thực thi JavaScript để hiển thị cả.

Với ứng dụng React, kể cả các thẻ <meta> cũng được render khi JavaScript được thực thi. Vì vậy HTML ban đầu của trang web thường là một template và nó sẽ giống nhau cho toàn bộ website. Điều này có thể khiến việc hiển thị cho từng trang web gặp rất nhiều khó khăn.

Sitemap

Sitemap là một tập tin mà website cung cấp thông tin về các trang, video và các tài nguyên khác. Để crawl trang web hiệu quả hơn, Google thường sử dụng tập tin này.

Với React thì việc tạo sitemap cho website là tương đối khó khăn. Bởi vì React không có công cụ sẵn nào để làm việc đó. Nếu sử dụng các thư viện như React Router thì đỡ hơn một chút. Có thể tìm thấy nhiều công cụ hỗ trợ tạo sitemap, nhưng cũng sẽ tốn khá nhiều công sức.

Các vấn đề khác không liên quan đến React

Những vấn đề dưới đây liên quan đến SEO nói chung, không trực tiếp liên quan đến React nhưng cũng cần xem xét khi phát triển ứng dụng React.

  • Cần phải có cấu trúc URL tốt, thân thiện với người dùng, để cả người dùng và máy tím kiếm có thể hiểu được phần nào nội dung trang web.
  • Tập tin robots.txt cần phải được tối ưu. Tập tin này sẽ giúp search bot crawl website chính xác hơn.
  • Sử dụng CDN để lưu trữ và phân phối các tài nguyên tĩnh như CSS, JS, v.v.... Đồng thời sử dụng responsive image để giảm thời gian chờ (tăng trải nghiệm người dùng).

Trong các phần tiếp theo, tôi sẽ trình bày những phương pháp giúp mình vượt qua những khó khăn trên.

Cách khắc phục

Chỉ dùng React một phần

Những vấn đề nêu trên gọi là vấn đề của React thì không hoàn toàn chính xác. Nói một cách đúng nhất thì đó là những vấn đề của những ứng dụng kiểu Single Page App (SPA) sử dụng React (cùng với React Redux). Vào thời điểm này thì có lẽ hầu hết các ứng dụng React đều thuộc dạng này (nên nói kiểu tổng quát là vấn đề của React thì cũng tạm chấp nhận được).

Một trong những phương pháp có thể áp dụng để giải quyết vấn đề là ngừng sử dụng React theo kiến trúc như vậy và quay lại với những ứng dụng web truyền thống. Chỉ những thành phần cần thay đổi UI khi tương tác với người dùng mới sử dụng React mà thôi. Trong trường hợp đó, các React Component thường không quá phức tạp, có thể sử dụng kiến trúc Flux nguyên thủy của Facebook, hoặc thậm chí chẳng cần kiến trúc nào cũng được.

Áp dụng phương pháp này, rất nhiều vấn đề về SEO mà React gặp phải trong phần trước sẽ tự động biết mất. Khi tôi bắt đầu làm quen với React cũng làm việc theo kiểu thế này. Ứng dụng chủ yếu viết bằng Ruby on Rails, một số phần UI ở phía client cần tương tác và cập nhật theo dữ liệu người dùng nhập vào sẽ viết bằng React.

Thế nhưng, những ứng dụng như vậy ngày càng ít xuất hiện, ắt hẳn phải có lý do nào đó. Có lẽ một phần cũng vì những phương pháp dưới đây đã giúp giải quyết nhiều vấn đề liên quan đến React nên một ứng dụng SPA React cũng không gặp phải vấn đề gì lớn cả 👍.

Client-side Rendering With Bootstrapped Data

Phương pháp này cũng tương tự như phương pháp render mặc định của React (thực hiện truy vấn để lấy dữ liệu hiển thị). Nhưng thay vì chờ JavaScript gửi truy vấn và lấy dữ liệu từ server theo kiểu bất đồng bộ, server có thể đưa sẵn những thông tin này ngay trong HTML.

Ví dụ, trong HTML có thể nhúng vào dữ liệu thế này:

<script id="data" type="application/json">
    {"title": "My blog title", "comments":["comment 1","comment 2"]}
</script>

Và khi React chạy sẽ lấy dữ liệu đó để hiển thị:

let data = JSON.parse(document.getElementById('data').innerHTML);

Phương pháp này có thể giúp tăng hiệu suất của trang web, trong khi lập trình viên vẫn làm việc theo phong cách quen thuộc. Tuy nhiên, phương pháp này cũng sẽ gặp những vấn đề về SEO và React gặp phải.

Isomorphic React

Isomorphic React có nghĩa là với các ứng dụng React, server và client sẽ tương tự nhau. Nghĩa là, các React component sẽ được sử dụng ở cả server và client để đảm bảo sự thống nhất.

Theo từ điển Cambridge, isomorphic nghĩa là: “the same or similar in structure or shape”.

Cách tiếp cận này giúp server có thể render nội dung của React và gửi nó (HTML đã được render) cho người dùng (và cả máy tìm kiếm). Điều này khiến nội dung của trang web sẽ được hiển thị ngay mà không cần phải chờ JavaScript được thực thi.

Một số framework như Next.js hoặc Gatsby đã sử dụng cách tiếp cận này. Nhưng cũng nên lưu ý rằng, các ứng dụng theo kiểu isomorphic sẽ khác so với React truyền thống. Ví dụ các component trên server có thể chứa các token, hoặc key để gọi API. Điều mà khó xuất hiện ở phía client.

Có rất nhiều cách triển khai cụ thể khác nhau. Tôi sẽ mô tả kĩ hơn ở những phần sau. Nhưng lưu ý rằng, một website hoàn toàn có thể áp dụng nhiều phương pháp khác nhau, tùy thuộc vào từng trang cụ thể. Hơn nữa, những đánh giá trên đây hoàn toàn là áp dụng cho trang web công khai, nghĩa là trang web mà bất cứ ai cũng có thể xem được.

Với những trang yêu cầu đăng nhập, những trang web có chứa thông tin cá nhân của người dùng cũng không cần SEO nên không cần phải suy xét nhiều quá.

Ngoài ra, trong nội dung bài viết này, mọi so sánh đều dựa trên góc nhìn về SEO. Sẽ còn rất nhiều khía cạnh khác cần phải suy nghĩ khi lựa chọn phương pháp nào phù hợp với một ứng dụng web. Điểm quan trọng là phải làm sao hài hòa được tất cả lợi ích (và đây cũng thường là điểm khó nhất).

Server-side Rendering With Rehydration

Với phương pháp này, server sẽ thực hiện first render, nghĩa là sẽ render nội dung trước, sau đó trả về HTML cho client (kèm với CSS và JS). Ở phía client, React sẽ thực hiện một hành động gọi là rehydrate và sau đó mọi hoạt động của ứng dụng đều do React phía client đảm nhiệm.

React có sẵn một phương thức cho phép làm việc đó là ReactDOMServer. Khi lần đầu truy vấn, truy vấn sẽ được xử lý bởi server và sau đó mới đến client. Những ứng dụng kiểu như vậy được gọi là universal React apps (render ở cả client và server). Code trong những ứng dụng kiểu như vậy thường sẽ lặp lại những phần giống nhau cho server và client.

Phương pháp này sẽ giúp vượt qua những khó khăn liên quan đến SEO của React trong khi vẫn đảm bảo hiệu suất trang web. Tuy nhiên nó sẽ gây nhiều code dư thừa (do phải xử lý ở cả client và server).

Một vấn đề của phương thức ReactDOMServer là nó không hỗ trợ React.lazy. Vì vậy để có thể module hóa tốt hơn, một số thư viện như Loadable Components nên được sử dụng.

Một vấn đề khác nữa là ReactDOMServer hoạt động không giống như React trên client. Những thứ như hook hay life cycle không hoạt động trên server. Do đó, việc truyền dữ liệu cho những component này cần một cách thức khác. Những framework như Next.js đã giúp lập trình viên làm việc với phương pháp này dễ dàng hơn.

Pre-rendering With Rehydration

Liệu một website có thể render nội dung trước khi người dùng truy vấn hay không? Điều này thực ra là hoàn toàn có thể. Ví dụ như blog của tôi đang áp dụng cách này.

Thế nhưng với những ứng dụng web thì sao? Mọi việc sẽ phức tạp hơn vì còn phải tương tác với người dùng. Cũng vì lý do này, mà chỉ một số ứng dụng đặc thù nhất định, như blog hay thương mại điện tử (trang hiển thị sản phẩm) là có thể áp dụng, vì nội dung của chúng không phụ thuộc vào dữ liệu từ người dùng.

Với việc render trước nội dung sẽ giảm tải cho phía server (thậm chỉ chẳng cần server vì các trang là HTML tĩnh được render từ trước) và tăng hiệu suất rất nhiều. Hoạt động ở phía client cũng tương tự như phương pháp trên, truy vấn đầu tiên sẽ thu được dữ liệu đã render từ trước. Sau đó React sẽ rehydrate và mọi hoạt động của ứng dụng đều do React phía client đảm nhiệm.

Những phương pháp khác

Ngoài những phương pháp được mô tả trong bài viết này, còn rất nhiều yếu tố khác cần suy nghĩ liên quan đến SEO. Bài viết này của các kỹ sư làm việc ở Google đã mô tả rất kĩ những phương pháp liên quan đến những ứng dụng nặng về JavaScript như React. Một số phương pháp khác có thể áp dụng là: streaming server rendering, trisomorphic rendering, dynamic rendering (render nội dung khác nhau cho người dùng và bot).

Tôi xin lỗi nếu bài viết có bất kỳ typo nào. Nếu bạn nhận thấy điều gì bất thường, xin hãy cho tôi biết.

Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.