SEO cho Angular

Cách giúp ứng dụng Angular được crawl và lập chỉ mục: @angular/ssr, prerendering, hybrid rendering, hydration, service Title/Meta, định tuyến History API và kiểm thử output Googlebot thực sự render.

Xuất bản lần đầu: 26 thg 6, 2026 · Cập nhật lần cuối: 22 thg 8, 2026 · Advanced
Ngôn ngữ

Angular SEO quy về việc gửi HTML hoàn chỉnh thay vì shell render phía client. Angular v17+ tích hợp SSR trong @angular/ssr: prerender route tĩnh, server-render route động, kết hợp theo từng route và hydrate để tái sử dụng HTML máy chủ. Sau đó đặt tiêu đề/mô tả riêng bằng Title và Meta, dùng URL History không có hash, liên kết <a href> thực, chèn JSON-LD an toàn và xác minh bằng URL Inspection. Dynamic rendering chỉ là giải pháp tình thế; AngularJS là framework khác.

Tóm tắt — Angular SEO quy về một quyết định kiến trúc: đưa HTML thực vào response thay vì gửi shell được render phía client. Angular hiện đại (v17+) tích hợp SSR trong CLI dưới tên @angular/ssr, kế nhiệm Angular Universal. Prerender route tĩnh, server-render route động, kết hợp chúng bằng hybrid rendering và dùng hydration để tái sử dụng thay vì dựng lại HTML từ máy chủ. Sau đó xử lý các nền tảng: tiêu đề/mô tả riêng qua service Title/Meta hoặc TitleStrategy của router; định tuyến HTML5 History, không bao giờ dùng HashLocationStrategy; liên kết <a href> thực; chèn JSON-LD an toàn; và xác minh bằng URL Inspection. Dynamic rendering là giải pháp tình thế được Google thừa nhận, không phải chiến lược.

Evidence for this claim Modern Angular documents @angular/ssr as its server-side and hybrid rendering package. Scope: Current Angular documentation; historical Angular Universal details are not required for this claim. Confidence: high · Verified: Angular: Server-side and hybrid rendering

Mặc định chính là vấn đề: client-side rendering

Một bản build Angular tiêu chuẩn cung cấp index.html với phần body gần như chỉ có <app-root></app-root> cùng các script tag. Nếu không thực thi JavaScript, crawler không thấy heading, nội dung hay liên kết; nội dung chỉ được lắp ráp trong trình duyệt sau khi bundle tải xong. Đây là vấn đề cốt lõi tôi mô tả trong JavaScript SEO: web đã rời xa HTML thuần và CSR nằm ở đầu rủi ro nhất của phổ đó.

(Các chi tiết triển khai trong hướng dẫn này — API RenderMode, hydrate trigger và event replay — phản ánh Angular v22. Các tính năng phụ thuộc phiên bản bên dưới đều ghi rõ mốc: đổi tên SSR ở v17, event replay ở v18 và incremental hydration từ v19–v20.)

CSR tạo ra ba vấn đề SEO riêng biệt:

  1. Lập chỉ mục chậm và kém tin cậy hơn. Google xử lý ứng dụng JS theo ba giai đoạn — “Crawling, Rendering, and Indexing” (bản dịch) “Thu thập dữ liệu, render và lập chỉ mục” — và đưa việc render vào hàng đợi. Nội dung chưa tồn tại để lập chỉ mục cho tới khi lượt render đó chạy.
  2. Core Web Vitals kém hơn. LCP chịu ảnh hưởng vì trình duyệt phải tải và thực thi bundle trước khi vẽ nội dung có ý nghĩa.
  3. Crawler ngoài Google chỉ thấy shell. Bingbot render JS kém nhất quán hơn nhiều, còn bot mạng xã hội hoặc tạo bản xem trước liên kết thường không render; vì thế thẻ Open Graph và nội dung được chèn phía client không đến được với chúng.

Googlebot thực sự xử lý ứng dụng Angular như thế nào

