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.
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 — Theo mặc định, Angular dựng trang trong trình duyệt bằng JavaScript, vì vậy HTML thô mà công cụ tìm kiếm nhìn thấy ban đầu gần như trống. Cách khắc phục là gửi HTML thực đã hoàn thiện — bằng server-side rendering tích hợp của Angular (
@angular/ssr) hoặc prerendering. Sau đó xử lý các yếu tố cơ bản: tiêu đề và mô tả riêng cho từng trang, URL gọn gàng (không có#) và liên kết<a href>thực.
Vấn đề trong một câu
Một ứng dụng Angular mặc định gửi cho trình duyệt một HTML shell rất nhỏ — về cơ bản chỉ là một <div> trống — cùng một bundle JavaScript lớn. Trình duyệt chạy JavaScript đó rồi trang mới được điền nội dung. Đây gọi là client-side rendering (CSR).
Vấn đề là khi công cụ tìm kiếm tải URL đó lần đầu, nó chỉ thấy shell trống. Google có thể chạy JavaScript để xem nội dung thực, nhưng việc này diễn ra sau, ở một bước riêng và không phải lúc nào cũng đáng tin cậy. Evidence for this claim Google can render JavaScript but processes rendering as a stage after crawling. Scope: Google Search rendering; other crawler behavior is not covered by this record. Confidence: high · Verified: Google: JavaScript SEO basics Các crawler khác — Bing và bot tạo bản xem trước trên mạng xã hội — thường hoàn toàn không chạy được JavaScript. Vì vậy chúng không thấy nội dung.
Cách khắc phục: gửi HTML hoàn chỉnh
Thay vì buộc trình duyệt hoặc crawler dựng trang, hãy dựng trang trước tại thời điểm build hoặc trên máy chủ rồi gửi HTML hoàn chỉnh. Angular cung cấp hai cách chính:
- Server-side rendering (SSR) — máy chủ chạy Angular cho từng request và trả về toàn bộ trang. Phù hợp với nội dung thay đổi thường xuyên. Evidence for this claim Angular supports server-side rendering and build-time prerendering through its SSR tooling. Scope: Current Angular SSR and prerender features. Confidence: high · Verified: Angular: Server-side and hybrid rendering
- Prerendering — Angular tạo các tệp HTML tĩnh cho trang tại thời điểm build nên không cần máy chủ chạy theo request. Đây là lựa chọn nhanh nhất, rất phù hợp cho bài blog và trang marketing.
Angular hiện đại, từ phiên bản 17 trở lên, tích hợp cả hai cách vào bộ công cụ. Bạn thêm chúng bằng một lệnh: ng add @angular/ssr. Có thể bạn từng nghe tên cũ Angular Universal — cùng ý tưởng nhưng trước đây là add-on riêng. Angular đưa nó vào core ở v17 và đổi tên.
Các yếu tố cơ bản khác
- Đặt tiêu đề và mô tả riêng cho từng trang. Angular không tự làm việc này; bạn thiết lập trong code bằng các service
TitlevàMetatích hợp. Nếu không, mọi trang dùng chung một tiêu đề. - Dùng URL gọn gàng, không dùng URL hash. Router mặc định của Angular tạo URL như
/products/shoes. Tránh kiểu hash cũ (/#/products) vì công cụ tìm kiếm không thể xử lý đáng tin cậy phần sau#. - Dùng liên kết thực. Điều hướng phải dùng liên kết
<a href>thực, không phải nút hay click handler; nếu không Google không thể đi theo liên kết.
Điều nhiều người hiểu sai nhất
“Google không thể lập chỉ mục Angular” là một ngộ nhận — Google có thể render JavaScript. Nhưng cách đó chậm và kém tin cậy hơn việc gửi HTML thực, còn crawler ngoài Google có thể hoàn toàn không làm được. Vì vậy, với nội dung cần được tìm thấy, hãy render trên máy chủ hoặc prerender.
Muốn xem phiên bản chuyên sâu — hybrid rendering theo route, hydration, các service SEO kèm code và cách kiểm tra điều Google thực sự thấy? Hãy chuyển sang tab Nâng cao.
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 renderingTó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 serviceTitle/MetahoặcTitleStrategycủa router; định tuyến HTML5 History, không bao giờ dùngHashLocationStrategy; 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.
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:
- 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.
- 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.
- 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,
localStoragehaysessionStorageđượ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/ssrCá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
@defervớ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ớihydrate nevervẫ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:
Title—setTitle()/getTitle().Meta—addTag(),addTags(),updateTag(),getTag(),removeTag(), với selector nhưname='description'hoặcproperty='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="/">trongindex.html. - Không bao giờ dùng
HashLocationStrategy/useHash: truecho 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
- Dùng định tuyến hash (URL có
#) — toàn bộ website trông như một URL. - Không gọi
Title/Meta— mọi trang dùng chung tiêu đề và mô tả. - Không có SSR/prerendering — nội dung chỉ tồn tại sau làn sóng render bị trì hoãn.
- Chặn
.js/.csstrongrobots.txt— Google không render được và lập chỉ mục shell trống. - Trả mã
200cho trang không tìm thấy — tạo soft 404; hãy trả404thực hoặc thêmnoindex. - Dùng
document.title = ...thay vì serviceTitle. - Đặt
isPlatformBrowser()trong@ifcủa template — gây hydration mismatch và CLS. - Truy cập
window/localStorage/documenttrong code chạy trên máy chủ — làm SSR lỗi. - Chia schema giữa HTML thô và DOM đã render.
- 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
curlvớ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ứ.
Tóm tắt bằng AI
Bản cô đọng của phần Nâng cao:
- Mặc định của Angular là client-side rendering (CSR) — một HTML shell gần như trống. Hệ quả là lập chỉ mục chậm/kém tin cậy, LCP xấu hơn và crawler ngoài Google như Bing hay bot mạng xã hội không thấy nội dung.
- Googlebot là evergreen và render JS, nhưng việc render được xếp hàng theo mô hình “hai làn sóng” không có lịch cố định, renderer không lưu trạng thái, không nhấp/cuộn và cache tài nguyên mạnh. SSR/prerendering tránh lượt chờ vì HTML hoàn chỉnh ngay lần fetch đầu.
@angular/ssrlà giải pháp hiện đại — SSR tích hợp trong Angular CLI từ v17, thay package Angular Universal (@nguniversal/express-engine) do bên ngoài duy trì. Thiết lập bằngng new --ssrhoặcng add @angular/ssr.- Prerendering, tức HTML tĩnh lúc build, nhanh nhất và triển khai được trên CDN; phù hợp nội dung marketing/blog/tài liệu tĩnh nhưng chỉ dùng dữ liệu có sẵn lúc build.
- Hybrid rendering đặt chế độ theo route trong
app.routes.server.ts:RenderMode.Prerendercho nội dung tĩnh,RenderMode.Servercho nội dung động vàRenderMode.Clientcho trang nội bộ không lập chỉ mục. - Hydration (
provideClientHydration()) tái sử dụng HTML máy chủ trên route đã SSR/prerender; nó không tạo HTML máy chủ cho CSR. Event replay (v18+) ghi và phát lại tương tác được hỗ trợ trước hydration; incremental hydration (xem trước ở v19, ổn định ở v20) cần SSR, hydration, deferrable views và event replay, dùng hydrate trigger trong@deferđể kiểm soát ranh giới còn chưa hydrate ở lần tải đầu;hydrate neverkhông chặn lượt tải phía client về sau. Cả hai không thay đổi cách Google Search lập chỉ mục. TránhisPlatformBrowser()trong@ifcủa template vì gây hydration mismatch và CLS; dùngafterNextRender(). - Dùng service
TitlevàMetatích hợp;TitleStrategycủa router (v14+) tự đặt tiêu đề theo route. - Dùng định tuyến HTML5 History, không dùng URL hash (
#); điều hướng phải là anchor<a href>thực. Có thể chèn JSON-LD qua tokenDOCUMENT. - Dynamic rendering là giải pháp tình thế, không phải chiến lược; Google khuyến nghị SSR, static rendering hoặc hydration.
- Kiểm thử bằng URL Inspection,
curl, Rich Results Test và crawler render JS. AngularJS ≠ Angular; hướng dẫn AngularJS cũ không áp dụng.
Tài liệu chính thức
Tài liệu nguồn sơ cấp từ Google và Angular.
- Nắm các nguyên tắc cơ bản của JavaScript SEO — các giai đoạn crawl → render → lập chỉ mục, liên kết
<a href>crawl được, cảnh báo định tuyến fragment, soft 404s và dữ liệu có cấu trúc được chèn bằng JS. - Dynamic Rendering — giải pháp tình thế — vì sao đây chỉ là giải pháp tình thế, sắc thái về cloaking và các lựa chọn SSR/static/hydration.
- Render trên web (web.dev — Addy Osmani và Jason Miller) — định nghĩa chuẩn về SSR, CSR, static rendering, hydration và đánh đổi hiệu năng.
- Công cụ URL Inspection — cách xem HTML đã render mà Google thực sự lập chỉ mục cùng thông báo console JS.
Angular
- Angular — Server-side và hybrid rendering (SSR) — hướng dẫn chính thức về
@angular/ssr, server route vàRenderMode. - Angular — service
Title—setTitle()/getTitle(). - Angular — service
Meta—addTag(),updateTag()và selector. - Angular — tài liệu tham chiếu Router —
PathLocationStrategy, định tuyến hash vàTitleStrategy. - Giới thiệu Angular v17 — blog nhóm Angular — SSR trở thành tính năng hạng nhất trong CLI.
- Angular Universal — chế độ bảo trì — package lịch sử, nay được
@angular/ssrthay thế.
Trích dẫn từ nguồn
Các phát biểu chính thức từ Google và từ bài viết của tôi. Mỗi liên kết tới công cụ tìm kiếm là deep link dẫn thẳng tới đoạn được trích trên trang nguồn.
Google — cách xử lý ứng dụng JavaScript
- “Google processes JavaScript web apps in three main phases: 1. Crawling 2. Rendering 3. Indexing.” (bản dịch) “Google xử lý ứng dụng web JavaScript theo ba giai đoạn chính: 1. Thu thập dữ liệu 2. Render 3. Lập chỉ mục.” — Tài liệu Google Search Central. Đi tới trích dẫn
- “Google can only discover your links if they are <a> HTML elements with an href attribute.” (bản dịch) “Google chỉ có thể phát hiện liên kết nếu đó là phần tử HTML <a> có thuộc tính href.” — Tài liệu Google Search Central. Đi tới trích dẫn
Google — dynamic rendering là giải pháp tình thế
- “Dynamic rendering was a workaround and not a long-term solution for problems with JavaScript-generated content in search engines.” (bản dịch) “Dynamic rendering từng là giải pháp tình thế, không phải giải pháp dài hạn cho vấn đề nội dung do JavaScript tạo ra trong công cụ tìm kiếm.” — Tài liệu Google Search Central. Đi tới trích dẫn
web.dev — ưu tiên SSR / static rendering (Addy Osmani và Jason Miller)
- “Rendering an app on the server to send HTML, rather than JavaScript, to the client.” (bản dịch) “Render ứng dụng trên máy chủ để gửi HTML thay vì JavaScript tới client.” — định nghĩa server-side rendering. Đi tới trích dẫn
Patrick Stox (bài của tôi — JavaScript SEO: Hướng dẫn toàn diện)
- “JavaScript is not bad for SEO, and it’s not evil. It’s just different from what many SEOs are used to.” (bản dịch) “JavaScript không tệ cho SEO và cũng không xấu; nó chỉ khác điều nhiều người làm SEO quen thuộc.”
- “Any kind of SSR, static rendering, and prerendering setup is going to be fine for search engines.” (bản dịch) “Mọi cách thiết lập SSR, static rendering và prerendering đều sẽ phù hợp với công cụ tìm kiếm.”
Checklist Angular SEO
Kiểm tra nhanh để xác nhận ứng dụng Angular có thể được crawl và lập chỉ mục:
- Nội dung công khai được phục vụ dưới dạng HTML thực qua
@angular/ssr(SSR) hoặc prerendering, không phải CSR hoàn toàn. - Chế độ render được đặt theo từng route trong
app.routes.server.ts: prerender route tĩnh, server-render route động, chỉ client-render trang nội bộ không lập chỉ mục. - Đã bật hydration (
provideClientHydration()) và không template nào dùngisPlatformBrowser()trong@if; thay vào đó dùngafterNextRender(). - Mỗi trang đặt tiêu đề riêng bằng service
TitlehoặcTitleStrategy, và mô tả riêng bằng serviceMeta. - Thẻ Open Graph / Twitter Card được đặt bằng service
Metacho bản xem trước mạng xã hội. - Router dùng HTML5 History API mặc định với
<base href="/">, không dùngHashLocationStrategy/useHash: true. - Mọi điều hướng dùng anchor
<a href>thực, kể cả liên kết tới route lazy-loaded. -
robots.txtkhông chặn tài nguyên.jshoặc.css. - Trang không tìm thấy phía client trả mã
404thực hoặc cónoindex, tránh soft 404. - Code chạy trên máy chủ không truy cập
window/localStorage/document; dùng tokenDOCUMENThoặc platform guard. - JSON-LD nằm ở một nơi và vượt qua Rich Results Test.
- Bạn đã xác minh HTML đã render trong URL Inspection, không chỉ ở môi trường phát triển local.
Các mô hình tư duy
1. Đưa HTML thực vào response. Gần như mọi vấn đề Angular SEO quy về một câu hỏi: crawler nhận HTML hoàn chỉnh ngay lần fetch đầu hay nhận shell mà nó phải render? SSR và prerendering trả lời “có”. CSR trả lời “cuối cùng thì có thể”. Hãy bắt đầu mọi cuộc audit tại đây.
2. Quy tắc chọn chế độ render theo route.
- Nội dung tĩnh như trang chủ, giới thiệu, blog, tài liệu →
RenderMode.Prerender. - Nội dung động phải luôn mới như tìm kiếm hoặc dữ liệu trực tiếp →
RenderMode.Server. - Trang nội bộ/yêu cầu đăng nhập mà bạn không muốn lập chỉ mục →
RenderMode.Clientlà phù hợp.
3. “Angular Universal” và “@angular/ssr” là cùng ý tưởng ở hai thời kỳ.
Universal là package ngoài; v17 đưa SSR vào CLI và đổi tên. Với Angular hiện đại, hãy dùng @angular/ssr; repository Universal đang ở chế độ bảo trì.
4. Hydrate, đừng render lại.
SSR đơn giản gửi HTML rồi loại bỏ nó. provideClientHydration() tái sử dụng HTML đó, nhưng chỉ trên route đã SSR/prerender; nó không tạo HTML máy chủ cho route CSR. Event replay và incremental hydration (@defer) giảm JavaScript ban đầu. Không bao giờ rẽ nhánh template bằng isPlatformBrowser(); đó là cách tạo hydration mismatch và làm xấu CLS.
5. Bạn phải tự quản lý thẻ trong head, Angular không làm thay.
Ở đây không có Yoast. Hãy chủ động đặt tiêu đề và mô tả theo từng trang bằng service Title/Meta hoặc TitleStrategy. “Mọi trang dùng chung một tiêu đề” là lỗi mặc định, không phải vận rủi.
6. Thiết kế cho bot không lưu trạng thái trên URL gọn.
Dùng định tuyến History API, liên kết <a href> thực, không phụ thuộc cookie/storage và không giấu nội dung sau lượt nhấp. Sau đó để URL Inspection, không phải laptop của bạn, cho biết nội dung đã render.
Angular SEO — bảng tra nhanh
Chế độ render
| Chế độ | HTML được tạo ở đâu | SEO | Phù hợp nhất | Cấu hình Angular |
|---|---|---|---|---|
| Prerender (SSG) | Lúc build → tệp tĩnh | ✅ Tốt nhất | Marketing/blog/tài liệu tĩnh | RenderMode.Prerender |
| SSR | Máy chủ, theo request | ✅ Rất tốt | Nội dung động phải luôn mới | RenderMode.Server / @angular/ssr |
| Client (CSR) | Trong trình duyệt | ⚠️ Rủi ro | Trang nội bộ/đăng nhập, không lập chỉ mục | RenderMode.Client |
| Dynamic rendering | Máy chủ riêng cho bot | Chỉ là giải pháp tình thế | Ứng dụng cũ chưa thể di chuyển | Puppeteer / Rendertron / prerender.io |
Lệnh thiết lập
| Mục tiêu | Lệnh |
|---|---|
| Dự án mới có SSR | ng new my-app --ssr |
| Thêm SSR vào ứng dụng hiện có | ng add @angular/ssr |
| Bật hydration | provideClientHydration() trong app.config.ts |
Quy tắc nhanh cho head và router
- Tiêu đề/mô tả: service
TitlevàMetacủa Angular, không cần thư viện bên thứ ba. - Tự đặt tiêu đề theo route:
TitleStrategycủa router (v14+) qua thuộc tínhtitlecủa route. - Định tuyến: HTML5 History API cùng
<base href="/">. Không bao giờ dùnguseHash: true. - Liên kết: anchor
<a href>thực, kể cả tới route lazy-loaded. - JSON-LD: chèn qua token
DOCUMENTvà giữ ở một nơi.
Tên gọi
- Angular Universal = package ngoài cũ (
@nguniversal/express-engine), đang ở chế độ bảo trì. @angular/ssr= cùng cơ chế SSR, tích hợp vào CLI từ v17.- AngularJS (v1.x) ≠ Angular (v2+) — hai framework khác nhau; hướng dẫn AngularJS cũ không áp dụng.
Điểm dễ vấp
isPlatformBrowser()trong@ifcủa template → hydration mismatch → CLS. DùngafterNextRender().window/localStorage/documenttrên máy chủ → SSR lỗi.- Chặn
.js/.csstrong robots.txt → Google không render được.
Kiểm tra ứng dụng Angular có thực sự được server-render hay không
Cách nhanh nhất để biết một URL dùng CSR hay SSR/prerendering là tải HTML thô trước khi JavaScript chạy và tìm nội dung thực. Ứng dụng Angular CSR trả về <app-root> gần như trống; phiên bản SSR/prerender trả về markup hoàn chỉnh.
macOS / Linux
# Raw HTML as the server sends it — this is the "first fetch", pre-JS
curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/page/ -o raw.html
# Is your real headline in the raw HTML? Empty result = CSR with no SSR
grep -o "Your headline text" raw.html
# A near-empty <app-root> is the tell-tale CSR signature
grep -o "<app-root></app-root>" raw.htmlWindows (PowerShell)
$ua = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
Invoke-WebRequest -Uri "https://example.com/page/" -UserAgent $ua -OutFile raw.html
Select-String -Path raw.html -Pattern "Your headline text"
Select-String -Path raw.html -Pattern "<app-root></app-root>"Nếu thiếu heading và bạn thấy <app-root> trống, nội dung đang phụ thuộc vào render; hãy thêm SSR hoặc prerendering. Để xem DOM đã render, dùng “View Crawled Page → rendered HTML” trong URL Inspection; curl thông thường không chạy được JS.
Xác nhận bạn không chặn JS/CSS của Angular trong robots.txt
macOS / Linux
curl -sL https://example.com/robots.txt | grep -iE "disallow.*\.(js|css)|Disallow:\s*/(assets|dist|main)"Nếu một quy tắc Disallow khớp bundle của bạn, Google không thể render trang đúng cách; đây gần như luôn là lỗi.
Công cụ gỡ lỗi Angular SEO
- URL Inspection (Google Search Console) — nguồn xác thực chính. Chạy Live Test, rồi đọc HTML đã render, tức DOM sau khi Googlebot chạy Angular JS; xem thêm ảnh chụp, tài nguyên trang đã tải hoặc bị chặn và thông báo console JavaScript.
- Rich Results Test — xác nhận JSON-LD xuất hiện trong output đã render sau mọi thay đổi về render.
curl— tải HTML thô trước JS để phân biệt CSR có<app-root>trống với SSR/prerendering.- Ahrefs Site Audit khi bật render JS — crawl bằng headless Chrome, so sánh DOM thô với DOM đã render và phát hiện metadata thiếu, canonical hỏng cùng vấn đề indexability trên quy mô lớn.
- Screaming Frog SEO Spider ở chế độ render JS — so sánh nội dung thô với nội dung đã render theo từng URL.
- Lighthouse / PageSpeed Insights — đo tác động của chiến lược CSR, SSR hoặc prerender tới Core Web Vitals.
- Angular CLI / DevTools — xác nhận
outputMode, server route và hydration được cấu hình đúng như dự kiến.
Những tài nguyên đáng dành thời gian
Bài viết liên quan của tôi
- JavaScript SEO: Hướng dẫn toàn diện — hướng dẫn đầy đủ của tôi về render, tính tương đương DOM, quy tắc directive hạn chế nhất và các cấu hình render an toàn. Angular SEO là một ứng dụng cụ thể của toàn bộ nội dung đó.
- Hướng dẫn Technical SEO cho người mới — vị trí của render và crawl trong bức tranh lớn.
Bài thuyết trình của tôi
- JavaScript SEO — Ungagged 2019 (SlideShare) — hành vi render không lưu trạng thái, viewport và cache của Googlebot cùng các cách render thời đó. Lưu ý thường trực: khuyến nghị dynamic rendering trong slide nay đã lỗi thời; Google sau đó gọi nó là giải pháp tình thế.
Từ ngành
- Render trên web (web.dev) — bài chuẩn của Addy Osmani và Jason Miller về SSR, CSR, static rendering và các đánh đổi của hydration.
- Server-side và hybrid rendering (SSR) (angular.dev) — hướng dẫn
@angular/ssrchính thức: server route,RenderMode, prerendering và hydration. - Giới thiệu Angular v17 (blog nhóm Angular) — bản phát hành đưa SSR thành tính năng hạng nhất trong CLI và giới thiệu package
@angular/ssr. - Angular Universal — chế độ bảo trì (GitHub) — package SSR lịch sử được
@angular/ssrthay thế, hữu ích để hiểu việc đổi tên. - Hướng dẫn Angular SEO (Search Engine Journal, Jamie Indigo) — hướng dẫn kinh điển về lập chỉ mục hai làn sóng; nền tảng vững nhưng có trước lần đổi tên ở v17.
- Hướng dẫn Angular SSR (Angular Architects, Alexander Thalhammer, tháng 3 năm 2025) — hướng dẫn thiết lập SSR nhiều code; hãy đối chiếu mẫu code với tài liệu
@angular/ssrchính thức để phát hiện thay đổi API sau khi xuất bản. - r/TechSEO — cộng đồng về gỡ lỗi render và lập chỉ mục.
Các anti-pattern Angular SEO
Những lỗi cụ thể tôi thường xuyên thấy trong ứng dụng Angular; mỗi lỗi là một thói quen cần kiểm tra trực tiếp, không chỉ là rủi ro lý thuyết.
Phát hành bản build chỉ có CSR rồi coi như hoàn tất
Output mặc định của ng new không có SSR hoặc prerendering. Đây là cách bắt đầu dự án nhanh nhất nhưng cũng dễ dẫn tới <app-root> trống ở lần fetch đầu. Vì sao sai: HTML thô crawler nhìn thấy không có nội dung, nên việc lập chỉ mục phụ thuộc hoàn toàn vào lượt render trì hoãn của Google; bot khác không có cơ hội thứ hai. Cách làm đúng: thêm @angular/ssr ngay từ đầu bằng ng new my-app --ssr, hoặc chạy ng add @angular/ssr trên dự án hiện có trước khi phát hành bất kỳ nội dung nào cần được tìm thấy.
Dùng HashLocationStrategy cho route công khai
Định tuyến hash (useHash: true, URL như /#/products/shoes) vẫn là mặc định trong một số hướng dẫn và boilerplate Angular cũ. Vì sao sai: mọi thứ sau # bị loại ở phía client trước khi request tới máy chủ, nên máy chủ và Googlebot chỉ thấy một URL cho toàn ứng dụng. Cách làm đúng: dùng định tuyến HTML5 History API mặc định (PathLocationStrategy) với <base href="/"> trong index.html.
Rẽ nhánh template bằng isPlatformBrowser()
Bọc nội dung trong @if (isPlatformBrowser(platformId)) có vẻ là cách hiển nhiên để bảo vệ code chỉ dành cho trình duyệt. Vì sao sai: máy chủ render một nhánh còn client render nhánh kia khi hydration, tạo hydration mismatch. Angular phải hòa giải khác biệt và kết quả nhìn thấy là layout shift được ghi nhận trong CLS. Cách làm đúng: dùng afterNextRender() cho công việc chỉ chạy trong trình duyệt để chính template render giống hệt trên máy chủ và client.
Đặt trực tiếp document.title thay vì dùng service Title
Cách này hoạt động khi phát triển local nên dễ trở thành lối tắt. Vì sao sai: truy cập DOM trực tiếp như document.title = '...' không tương thích tốt với SSR; máy chủ không có biến document toàn cục theo cùng nghĩa với trình duyệt, và bạn mất lợi ích tích hợp router từ cơ chế tiêu đề của Angular. Cách làm đúng: inject service Title của Angular và gọi setTitle(), hoặc cấu hình tiêu đề theo route bằng TitleStrategy.
Truy cập window, localStorage hoặc document trong code chạy khi SSR
Component hoặc service đọc localStorage hay kiểm tra window.innerWidth lúc khởi tạo hoạt động bình thường trong trình duyệt nhưng làm server render lỗi. Vì sao sai: các biến toàn cục đó không tồn tại trong tiến trình máy chủ Node chạy bản build SSR, nên render ném lỗi và request trả lỗi máy chủ hoặc âm thầm rơi về response trống. Cách làm đúng: đặt code đó sau afterNextRender(), hoặc inject token DOCUMENT thay cho biến toàn cục; kiểm thử bản build SSR tại local bằng ng build rồi phục vụ output SSR, không chỉ dùng ng serve.
Coi dynamic rendering là giải pháp lâu dài
Dựng Puppeteer hoặc dịch vụ như Rendertron để gửi snapshot prerender cho bot giải quyết triệu chứng tức thời. Vì sao sai: đây là một hệ thống bổ sung phải duy trì, có thể lệch khỏi nội dung người dùng thực nhìn thấy, và Google nói thẳng rằng nó chỉ là giải pháp tình thế chứ không phải giải pháp dài hạn. Cách làm đúng: chuyển sang @angular/ssr hoặc prerendering để mọi bên yêu cầu — bot hay người — nhận cùng HTML thực từ cùng pipeline.
Route này nên dùng chế độ render nào?
Angular v17+ cho phép đặt chế độ render theo từng route trong app.routes.server.ts. Câu hỏi không phải “SSR hay prerender cho toàn ứng dụng”, mà phải được hỏi riêng cho từng route.
Choosing a rendering mode for an Angular route
Prompt cho công việc Angular SEO
Các prompt có thể sao chép cho những tác vụ Angular SEO cụ thể trong bài. Hãy dán input được mô tả và kiểm tra output bằng phán đoán của chính bạn; chúng tiết kiệm thời gian cho phần cơ học, không thay thế kiểm thử bằng URL Inspection.
1. So sánh HTML thô với HTML đã render cho một route
Dán output của curl -sL <url> (HTML thô) và panel “rendered HTML” từ URL Inspection Live Test — hoặc crawl render JS bằng Ahrefs/Screaming Frog — cho cùng một URL.
Here is the raw HTML for [URL] (fetched with curl, before JavaScript runs):
[paste raw HTML]
Here is the rendered HTML for the same URL (from Google Search Console URL
Inspection's Live Test, or a JS-rendering crawler):
[paste rendered HTML]
Compare the two. List: (1) content present in rendered but missing from raw —
this is what depends on client-side rendering, (2) any <title>, meta description,
or JSON-LD that differs between the two versions, (3) whether the raw HTML shows
a near-empty <app-root> (a sign of CSR with no SSR/prerendering).Kết quả mong đợi là danh sách ngắn những phần phụ thuộc CSR cùng mọi sai lệch tiêu đề/meta/schema giữa bản thô và bản đã render — hai nhóm đáng sửa trước.
2. Review cách triển khai service Title/Meta
Dán SeoService Angular của bạn, hoặc thành phần tương đương, có gọi các service Title và Meta.
Here is an Angular service that sets page titles and meta tags:
[paste the service's TypeScript code]
Check it against these rules: (1) titles are set via the Title service's
setTitle(), never document.title directly, (2) description is set with
meta.updateTag({ name: 'description', ... }) not addTag() (which can duplicate
the tag on repeat calls), (3) Open Graph tags use the property selector, not
name, (4) nothing in this code reads window/localStorage/document directly in a
way that would break during SSR. Flag any line that violates one of these and
suggest the fix.Kết quả mong đợi là đánh giá đạt/không đạt theo từng dòng dựa trên bốn quy tắc đó, kèm đoạn code đã sửa cho mọi mục bị gắn cờ.
3. Audit app.routes.server.ts để tìm lỗi chế độ render
Dán tệp cấu hình server route của bạn.
Here is my Angular app.routes.server.ts, which sets RenderMode per route:
[paste the file]
For each route, tell me: is RenderMode.Prerender used on anything that depends
on per-request or per-user data (a mistake — it would bake stale/wrong data into
the static build)? Is RenderMode.Client used on anything that looks like public,
indexable content (a missed-SEO-opportunity)? Is RenderMode.Server used on fully
static content where Prerender would be faster and cheaper? List each route with
its current mode and whether it matches the decision rule: static data →
Prerender, must-be-fresh → Server, non-indexed internal → Client.Kết quả mong đợi là phán quyết theo từng route, gắn cờ route có RenderMode không khớp nhu cầu thực tế.
Tự kiểm tra: Angular SEO
Năm câu hỏi nhanh về cách làm ứng dụng Angular crawl và lập chỉ mục được. Chọn một đáp án cho mỗi câu rồi kiểm tra kết quả.
Nhật ký thay đổi
Đã cập nhật 22 thg 8, 2026.
Tóm tắt biên tập và chi tiết thay đổi đã ghi nhận.Chi tiết thay đổi
-
Ghi chú thay đổi chi tiết hiện chỉ có bằng tiếng Anh.
Không thể so sánh đầy đủ — không có bản lưu trước đó cho lần sửa đổi này.
Đã cập nhật 9 thg 8, 2026.
Tóm tắt biên tập và chi tiết thay đổi đã ghi nhận.Chi tiết thay đổi
-
Ghi chú thay đổi chi tiết hiện chỉ có bằng tiếng Anh.
Không thể so sánh đầy đủ — không có bản lưu trước đó cho lần sửa đổi này.
Đã cập nhật 17 thg 7, 2026.
Tóm tắt biên tập và chi tiết thay đổi đã ghi nhận.Chi tiết thay đổi
-
Ghi chú thay đổi chi tiết hiện chỉ có bằng tiếng Anh.
-
Ghi chú thay đổi chi tiết hiện chỉ có bằng tiếng Anh.
-
Ghi chú thay đổi chi tiết hiện chỉ có bằng tiếng Anh.
Không thể so sánh đầy đủ — không có bản lưu trước đó cho lần sửa đổi này.