Googlebot là evergreen — nó render bằng phiên bản hiện hành của engine V8 trong Chrome và cập nhật cùng các bản phát hành Chrome. Evidence for this claim Googlebot uses an evergreen version of Chromium for rendering. Scope: Google Search rendering engine; this does not remove application-level rendering risks. Confidence: high · Verified: Google: Evergreen Googlebot Vì vậy nó có thể chạy Angular. Tuy nhiên, hành vi này có những giới hạn cứng cần tính đến khi thiết kế:

  • Render được xếp hàng, không diễn ra ngay. Google mô tả các giai đoạn crawl, render và lập chỉ mục riêng biệt nhưng không công bố thời gian cố định để render theo kịp crawl. Cách gọi tắt trong ngành là “hai làn sóng”: HTML thô được lập chỉ mục trước — với CSR Angular đó là shell trống — rồi DOM đã render được lập chỉ mục sau, khi lượt render chạy. SSR/prerendering tránh thời gian chờ này vì HTML hoàn chỉnh ngay ở lần fetch đầu tiên.
  • Renderer không lưu trạng thái. Không có cookie, localStorage hay sessionStorage được giữ giữa các lượt tải; renderer cũng từ chối yêu cầu quyền. Đừng khóa nội dung sau trạng thái phía client.
  • Nó không nhấp hoặc cuộn, và render với viewport rất cao. Nội dung nằm sau tương tác sẽ không được nhìn thấy.
  • Tài nguyên được cache mạnh — dùng tên tệp có fingerprint nội dung, mặc định Angular là main.<hash>.js, để bundle mới không bị phục vụ từ cache cũ.

@angular/ssr — bản chất và lịch sử tên gọi

Server-side rendering chạy Angular trên máy chủ Node cho từng request và trả về HTML đã render hoàn chỉnh. Sau đó trình duyệt hydrate — gắn event listener vào DOM hiện có thay vì render lại. Mọi crawler, từ Googlebot, Bingbot đến bot mạng xã hội, đều nhận HTML hoàn chỉnh ngay ở request đầu tiên mà không cần làn sóng thứ hai.

Tên gọi dễ gây nhầm nên cần nói chính xác: Angular Universal là giải pháp SSR trước đây, được phát hành dưới dạng package ngoài (@nguniversal/express-engine). Từ Angular v17 (tháng 11 năm 2023), SSR được tích hợp trực tiếp vào Angular CLI và Application Builder rồi đổi tên thành @angular/ssr; repository Angular Universal hiện ở chế độ bảo trì. Evidence for this claim Modern Angular documents @angular/ssr as its server-side and hybrid rendering package. Scope: Current Angular documentation; historical Angular Universal details are not required for this claim. Confidence: high · Verified: Angular: Server-side and hybrid rendering Thiết lập giờ chỉ cần một dòng:

# New project with SSR enabled
ng new my-app --ssr

# Add SSR to an existing project
ng add @angular/ssr

Cách này thay cho lệnh cũ ng add @nguniversal/express-engine. Ý tưởng không đổi nhưng công cụ giờ là thành phần chính thức.

Prerendering (tạo tĩnh / SSG)

Prerendering tạo HTML tĩnh cho các route tại thời điểm build, nên không có máy chủ chạy theo request và bạn có thể triển khai lên CDN. Nó mang lại TTFB/FCP/LCP nhanh nhất cùng chi phí vận hành thấp nhất. Từ v17+, bạn cấu hình theo từng route trong tệp server route (app.routes.server.ts) bằng RenderMode.Prerender; outputMode: 'static' tạo ứng dụng hoàn toàn tĩnh.

Giới hạn là dữ liệu phải có sẵn lúc build, không thể có nội dung riêng theo từng người dùng và website rất lớn sẽ build chậm. Cách này lý tưởng cho trang marketing, tài liệu và bài blog — chính những nội dung cần xếp hạng và được trích dẫn nhất.

Hybrid rendering — chọn chế độ theo từng route

Bước tiến lớn từ v17+ là chế độ render trở thành quyết định theo từng route, được cấu hình trong app.routes.server.ts:

  • RenderMode.Prerender — route tĩnh như trang chủ, trang giới thiệu và bài blog.
  • RenderMode.Server — route động theo từng request như kết quả tìm kiếm hoặc dashboard có dữ liệu mới.
  • RenderMode.Client — route chỉ dùng nội bộ mà bạn vốn không muốn lập chỉ mục, như giao diện quản trị hoặc màn hình yêu cầu đăng nhập. CSR hoàn toàn phù hợp ở đây.

Quy tắc quyết định trong thực tế: prerender nội dung tĩnh, server-render nội dung phải luôn mới và chỉ dùng client rendering cho những gì không nên xuất hiện trong chỉ mục.

Hydration — và sai lầm phá hỏng CLS

SSR đơn giản có một nhược điểm: máy chủ gửi HTML rồi trình duyệt loại bỏ và render lại từ đầu, gây nhấp nháy và lãng phí công việc. Hydration khắc phục điều đó: trình duyệt khôi phục ứng dụng đã render trên máy chủ, tái sử dụng DOM khớp thay vì hủy rồi tạo lại và chỉ nối phần tương tác. Bật nó trong app.config.ts:

provideClientHydration()

Điều kiện tiên quyết: hydration là phần bổ trợ phía client cho SSR, không phải công tắc độc lập. provideClientHydration() chỉ có tác dụng trên route đã được server-render hoặc prerender; nó không thể biến shell của route RenderMode.Client thành HTML từ máy chủ. Hãy bật SSR/prerendering cho route trước.

Hai khả năng mới hơn có ý nghĩa với trải nghiệm người dùng liên quan gián tiếp tới SEO; không khả năng nào thay đổi việc lập chỉ mục của Google Search:

  • Event replay (v18+): ghi lại các tương tác người dùng được hỗ trợ xảy ra trước khi hydration hoàn tất rồi phát lại sau đó. Nó giảm số lượt nhấp bị mất đối với các loại tương tác được hỗ trợ; đây là tính năng UX/tương tác, không phải tính năng SEO và không ảnh hưởng tới nội dung Google lập chỉ mục.
  • Incremental hydration (bản xem trước cho nhà phát triển ở v19, ổn định ở v20): phụ thuộc đồng thời vào SSR, hydration, deferrable views và event replay; nó không phải tính năng độc lập. Cơ chế này dùng các khối @defer với hydrate trigger để kiểm soát ranh giới nào còn chưa hydrate trong lần render ban đầu. Ranh giới hydrate never vẫn chưa hydrate ở lần tải đầu nhưng phần phụ thuộc của nó không nhất thiết bị chặn trong lần render phía client sau đó, chẳng hạn sau khi đổi route. Tác động liên quan SEO là gián tiếp: lượng JavaScript được hydrate ít hơn ban đầu có thể cải thiện LCP, nhưng cấu hình trigger là chi tiết render, không phải cơ chế định thời crawler.

Sai lầm quan trọng: đặt @if (isPlatformBrowser(...)) trực tiếp trong template khiến máy chủ và client render markup khác nhau — tạo hydration mismatch — dẫn đến layout shift và làm xấu CLS. Hãy dùng afterNextRender() cho công việc chỉ chạy trong trình duyệt thay vì rẽ nhánh template theo platform.

Tiêu đề và thẻ meta — các service tích hợp của Angular

Angular không thể bind trực tiếp vào text của phần tử <title>, nên bạn quản lý các thẻ trong head bằng hai service từ @angular/platform-browser:

  • TitlesetTitle() / getTitle().
  • MetaaddTag(), addTags(), updateTag(), getTag(), removeTag(), với selector như name='description' hoặc property='og:title'.

Bạn không cần thư viện bên thứ ba cho các nhu cầu cơ bản. Dùng một SEO service chung là mẫu triển khai gọn gàng:

@Injectable({ providedIn: 'root' })
export class SeoService {
  private title = inject(Title);
  private meta = inject(Meta);

  updatePage(title: string, description: string) {
    this.title.setTitle(title);
    this.meta.updateTag({ name: 'description', content: description });
    this.meta.updateTag({ property: 'og:title', content: title });
  }
}

Riêng với tiêu đề, TitleStrategy của router (Angular v14+) cho phép đặt title ngay trong cấu hình route để tiêu đề trang tự cập nhật khi điều hướng, không cần code riêng trong từng component. Đừng tự đặt document.title = ...; hãy dùng service Title để hoạt động đúng với SSR.

Cấu trúc URL

  • Dùng định tuyến HTML5 History API mặc định (PathLocationStrategy) để có URL gọn như /products/shoes. Cách này cần <base href="/"> trong index.html.
  • Không bao giờ dùng HashLocationStrategy / useHash: true cho nội dung công khai. Fragment sau # bị bỏ trước HTTP request nên máy chủ không nhìn thấy; Googlebot cũng không thể phân giải #/products đáng tin cậy và toàn bộ website có thể bị gộp thành một URL.
  • Dùng liên kết <a href> thực để điều hướng, kể cả tới route lazy-loaded. Lazy loading tốt cho hiệu năng, nhưng liên kết tới các route đó vẫn phải là anchor crawler đi theo được, không phải click handler.

Dữ liệu có cấu trúc (JSON-LD)

Google hỗ trợ chèn JSON-LD bằng JavaScript. Mẫu Angular vững chắc là một service tạo <script type="application/ld+json"> rồi thêm vào document.head, sử dụng injection token DOCUMENT của Angular thay vì truy cập biến document toàn cục — thao tác sẽ hỏng trên máy chủ. Giữ toàn bộ schema ở một nơi, không chia giữa HTML tĩnh và DOM đã render, rồi xác thực bằng Rich Results Test cùng URL Inspection.

Dynamic rendering — giải pháp tình thế cũ, không phải kế hoạch

Dynamic rendering gửi phiên bản prerender cho bot, thông qua Puppeteer, Rendertron hoặc prerender.io, còn gửi SPA đầy đủ cho người dùng. Google nói rõ rằng “dynamic rendering is a workaround and not a long-term solution,” (bản dịch) “dynamic rendering là giải pháp tình thế, không phải giải pháp dài hạn”, và có “better solutions than dynamic rendering” (bản dịch) “những giải pháp tốt hơn dynamic rendering” — cụ thể là server-side rendering, static rendering hoặc hydration. Đây không tự động là cloaking nếu hai phía nhận nội dung về cơ bản giống nhau, nhưng nó tạo thêm một máy chủ render phải vận hành, có nguy cơ lệch nội dung và không cải thiện Core Web Vitals cho người dùng thực. Với dự án Angular mới, hãy chọn @angular/ssr hoặc prerendering.

Các lỗi Angular SEO thường gặp

  1. Dùng định tuyến hash (URL có #) — toàn bộ website trông như một URL.
  2. Không gọi Title/Meta — mọi trang dùng chung tiêu đề và mô tả.
  3. Không có SSR/prerendering — nội dung chỉ tồn tại sau làn sóng render bị trì hoãn.
  4. Chặn .js/.css trong robots.txt — Google không render được và lập chỉ mục shell trống.
  5. Trả mã 200 cho trang không tìm thấy — tạo soft 404; hãy trả 404 thực hoặc thêm noindex.
  6. Dùng document.title = ... thay vì service Title.
  7. Đặt isPlatformBrowser() trong @if của template — gây hydration mismatch và CLS.
  8. Truy cập window/localStorage/document trong code chạy trên máy chủ — làm SSR lỗi.
  9. Chia schema giữa HTML thô và DOM đã render.
  10. Chỉ kiểm thử local thay vì dùng URL Inspection — dựa vào giả định chứ không dựa vào điều Googlebot thấy.

Kiểm thử Angular cho SEO

  • URL Inspection (Search Console) là nguồn xác thực chính: chế độ xem trang đã crawl hiển thị HTML đã render — DOM sau khi Googlebot chạy JavaScript và cũng là nội dung được lập chỉ mục — cùng thông báo console JS và tài nguyên bị chặn. Chạy Live Test để render theo yêu cầu.
  • Dùng curl với URL để xem HTML thô trước JavaScript; shell trống nghĩa là CSR không có SSR.
  • Rich Results Test xác thực dữ liệu có cấu trúc.
  • Ahrefs Site Audit khi bật render JS và Screaming Frog ở chế độ JS so sánh DOM thô với DOM đã render trên quy mô lớn.
  • Lighthouse / PageSpeed Insights đo tác động của lựa chọn render tới Core Web Vitals.

Angular không tệ cho SEO — nó chỉ khác. Hãy đưa HTML thực vào response, quản lý các thẻ trong head, giữ URL và liên kết cho phép crawl, rồi dùng chính công cụ của Google để phân xử nội dung đã render. Chủ đề này nằm cạnh JavaScript SEO và các câu hỏi về render headless CMS trong cụm bài; bài học nền tảng đều giống nhau: chế độ render quyết định gần như mọi thứ.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.