Hướng dẫn về SPA SEO
Cách làm single-trang applications (React Router, Vue Router, Angular Router) crawlable và indexable — đó app-shell và soft-404 các vấn đề, History API so với hash routing, theo-route HTML qua SSR/prerendering, theo-route canonicals và các tiêu đề, và sitemap generation cho client-side routes.
Ngôn ngữ
MỘT single-trang application loads một document và swaps views với JavaScript thay vì requesting một new trang từ đó máy chủ. Nhiều SPAs implement đó với an 'app shell' — đó máy chủ trả về một URL's worth of real HTML, một near-empty shell, until JS renders mỗi route — nhưng đó là một phổ biến implementation lựa chọn, không một rule mỗi SPA follows; kiểm tra điều gì một trực tiếp yêu cầu để mỗi route thực ra trả về trước assuming điều này. Đó cách sửa, nơi một bare app-shell setup là đó vấn đề, có hai independent halves: addressability (History API, không hash/#! fragments, so mỗi view có một real URL — một trình duyệt soft navigation qua đó History API thay đổi đó URL và UI nhưng không itself tạo một new máy chủ phản hồi) và nội dung availability (SSR, prerendering, hoặc một meta-framework so mỗi of những URLs có thể trả về unique HTML on yêu cầu). On top of đó, verify mỗi route tiêu đề, canonical, và robots state cả hai on trực tiếp entry và sau kết xuất, xử lý soft 404s (routers tend để giữ một 200 status cho 'không tìm thấy' views — chuyển hướng để một URL đó itself 404s, hoặc thêm một được kết xuất noindex, though an ban đầu noindex có thể nguyên nhân kết xuất để là skipped), và hãy bảo đảm mỗi route trong của bạn sitemap thực ra resolves để real, indexable HTML — có không special SPA sitemap format.
Tóm tắt — single-trang application (SPA) loads một trang từ máy chủ và sau đó dùng JavaScript để chuyển “các trang” không có đầy đủ reload. catch: nhiều SPAs gửi giống nhau near-rỗng starting trang không quan trọng mà URL công cụ tìm kiếm asks cho — đó phổ biến risk của Cách SPAs là thường được xây dựng, không bảo đảm của mỗi SPA. để khắc phục nó, hãy đảm bảo mỗi route có của nó own thực URL và của nó own thực HTML — thường by kết xuất các trang on máy chủ hoặc pre-building them.
Điều gì SPA là
single-trang application commonly cập nhật client-side views và routes không có đầy đủ document navigation. Evidence for this claim Primary standard or official documentation supporting the adjacent article claim. Scope: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Confidence: high · Verified: MDN: Single-page application Google có thể render JavaScript SPAs, nhưng crawlable các URL, links, status xử lý, và được kết xuất nội dung vẫn necessary. Evidence for this claim Primary standard or official documentation supporting the adjacent article claim. Scope: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Confidence: high · Verified: Google: JavaScript SEO basics
single-trang application là trang web được xây dựng as một HTML trang. Khi bạn nhấp khoảng — từ các sản phẩm trang để về trang — JavaScript swaps out Điều gì on screen thay vì asking máy chủ cho toàn bộ new trang. React (với React Router), Vue (với Vue Router), và Angular all hoạt động điều này way theo mặc định. nó feels fast và app-như, mà là Vì sao nó popular.
risk là Điều gì máy chủ gửi. nhiều SPAs sử dụng “app shell” implementation:
đầu tiên time anyone (including Google) loads app, máy chủ trả về
mostly-rỗng shell, và nội dung thực nhận được xây dựng trong trình duyệt afterward. đó
một phổ biến way để xây dựng SPA — không definition của một, và không điều gì đó mỗi
SPA làm. nhưng Khi trang web làm ship bare app shell, thất bại chế độ là thực: nếu
Google asks máy chủ cho /about và /products riêng, nó có thể nhận chính xác
giống nhau blank shell cho cả hai, vì không có gì về route changed Điều gì máy chủ
được gửi. way để know liệu của bạn app là trong đó position là để kiểm tra Điều gì trực tiếp
yêu cầu để mỗi route thực ra trả về — không để assume nó từ fact đó nó
SPA.
Vì sao đó hurts SEO
các công cụ tìm kiếm cần để see của bạn nội dung để xếp hạng nó. với bare SPA, three điều tend để go sai:
- mỗi URL looks giống nhau để máy chủ. Deep links, chia sẻ, và các crawler all land on giống nhau shell.
- 404 các trang vẫn chẳng hạn “200 OK.” JavaScript router có thể hiển thị “không tìm thấy” screen trong khi trang technically các báo cáo thành công, so Google có thể chỉ mục rỗng các trang.
- ** sai các URL.** Older SPAs được sử dụng addresses như
example.com/#/products. Google có thể’t reliably chỉ mục những điều đó.
Cách khắc phục nó ( ngắn version)
- Cho mỗi view thực URL sử dụng trình duyệt History API (sạch paths như
/products), không#-based ones. - gửi thực HTML cho mỗi URL. Render các trang on máy chủ (SSR) hoặc pre-xây dựng them ahead của time (prerendering). nếu bạn’re starting fresh, framework đó làm điều này cho bạn — tiếp theo.js, Nuxt, SvelteKit, Angular SSR — saves bạn trouble.
- Cho mỗi trang của nó own tiêu đề và mô tả đó thay đổi Khi route thay đổi.
- Làm links thực links (
<a href>), không clickable<div>s.
Muốn đó mechanics — vì sao hash routing fails, đó “most restrictive directive” (bản dịch) «hầu hết restrictive directive» trap với canonicals, và cách generate một sitemap cho client-side routes? Chuyển để đó Advanced tab.
Tóm tắt — SPA cốt lõi SEO risk không phải “JavaScript” trong abstract — nó đó client-side routing thay đổi Điều gì người dùng sees không có thay đổi Điều gì máy chủ sẽ trả về. khắc phục có hai independent halves mọi người constantly conflate: addressability (History API, unique theo-route các URL, không
#!fragments) và nội dung availability (SSR, prerendering/SSG, hoặc meta-framework). Làm chỉ đầu tiên và bạn nhận tidy sitemap của các URL đó all render giống nhau shell. bên cạnh cả hai, mỗi route cần của nó own canonical/tiêu đề/mô tả trong được kết xuất DOM, bạn có để xử lý soft 404s đó giữ200, và mỗi route trong sitemap có để independently resolve để thực HTML.
Điều gì SPA SEO thực ra là
SPA là application architecture, không guaranteed SEO thất bại state. Evidence for this claim Primary standard or official documentation supporting the adjacent article claim. Scope: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Confidence: high · Verified: MDN: Single-page application máy chủ kết xuất, prerendering, hoặc cẩn thận implemented client kết xuất có thể expose nội dung, nhưng none bảo đảm lập chỉ mục. Evidence for this claim Primary standard or official documentation supporting the adjacent article claim. Scope: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Confidence: high · Verified: Google: JavaScript SEO basics
Đây là deep dive on single-trang applications cụ thể — client-side routing với React Router, Vue Router, hoặc Angular Router, nơi sau khi đầu tiên load máy chủ không bao giờ gửi một đầy đủ trang. nó sits alongside rộng hơn JavaScript SEO hướng dẫn (mà covers parity, lazy-loading, infinite scroll, và JS kết xuất generally) và theo-framework ghi-ups cho React, tiếp theo.js, Nuxt, Angular, Vue, Svelte, và Astro. Ở đây I’m chỉ interested trong routing layer và Điều gì nó làm để crawling và lập chỉ mục.
Vì sao bare client-side-routed SPAs fail tại SEO
Một URL, một HTML phản hồi — app-shell vấn đề
Google mô tả đó chế độ lỗi precisely: “Some JavaScript sites may use the app
shell model where the initial HTML does not contain the actual content and Google needs
to execute JavaScript before being able to see the actual page content.” (bản dịch) «Some JavaScript các trang có thể dùng đó app shell model nơi đó ban đầu HTML không contain đó thực tế nội dung và Google cần để execute JavaScript trước đang able để see đó thực tế trang nội dung.» Trong một bare
SPA, đó “app shell” là all đó máy chủ bao giờ trả về. Fetch /products fresh và bạn
nhận đó giống nhau near-empty document bạn’d nhận cho /about. Đó nội dung chỉ diverges
sau đó trình duyệt chạy của bạn JavaScript và của bạn router decides điều cần cho thấy.
đó toàn bộ vấn đề trong một sentence: client-side routing thay đổi Điều gì người dùng sees không có thay đổi Điều gì máy chủ sẽ trả về. Mọi thứ downstream — soft 404s, duplicate nội dung, bị thiếu các tiêu đề — là symptom của đó.
máy chủ không bao giờ sees mà “trang” là requested (Vì sao hash routing fails)
Older SPAs load views từ URL fragments — example.com/#/products. Google chính xác
words: “A SPA may use URL fragments (for example https://example.com/#/products) for
loading different views.” (bản dịch) «MỘT SPA có thể dùng URL fragments (ví dụ https://example.com/#/products) cho loading khác nhau views. » Này fails cho một cụ thể, mechanical reason: các trình duyệt
không bao giờ gửi đó fragment (bất cứ điều gì sau #) để đó máy chủ trong đó HTTP yêu cầu. Đó
máy chủ theo nghĩa đen không thể know mà “trang” đã là requested, so điều này không thể trả về khác nhau
nội dung hoặc một khác nhau mã trạng thái cho điều này. MỘT crawler đó fetches đó URL khi nhận
giống hệt HTML regardless of đó fragment.
đó là cũng vì sao Google formally deprecated của nó 2009 AJAX-crawling scheme lại trong
October 2015: “In short: We are no longer recommending the AJAX crawling proposal we
made back in 2009.” (bản dịch) «Tóm lại: We là không lâu hơn recommending đó AJAX crawling proposal we đã làm lại trong 2009.» Đó old _escaped_fragment_ workaround let các máy chủ pre-render
fragment routes on yêu cầu — nhưng điều này patched khoảng đó routing vấn đề thay vì
sửa điều này. Google own khuyến nghị thay thế điều này là đó History API, covered dưới.
Soft 404s — client-side routers giữ 200 cho mọi thứ
Này một là close để inevitable trong một bare SPA. Client-side routers, by design, giữ đó
original trang 200 status cho mỗi virtual navigation — including “không tìm thấy”
trạng thái. Google flags điều này explicitly: “In a single-page application (SPA), this can be
especially difficult. To prevent error pages from being indexed, you can use one or both
of the following strategies.” (bản dịch) «Trong một single-trang application (SPA), này có thể là especially difficult. Để ngăn lỗi các trang từ đang được lập chỉ mục, bạn có thể dùng một hoặc cả hai of đó sau strategies.» Và đó vì sao: “When a SPA is using client-side JavaScript
to handle errors they often report a 200 HTTP status code instead of the appropriate
status code.” (bản dịch) «Khi một SPA là dùng client-side JavaScript để xử lý các lỗi they thường báo cáo một HTTP mã trạng thái thay vì đó appropriate mã trạng thái.»
kết quả là rỗng hoặc lỗi views getting được lập chỉ mục as thin 200 các trang. Google
documents chính xác hai strategies ở đây, precisely: chuyển hướng (hoặc làm đầy đủ yêu cầu) để
URL whose máy chủ trả về thực 404/lỗi status, hoặc thêm noindex tag để
lỗi view với JavaScript. Watch thứ hai một: nếu đó noindex là present từ
rất đầu tiên paint thay vì đã thêm sau khi app decides route là không hợp lệ, nó có thể
nguyên nhân Google để skip kết xuất trang altogether — so verify Điều gì trực tiếp yêu cầu
trả về trước khi bất kỳ JS chạy, không chỉ Điều gì hiển thị lên trong được kết xuất DOM afterward.
Shared-shell duplicates — có thể nguyên nhân, không tự động diagnosis
có một second, sneakier chế độ lỗi: distinct routes nhận được lập chỉ mục as duplicates of mỗi other vì they render xuống để đó giống nhau shared header/nav/footer boilerplate. Gary Illyes described một way này happens: “I have a bunch of emails in my inbox where the issue is that the centerpiece took forever to load, so rendering timed out (my most likely explanation) and we were left with a bunch of pages that only had the boilerplate. With only the boilerplate, those pages are dups.” (bản dịch) «I có một bunch of emails trong my inbox nơi đó vấn đề là đó centerpiece took forever để load, so kết xuất timed out (my hầu hết có khả năng lời giải thích) và we đã là left với một bunch of các trang đó chỉ đã có đó boilerplate. Với chỉ đó boilerplate, những các trang là dups.» His cách sửa: “Try to restructure the js calls such that the content (including marginal boilerplate) loads first.” (bản dịch) «Try để restructure đó js calls such đó nội dung (including marginal boilerplate) loads đầu tiên.» (Relayed qua một LinkedIn post, mà resists automated verification; treat as cao-confidence phụ.)
Notice Illyes frames điều này as “my most likely explanation,” (bản dịch) «my hầu hết có khả năng lời giải thích,» không một confirmed diagnosis — đó là worth taking theo nghĩa đen. “Render timeout” (bản dịch) «Render timeout» không điều gì đó bạn có thể diagnose từ symptoms alone; bạn cần yêu cầu-cấp độ evidence (điều gì một trực tiếp fetch thực ra trả về), được kết xuất-output evidence (liệu đó centerpiece nội dung là present sau kết xuất), và Search Console evidence (duplicate/các tín hiệu lập chỉ mục) trước attributing một route duplicate-nội dung vấn đề để một timeout cụ thể, thay vì để một bug, một blocked tài nguyên, hoặc một genuinely giống hệt shell. có cũng không fixed, published timeout để design khoảng — Google own basics doc says một trang “may stay on this queue for a few seconds, but it can take longer than that,” (bản dịch) «có thể stay on này queue cho vài seconds, nhưng điều này có thể take lâu hơn đó,» không có committing để một number. không plan khoảng an assumed render-queue duration; thay vì, hãy bảo đảm chính nội dung loads as sớm as có thể regardless of cách dài kết xuất ends lên taking.
khắc phục: nhận thực HTML theo route
đầy đủ khắc phục có hai independent parts đó mọi người conflate constantly:
- URL addressability — History API, unique theo-route các URL, không fragments.
- nội dung availability — SSR, static prerendering/SSG, hoặc meta-framework.
Làm chỉ #1 và bạn nhận sạch, shareable các URL đó all vẫn trả về giống nhau blank shell. cho route bạn thực ra muốn được lập chỉ mục, bạn cần cả hai.
đó không universal mandate để thêm SSR mọi nơi, though. SSR và prerendering reduce Cách nhiều bạn phụ thuộc on crawler successfully executing của bạn JavaScript — họ không become mandatory instant trang web là technically SPA. trước khi picking architecture cho được cho route, kiểm tra three điều: liệu đó route cần để xếp hạng tại all ( internal admin panel không), Điều gì trực tiếp, JS-free yêu cầu để nó đã trả về (some setups đã gửi có ý nghĩa HTML), và mà các crawler bạn thực ra cần để satisfy (Google renders JS fairly reliably; Bing và phần lớn AI các crawler ít hơn so — see dưới). CSR đó đã truyền những điều đó kiểm tra không cần để become SSR chỉ vì trang web là SPA.
máy chủ-side kết xuất (SSR)
máy chủ chạy của bạn app cho mỗi yêu cầu và trả về fully-formed HTML cho đó route, sau đó client “hydrates” nó vào trực tiếp SPA. Đây là phần lớn robust option vì crawler nhận hoàn tất nội dung on đầu tiên fetch, không JS execution bắt buộc.
Static prerendering / SSG
thay vì kết xuất theo yêu cầu, bạn xây dựng mỗi route HTML ahead của time tại deploy. Perfect cho nội dung đó không thay đổi theo người dùng. từ my own JavaScript SEO writing: bất kỳ kind của SSR, static kết xuất, và prerendering setup là going để là fine Đối với tìm kiếm engines — điều để tránh là leaving nội dung locked behind client-chỉ kết xuất.
hoặc: không hand-roll nó — sử dụng meta-framework
cho new xây dựng, honest khuyến nghị là để không hand-roll React-Router-chỉ hoặc Vue-Router-chỉ client-side routing tại all. meta-framework — tiếp theo.js, Nuxt, Angular với của nó SSR package, SvelteKit, Remix — cho bạn SSR/SSG và route-based metadata out của box, mà sidesteps điều này đểàn bộ category của vấn đề. (mỗi của những điều đó có của nó own deep dive on điều này trang web.) là honest về flip side: retrofitting SSR onto existing hand-rolled SPA là thực engineering hoạt động — migration project, không config toggle.
Dynamic kết xuất as stopgap, không đích
Bạn có thể serve các crawler một riêng-được kết xuất HTML snapshot (dynamic kết xuất). Cả hai Google và Bing accept điều này — Bing “recommend[s] dynamic rendering as a great alternative for websites relying heavily on JavaScript” (bản dịch) «khuyến nghị[s] dynamic kết xuất as một great alternative cho websites relying heavily on JavaScript» (relayed từ Bing 2018 blog; đó hai ngắn bingbot-khả năng quotes dưới là trực tiếp verified, này lâu hơn một không phải independently re-checked) — nhưng Google là clear đây là một workaround: “Dynamic rendering was a workaround and not a long-term solution… Instead, we recommend that you use server-side rendering, static rendering, or hydration as a solution.” (bản dịch) «Dynamic kết xuất đã là một workaround và không một dài-term giải pháp… Thay vì, we khuyến nghị đó bạn dùng máy chủ-side kết xuất, static kết xuất, hoặc hydration as một giải pháp.» (Relayed từ Google dynamic-kết xuất doc; wording matches điều gì là đã cited trong này site rộng hơn JavaScript SEO hướng dẫn.) đây là một bridge, không an architecture.
History API so với. hash routing (#!)
Five trạng thái, không hai
Kiểm thử an SPA route nhận confusing vì “does it work” (bản dịch) «làm điều này hoạt động» thực ra spans five khác nhau, riêng-verifiable trạng thái:
| State | Điều gì nó là |
|---|---|
| máy chủ phản hồi | bytes fresh, JS-free HTTP yêu cầu để URL thực ra nhận lại. |
| Được kết xuất DOM | Điều gì trình duyệt (hoặc Googlebot’s renderer) xây dựng sau khi executing JavaScript so với đó máy chủ phản hồi. |
| Tìm kiếm processing | Cách Google riêng crawl máy chủ phản hồi, sau đó renders trang, và indexes dựa trên cả hai. |
| trình duyệt đầy đủ-document navigation | thực tế new HTTP yêu cầu để URL — chỉ state đó có thể thay đổi máy chủ phản hồi hoặc mã trạng thái. |
| trình duyệt soft navigation | History API transition (pushState/replaceState) đó thay đổi visible URL, trình duyệt history, và on-screen UI. |
một mọi người conflates: soft navigation thay đổi URL và UI, nhưng nó làm
không by itself tạo new HTTP phản hồi hoặc status — đó chỉ happens on đầy đủ
navigation (hoặc tương đương trực tiếp yêu cầu, như curl). Kiểm thử route by clicking
qua app từ homepage exercises soft navigation; kiểm thử nó by requesting
URL trực tiếp exercises máy chủ phản hồi. Cả hai quan trọng, và họ có thể disagree.
Điều gì History API cho bạn
Google khuyến nghị là unambiguous: “We recommend using the History API to load
different content based on the URL in a SPA.” (bản dịch) «We khuyến nghị dùng đó History API để load khác nhau nội dung dựa trên đó URL trong một SPA.» (Trong đó trực tiếp doc “History API” là
một link, so này deep link targets đó lead-trong clause.) Đó History API
(pushState/replaceState) lets của bạn router thay đổi đó visible URL để một real,
bookmarkable path — /products, không /#/products — không có một đầy đủ reload. Đó các cách sửa
đó addressability half: mỗi view hiện tại có một URL một máy chủ có thể respond để
differently.
Vì sao fragment/hash các URL là invisible để máy chủ — và để Google
Vì đó fragment không bao giờ reaches đó máy chủ (see trên), đó History API là đó chỉ
way để cho mỗi route một URL đó máy chủ có thể thực ra serve. Google diễn đạt một floor dưới
điều này trong của nó basics doc: “don’t use fragments to load different page content. The
following example is a bad practice, because Googlebot can’t reliably resolve the
URLs.” (bản dịch) «không dùng fragments để load khác nhau trang nội dung. Đó sau ví dụ là một bad practice, vì Googlebot không thể reliably resolve đó URLs.» Này là đó giống nhau hướng dẫn I’ve được viết elsewhere — dùng thông thường-looking URLs như
/products, không hash URLs như /#/products, vì Google không thể reliably chỉ mục đó
hash ones.
2015 deprecation, briefly
Đó hash-bang (#!) era ended với Google 2015 post: “Times have changed. Today, as
long as you’re not blocking Googlebot from crawling your JavaScript or CSS files, we are
generally able to render and understand your web pages like modern browsers,” (bản dịch) «Times có changed. Hôm nay, miễn là bạn là không blocking Googlebot từ crawling của bạn JavaScript hoặc CSS files, we là generally able để render và understand của bạn web các trang như modern các trình duyệt,» và
“you can use the History API pushState() to ensure accessibility for a wider range of
browsers (and our systems).” (bản dịch) «bạn có thể dùng đó History API pushState() để bảo đảm accessibility cho một wider range of các trình duyệt (và của chúng ta các hệ thống).» Một nuance worth giữ: Google đã không instantly deindex
old hash-bang các trang — “we’ll generally crawl, render, and index the #! URLs” (bản dịch) «we’ll generally crawl, render, và chỉ mục đó URLs» — nhưng
“we can still try” (bản dịch) «we có thể vẫn try» không phải “you should still do this.” (bản dịch) «bạn nên vẫn làm này.»
Đang làm mỗi route independently indexable
Sạch các URL và thực HTML nhận bạn được crawl. để nhận được lập chỉ mục correctly, mỗi route cần của nó own các tín hiệu.
Theo-route canonical tags — và đó “most restrictive directive” (bản dịch) «hầu hết restrictive directive» trap
mỗi route cần của nó own rel=canonical trong được kết xuất DOM. trap: nếu
placeholder canonical (hoặc noindex) ships trong thô HTML shell và JavaScript là
supposed để overwrite nó sau đó, Bạn có thể nhận conflict. Google resolves conflicts
giữa thô và được kết xuất versions by taking nhiều hơn restrictive tín hiệu — as I’ve
put nó trước khi, Google sẽ chọn phần lớn restrictive statements giữa HTML và
được kết xuất version của trang. stray noindex hoặc sai canonical baked vào
shell có thể silently suppress toàn bộ route ngay cả sau khi của bạn JS “các cách sửa” nó.
Theo-route các tiêu đề và meta các mô tả qua JavaScript
Setting những trong JS là fine — Google says so trực tiếp: “You can use JavaScript to set
or change the meta description as well as the <title> element.” (bản dịch) «Bạn có thể dùng JavaScript để set hoặc thay đổi đó mô tả meta cũng như đó thẻ tiêu đề element.» Đó requirement là
đó they land trong đó được kết xuất DOM Google evaluates, không chỉ flash briefly. Cho mỗi
trang của nó own tiêu đề và mô tả đó cập nhật khi đó route thay đổi.
thực <a href> links giữa routes
Làm của bạn inter-route links real anchors với href các thuộc tính, không nhấp handlers on
<div>s. Google discovers URLs by extracting hrefs; một <div onClick> đó navigates
qua đó router là invisible để đó crawler link extraction. Và không lean on
client-side state để carry nội dung trên những navigations — Google renderer “does
not retain state across page loads: Local Storage and Session Storage data are cleared
across page loads. HTTP Cookies are cleared across page loads.” (bản dịch) «không retain state trên trang loads: Local Storage và Session Storage dữ liệu là cleared trên trang loads. HTTP Cookies là cleared trên trang loads.»
Sitemap generation cho SPA routes
có không special “SPA sitemap” format — nó accounting vấn đề
sitemap giao thức là unchanged cho SPAs. thực tế hoạt động là đang làm sure mỗi route bạn list independently resolves để thực, unique, được kết xuất HTML. sitemap của 500 client-side routes là worthless nếu những điều đó routes all trả về giống nhau shell. So sitemap là downstream của bạn kết xuất strategy, không substitute cho nó.
Xử lý dynamic/parameterized routes (/product/:id)
cho apps với parameterized routes, Bạn có thể’t hand-maintain list. sitemap
generator có để chạy so với giống nhau dữ liệu nguồn app dùng — xây dựng script hoặc
máy chủ điểm cuối đó enumerates mỗi id — so sitemap và app không bao giờ drift
apart. những điều đó enumerated các URL là chỉ worth listing sau khi họ’re SSR’d hoặc prerendered.
Giữ sitemap trong sync với Điều gì máy chủ-resolvable
Regenerate sitemap as part của bạn xây dựng hoặc on schedule tied để của bạn nội dung
nguồn. route đó 404s (hoặc tệ hơn, soft-404s tại 200) nhưng sits trong của bạn sitemap là
crawl-budget và quality tín hiệu bạn không muốn.
Kiểm thử Điều gì Google thực ra sees
không spot-kiểm tra một URL. toàn bộ point của SPA vấn đề là đó routes có thể differ trong trình duyệt nhưng không on wire, so kiểm thử multiple routes tại thô-HTML cấp độ:
- Fetch thô HTML theo route với
curl( Googlebot người dùng-agent nơi relevant) và xác nhận nội dung là unique theo URL, không shared shell. - URL Inspection trong Search Console — so sánh được crawl/được kết xuất HTML cho several routes, và xác nhận theo-route tiêu đề, mô tả, và canonical là present.
- xác nhận lỗi routes trả về right tín hiệu — “không tìm thấy” view nên either
chuyển hướng để thực lỗi status hoặc carry
noindextrong được kết xuất DOM.
phổ biến myths về SPA SEO
- “Google can’t index SPAs at all.” (bản dịch) «Google không thể chỉ mục SPAs tại all.» Outdated. Google chạy evergreen Chromium và generally renders SPA nội dung. Đó real risks là cụ thể: soft 404s, hash routing, render timeouts, và non-Google các crawler (Bing ít hơn reliably, hầu hết AI các crawler không tại all).
- “Adding the History API fixes SPA SEO.” (bản dịch) «Thêm đó History API các cách sửa SPA SEO.» Điều này các cách sửa addressability chỉ. Nếu đó máy chủ vẫn trả về đó giống nhau shell cho mỗi route, Google vẫn có để execute JS để see bất cứ điều gì.
- “Hash routing still works as a fallback.” (bản dịch) «Hash routing vẫn hoạt động as một fallback.» Deprecated since 2015 và được khuyến nghị so với bao giờ since.
- “Client-side rendering is a ranking penalty.” (bản dịch) «Client-side kết xuất là một xếp hạng hình phạt.» Không trực tiếp CSR hình phạt tồn tại. Đó damage là gián tiếp — failed/delayed kết xuất, soft 404s, và duplicate clustering từ render timeouts reduce điều gì nhận được lập chỉ mục.
- “Pre-rendering for bots is cloaking.” (bản dịch) «Pre-kết xuất cho bots là cloaking.» Không khi đó nội dung matches điều gì người dùng eventually see — chỉ đó khi/nơi of kết xuất differs, không đó nội dung.
- “You need a special SPA sitemap generator.” (bản dịch) «Bạn cần một special SPA sitemap generator.» Không special format tồn tại; đó hoạt động là đang làm mỗi listed route resolve để real HTML.
FAQs
có thể Google chỉ mục single-trang application? Có, nếu mỗi route resolves để thực, unique HTML (qua SSR/prerendering) tại thực URL. Bare client-chỉ SPAs thường không.
Làm I cần SSR, hoặc là client-side kết xuất bao giờ okay? CSR có thể hoạt động cho nội dung đó không cần để xếp hạng, nhưng cho bất cứ điều gì bạn muốn được lập chỉ mục reliably, prerender hoặc SSR nó — không bet on renderer executing của bạn JS trong time.
là hash (#!) routing bad Đối với SEO? Có — fragment không bao giờ reaches máy chủ, so
Google có thể’t reliably resolve những điều đó các URL. sử dụng History API.
Làm mỗi route cần của nó own thẻ canonical? Có, trong được kết xuất DOM — và làm
sure không có gì nhiều hơn restrictive ( stray noindex hoặc sai canonical) ships trong thô
shell.
là prerendering service cloaking? Không, provided nội dung phân phối để bots matches Điều gì người dùng see.
nên I sử dụng meta-framework thay vì building routing myself? cho new xây dựng, thường có — tiếp theo.js/Nuxt/Angular-SSR/SvelteKit cho bạn SSR/SSG và theo-route metadata cho free.
Vì sao làm my SPA trả về 200 Đối với các trang đó không exist? vì client-side
router giữ gốc 200 status cho virtual navigations. khắc phục nó với JS
chuyển hướng để thực lỗi status hoặc được kết xuất noindex.
AI summary
condensed take on Nâng cao version:
- SPA SEO = client-side routing cụ thể (React Router, Vue Router, Angular Router). An SPA là được định nghĩa by loading một document và swapping views với JavaScript — an “app shell” đó gửi near-empty HTML cho mỗi URL là một phổ biến implementation, không một rule mỗi SPA follows; kiểm tra điều gì một trực tiếp yêu cầu thực ra trả về thay vì assuming.
- Đó cốt lõi risk: client-side routing có thể thay đổi điều gì đó người dùng sees không có
thay đổi điều gì đó máy chủ sẽ trả về. On một bare app-shell SPA, fetching
/productsvà/abouttrực tiếp có thể nhận byte-giống hệt thô HTML. - Five trạng thái nhận conflated: máy chủ phản hồi, được kết xuất DOM, Tìm kiếm processing, trình duyệt đầy đủ-document navigation, và trình duyệt soft navigation. MỘT History API transition thay đổi đó URL và UI nhưng không itself tạo một new máy chủ phản hồi hoặc status.
- Three symptoms of một bare shell: đó app-shell vấn đề, soft 404s (routers giữ một
200cho “không tìm thấy” — Google hai được ghi lại các cách sửa là chuyển hướng để một URL đó itself 404s, hoặc một JS-đã thêmnoindex; an ban đầunoindexcó thể nguyên nhân kết xuất để là skipped), và shared-shell duplicates — mà “render timeout” (bản dịch) «render timeout» (Illyes) là một có thể nguyên nhân of, không an tự động diagnosis không có yêu cầu/render/Tìm kiếm evidence. - Hash routing fails mechanically: đó fragment sau
#không bao giờ reaches đó máy chủ, so đó máy chủ không thể differ by route. Deprecated by Google trong 2015; History API cho addressability, không một hoàn tất indexability cách sửa. - Đó cách sửa có hai independent halves nơi một bare shell là đó vấn đề: addressability (History API, unique theo-route URLs) và nội dung availability (SSR, prerendering/SSG, hoặc một meta-framework) — nhưng SSR không một universal requirement; base đó call on liệu đó route cần để xếp hạng, điều gì điều này đã trả về trực tiếp, và mà các crawler cần để render điều này.
- Theo route: own canonical/tiêu đề/mô tả trong đó được kết xuất DOM; watch đó
“most restrictive directive” (bản dịch) «hầu hết restrictive directive» trap nơi một thô-HTML
noindex/canonical overrides của bạn JS. Real<a href>links; không rely on client state (WRS clears storage/cookies trên loads). - Sitemaps: listing một route không prove điều này resolves — không special format tồn tại; mỗi listed route phải independently resolve để real HTML, và dynamic routes cần một generator tied để đó app dữ liệu nguồn.
- Dynamic kết xuất là một Google/Bing-accepted stopgap, không một dài-term architecture.
- Kiểm thử multiple routes tại đó thô-HTML cấp độ (trực tiếp các yêu cầu), không chỉ by navigating trong đó app.
Tài liệu chính thức
Chính-nguồn tài liệu từ các công cụ tìm kiếm.
- Understand đó JavaScript SEO basics — đó app-shell model, “Use the History API instead of fragments,” (bản dịch) «Dùng đó History API thay vì fragments,» và setting các tiêu đề/các mô tả qua JS.
- Cách sửa Tìm kiếm-related JavaScript các vấn đề — đó SPA soft-404 section, “Don’t use URL fragments to load different content,” (bản dịch) «không dùng URL fragments để load khác nhau nội dung,» và đó History API khuyến nghị.
- Dynamic Kết xuất as một workaround — vì sao dynamic kết xuất là một stopgap, không một dài-term giải pháp.
- Deprecating của chúng ta AJAX crawling scheme (2015) — đó formal end of hash-bang crawling và đó pushState khuyến nghị.
- Xây dựng và submit một sitemap — chung sitemap giao thức (có không SPA-cụ thể format).
Bing / Microsoft
- bingbot Series: JavaScript, Dynamic Kết xuất, và Cloaking. Oh My! — bingbot JS-kết xuất capabilities và của nó dynamic-kết xuất stance.
Quotes từ nguồn
On—record statements từ Google và Bing. mỗi link là deep link đó jumps để quoted passage on nguồn trang.
Google — app-shell vấn đề
- “Some JavaScript sites may use the app shell model where the initial HTML does not contain the actual content and Google needs to execute JavaScript before being able to see the actual page content.” (bản dịch) «Some JavaScript các trang có thể dùng đó app shell model nơi đó ban đầu HTML không contain đó thực tế nội dung và Google cần để execute JavaScript trước đang able để see đó thực tế trang nội dung.» Nhảy đến trích dẫn
Google — hash/fragment routing và History API
- “A SPA may use URL fragments (for example https://example.com/#/products) for loading different views.” (bản dịch) «MỘT SPA có thể dùng URL fragments (ví dụ https://example.com/#/products) cho loading khác nhau views. » Nhảy đến trích dẫn
- “We recommend using the History API to load different content based on the URL in a SPA.” (bản dịch) «We khuyến nghị dùng đó History API để load khác nhau nội dung dựa trên đó URL trong một SPA.» Nhảy đến trích dẫn (Trong đó trực tiếp doc “History API” là một link, so này deep link targets đó lead-trong clause; đó đầy đủ sentence là present verbatim trong order.)
- “don’t use fragments to load different page content. The following example is a bad practice, because Googlebot can’t reliably resolve the URLs.” (bản dịch) «không dùng fragments để load khác nhau trang nội dung. Đó sau ví dụ là một bad practice, vì Googlebot không thể reliably resolve đó URLs.» Nhảy đến trích dẫn
Google — SPA soft 404s
- “In a single-page application (SPA), this can be especially difficult. To prevent error pages from being indexed, you can use one or both of the following strategies.” (bản dịch) «Trong một single-trang application (SPA), này có thể là especially difficult. Để ngăn lỗi các trang từ đang được lập chỉ mục, bạn có thể dùng một hoặc cả hai of đó sau strategies.» Nhảy đến trích dẫn
- “When a SPA is using client-side JavaScript to handle errors they often report a
200HTTP status code instead of the appropriate status code.” (bản dịch) «Khi một SPA là dùng client-side JavaScript để xử lý các lỗi they thường báo cáo một HTTP mã trạng thái thay vì đó appropriate mã trạng thái.» Nhảy đến trích dẫn
Google — meta tags qua JavaScript, và stateless kết xuất
- “You can use JavaScript to set or change the meta description as well as the
<title>element.” (bản dịch) «Bạn có thể dùng JavaScript để set hoặc thay đổi đó mô tả meta cũng như đó thẻ tiêu đề element.» Nhảy đến trích dẫn - “WRS does not retain state across page loads: Local Storage and Session Storage data are cleared across page loads. HTTP Cookies are cleared across page loads.” (bản dịch) «WRS không retain state trên trang loads: Local Storage và Session Storage dữ liệu là cleared trên trang loads. HTTP Cookies là cleared trên trang loads.» Nhảy đến trích dẫn
Google — deprecating hash-bang crawling (2015)
- “In short: We are no longer recommending the AJAX crawling proposal we made back in 2009.” (bản dịch) «Tóm lại: We là không lâu hơn recommending đó AJAX crawling proposal we đã làm lại trong 2009.» Nhảy đến trích dẫn
- “Times have changed. Today, as long as you’re not blocking Googlebot from crawling your JavaScript or CSS files, we are generally able to render and understand your web pages like modern browsers.” (bản dịch) «Times có changed. Hôm nay, miễn là bạn là không blocking Googlebot từ crawling của bạn JavaScript hoặc CSS files, we là generally able để render và understand của bạn web các trang như modern các trình duyệt.» Nhảy đến trích dẫn
Google — dynamic kết xuất là workaround (relayed; wording matches Điều gì đã cited trong điều này trang web rộng hơn JavaScript SEO hướng dẫn, không independently re-verified điều này truyền)
- “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 kết xuất đã là một workaround và không một dài-term giải pháp cho các vấn đề với JavaScript-generated nội dung trong các công cụ tìm kiếm.» Nhảy đến trích dẫn
Bing — bingbot và JavaScript
- “bingbot is generally able to render JavaScript.” (bản dịch) «bingbot là generally able để render JavaScript.» — bingbot Series, Bing Quản trị viên web Blog. Đọc đó post
- “bingbot does not necessarily support all the same JavaScript frameworks that are supported in the latest version of your favorite modern browser.” (bản dịch) «bingbot không nhất thiết hỗ trợ all đó giống nhau JavaScript các framework đó là supported trong đó latest version of của bạn favorite modern trình duyệt.» — giống nhau post. Đọc đó post
Gary Illyes, Google — render timeouts tạo duplicates (relayed qua LinkedIn post, mà resists automated verification; treat as cao-confidence phụ)
- “the centerpiece took forever to load, so rendering timed out… and we were left with a bunch of pages that only had the boilerplate. With only the boilerplate, those pages are dups.” (bản dịch) «đó centerpiece took forever để load, so kết xuất timed out… và we đã là left với một bunch of các trang đó chỉ đã có đó boilerplate. Với chỉ đó boilerplate, những các trang là dups.» Đọc đó post
Mà kết xuất path nên I take?
Hoạt động top-xuống. Đó câu hỏi là luôn “does the crawler get real HTML for this route without executing my JS?” (bản dịch) «làm đó crawler nhận real HTML cho này route không có executing my JS?» — và bắt đầu by confirming đó route thực ra cần để là được lập chỉ mục và kiểm tra điều gì một trực tiếp, JS-free yêu cầu đã trả về; không mỗi route cần SSR, và some đã trả về usable HTML.
1. là bạn starting new xây dựng?
- Có → sử dụng meta-framework (tiếp theo.js, Nuxt, Angular với SSR, SvelteKit, Remix). SSR/SSG và theo-route metadata come được xây dựng trong. Dừng ở đây.
- Không → Continue.
2. Làm của bạn nội dung thay đổi theo người dùng / theo yêu cầu?
- Không (mostly static nội dung) → Prerender / SSG tại xây dựng time. Simplest robust khắc phục — mỗi route là thực HTML on disk.
- Có → máy chủ-side kết xuất (SSR) so mỗi yêu cầu trả về route-cụ thể HTML.
3. có thể’t làm SSR hoặc prerendering hiện tại (legacy hand-rolled SPA)?
- sử dụng dynamic kết xuất as tạm thời bridge — phục vụ các crawler được kết xuất snapshot. Plan migration để SSR/SSG; không treat điều này as đích.
4. Whatever path bạn chose, xác nhận tất cả những điều này theo route:
- thực URL qua History API (không
#!fragments). - Unique tiêu đề + mô tả meta + canonical trong được kết xuất DOM.
- Không có gì nhiều hơn restrictive (
noindex, sai canonical) baked vào thô shell. - thực
<a href>links giữa routes. - lỗi routes trả về thực lỗi status hoặc được kết xuất
noindex(không soft 404s). - mỗi route trong sitemap independently resolves để thực HTML.
SPA SEO checklist
Addressability
- mỗi view có thực URL qua History API (
/products), không fragment (/#/products). - Inter-route links là thực
<a href>anchors, không<div onClick>handlers.
nội dung availability
- mỗi route trả về thực, unique HTML không có crawler executing của bạn JS (SSR, prerendering/SSG, hoặc meta-framework).
- Route nội dung loads trước khi render window closes (tránh boilerplate-chỉ duplicates).
Theo-route indexability
- Unique
<title>và mô tả meta theo route, present trong được kết xuất DOM. - Unique
rel=canonicaltheo route trong được kết xuất DOM. - Không stray
noindex/placeholder canonical trong thô shell đó nhiều hơn restrictive tín hiệu có thể lock trong.
các lỗi
- “Không tìm thấy” routes chuyển hướng để URL whose máy chủ trả về thực lỗi status hoặc
carry
noindexđã thêm by JavaScript (không soft 404s tại200). - nếu sử dụng
noindexroute, xác nhận nó đã thêm sau khi app decides route là không hợp lệ, không present từ rất đầu tiên paint — ban đầunoindexcó thể nguyên nhân Google để skip kết xuất trang.
Sitemap
- mỗi listed route independently resolves để thực HTML.
- Dynamic routes (
/product/:id) enumerated từ app dữ liệu nguồn, không hand-maintained.
Verification
- Thô HTML checked trên multiple routes (curl), confirming unique nội dung theo URL.
- URL Inspection xác nhận được kết xuất nội dung, tiêu đề, mô tả, và canonical theo route.
mental models
1. hai halves bạn phải không conflate. Addressability (History API, unique các URL, không fragments) và nội dung availability (SSR, prerendering, hoặc meta-framework) là independent. Sạch các URL với không theo-route HTML = tidy sitemap của blank shells. bạn cần cả hai.
2. “What would the server return, fresh?” (bản dịch) «Điều gì sẽ đó máy chủ trả về, fresh?»
Đó toàn bộ SPA vấn đề là đó trình duyệt view diverges từ đó máy chủ phản hồi. Cho
bất kỳ route, ask điều gì một curl với không JS execution nhận lại. Nếu đây là đó shell, đó
crawler có thể see đó shell cũng.
3. status-code default là 200 — including cho các lỗi.
Client-side routers giữ gốc 200. Assume mỗi “lỗi” view là soft 404
cho đến khi bạn’ve có chủ ý đã làm nó trả về thực lỗi status hoặc được kết xuất noindex.
4. phần lớn-restrictive-directive trap.
Google reconciles thô so với. được kết xuất by taking nhiều hơn restrictive tín hiệu. noindex
hoặc sai canonical trong shell có thể override của bạn JS “cách sửa.” Audit thô HTML, không chỉ
được kết xuất DOM.
5. Meta-framework đầu tiên cho new xây dựng. Hand-rolling client-side routing có nghĩ là owning mỗi một của những điều này các vấn đề. Picking framework đó làm SSR/SSG và theo-route metadata là chọn không để có them.
SPA SEO bảng tra nhanh
Routing
| Approach | URL ví dụ | Reaches máy chủ? | Google-safe? |
|---|---|---|---|
| History API | /products | Có | Có (được khuyến nghị) |
| Hash routing | /#/products | Không (fragment không bao giờ được gửi) | Không — deprecated 2015 |
Hash-bang (#!) | /#!/products | Không | Không — legacy AJAX scheme, deprecated |
Kết xuất strategies
| Strategy | Crawler nhận thực HTML không có đang chạy JS? | Best cho |
|---|---|---|
| SSR | Có | Theo-người dùng / theo-yêu cầu nội dung |
| Prerendering / SSG | Có | Mostly-static nội dung |
| Meta-framework (tiếp theo/Nuxt/etc.) | Có (được xây dựng trong) | New xây dựng |
| Dynamic kết xuất | Có, cho bots chỉ | Tạm thời bridge on legacy SPAs |
| Bare CSR (client-chỉ) | Không | Không có gì bạn muốn được lập chỉ mục reliably |
Theo-route phải-haves (all trong được kết xuất DOM)
- Unique URL (History API) · unique
<title>· unique mô tả meta · uniquerel=canonical· thực<a href>links · lỗi routes với thực status hoặcnoindex.
Fast facts
- Fragment sau khi
#là không bao giờ được gửi để máy chủ — đó Vì sao hash routing fails. - Client-side routers giữ
200cho mọi thứ, including “không tìm thấy” → soft 404s. - Google reconciles thô so với. được kết xuất by taking nhiều hơn restrictive tín hiệu.
- Không special SPA sitemap format tồn tại — mỗi listed route phải resolve để thực HTML.
SPA SEO anti-patterns
Shipping bare CSR SPA và submitting đầy đủ sitemap. sitemap lists 500 routes; all 500 trả về giống nhau shell để fresh fetch. sitemap không làm nội dung exist — kết xuất làm.
Thêm History API và calling nó đã xong. Sạch các URL khắc phục addressability, không nội dung. không có SSR/prerendering máy chủ vẫn trả về shell cho mỗi route.
Hash / hash-bang routing “as a fallback.” (bản dịch) «as một fallback.» Đó fragment không bao giờ reaches đó máy chủ, so đó máy chủ không thể differ by route. Deprecated since 2015; không một fallback, một dead end.
Navigating với <div onClick> thay vì <a href>.
Google extracts hrefs để discover các URL. Nhấp-handler navigation là invisible để link
extraction, so những điều đó routes có thể không bao giờ là tìm thấy.
MỘT placeholder noindex hoặc canonical trong đó thô shell “that JS will overwrite.” (bản dịch) «đó JS sẽ overwrite.»
Google takes đó hơn restrictive of thô so với. được kết xuất. Đó shell directive có thể win và
silently suppress đó route.
Returning 200 cho “không tìm thấy” views.
Soft 404s nhận thin/rỗng các trang được lập chỉ mục. chuyển hướng để thực lỗi status hoặc thêm
được kết xuất noindex.
Loading route nội dung slowly, sau khi boilerplate. Render timeouts leave chỉ shared shell, và mỗi route collapses vào duplicate của mỗi khác (Illyes). Load centerpiece nội dung đầu tiên.
Relying on client state để carry nội dung trên navigations. renderer clears Local/Session Storage và cookies trên trang loads — nội dung đó chỉ tồn tại trong client state sẽ không là ở đó Khi Google renders tiếp theo route.
phổ biến SPA lập chỉ mục các vấn đề
mỗi route trả về giống nhau HTML
Symptom: /products và /about có khác trình duyệt views nhưng giống hệt thô
các phản hồi. có khả năng nguyên nhân: History API routing cung cấp addressability không có SSR
hoặc prerendering. khắc phục: Produce route-cụ thể HTML và xác nhận trực tiếp yêu cầu để
mỗi path contains của nó own heading, thân phản hồi copy, và metadata.
Bị thiếu routes xuất hiện as thành công các trang
Symptom: nonexistent route hiển thị không-tìm thấy view trong khi returning 200.
có khả năng nguyên nhân: client router owns lỗi sau khi máy chủ có đã được gửi
thành công shell. khắc phục: trả về right máy chủ status; nếu đó không thể ship tuy vậy,
chuyển hướng để URL với thực lỗi status hoặc render noindex. xác nhận với fresh
trực tiếp yêu cầu, không trong-app navigation.
Routes collapse vào duplicates sau khi kết xuất
Symptom: các công cụ tìm kiếm cluster distinct các URL hoặc retain chỉ shared navigation. có khả năng nguyên nhân: Route nội dung loads cũng muộn và kết xuất captures boilerplate. khắc phục: Prioritize chính nội dung trong máy chủ phản hồi hoặc earliest render path. xác nhận several routes expose unique nội dung trước khi tùy chọn scripts finish.
Kiểm thử Điều gì máy chủ thực ra trả về theo route
SPA trap là đó routes differ trong trình duyệt nhưng không on wire. kiểm tra multiple routes tại thô-HTML cấp độ, không một.
Fetch thô HTML theo route (macOS / Linux)
# Fetch a few routes with a Googlebot UA and compare — they should NOT be identical
UA="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
for path in / /products /about /product/123; do
echo "=== $path ==="
curl -s -A "$UA" "https://example.com$path" | wc -c # byte counts should differ
doneDiff hai routes’ thô HTML (là they đó giống nhau shell?)
UA="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
diff <(curl -s -A "$UA" https://example.com/products) \
<(curl -s -A "$UA" https://example.com/about) \
&& echo "IDENTICAL — bare shell, content is client-only" \
|| echo "Different — routes return distinct HTML (good)"Kiểm tra đó theo-route tiêu đề và canonical trong đó thô phản hồi (grep / regex)
UA="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
curl -s -A "$UA" https://example.com/products \
| grep -Eio '<title>[^<]*</title>|<link[^>]+rel=["'"'"']canonical["'"'"'][^>]*>'Kiểm tra đó HTTP status of một “không tìm thấy” route (soft-404 detector)
# A missing route should NOT return 200
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/this-route-does-not-existTrong đó trình duyệt DevTools console — inspect đó được kết xuất tiêu đề/canonical
// Run on each route after client-side navigation to confirm JS set them
console.log('title:', document.title);
console.log('description:',
document.querySelector('meta[name="description"]')?.content);
console.log('canonical:',
document.querySelector('link[rel="canonical"]')?.href);Bookmarklet — flag nhấp-handler “links” đó không real anchors
javascript:(()=>{const bad=[...document.querySelectorAll('[onclick],div[role="link"]')]
.filter(e=>!e.closest('a[href]'));
bad.forEach(e=>e.style.outline='3px solid red');
alert(bad.length+' non-anchor clickable(s) outlined — these are invisible to link extraction');})();XPath — tìm fragment/hash links một crawler không thể resolve (paste vào DevTools console)
$x('//a[starts-with(@href, "#") or contains(@href, "/#/")]')
.map(a => a.getAttribute('href'));
// Any results are hash-routed links; migrate them to History API paths. Tools cho auditing SPA
- URL Inspection (Google Search Console) — see đó được crawl so với. được kết xuất HTML cho một route và xác nhận đó theo-route tiêu đề, mô tả, và canonical thực ra land.
- Rich Kết quả Kiểm thử / URL Inspection “View crawled page” (bản dịch) «View được crawl trang» — Google own render of một single URL, hữu ích cho confirming nội dung survives kết xuất.
curlvới một Googlebot UA — đó fastest way để so sánh đó thô HTML of several routes và catch một shared shell.- Screaming Frog SEO Spider (JavaScript kết xuất chế độ) — crawl đó site với và không có kết xuất để so sánh thô so với. được kết xuất nội dung và các tiêu đề trên mỗi route.
- Ahrefs Site Audit — surfaces indexability các vấn đề, bị thiếu/duplicate các tiêu đề và canonicals, và soft-404-như patterns trên routes.
- DebugBear — SPA-oriented performance monitoring; render timing matters vì chậm routes có thể time out vào boilerplate duplicates.
- Bing Quản trị viên web Tools — URL Inspection — bingbot renders JS ít hơn consistently hơn Googlebot, so xác nhận của bạn routes on Bing side cũng.
Prove SPA route là independently indexable
Kiểm thử trực tiếp-entry HTML parity
Kiểm thử để chạy: Open representative routes trong new session và fetch giống nhau các URL
với curl. Dự kiến kết quả: mỗi URL trả về của nó own chính nội dung và head
các tín hiệu không có prior app state. thất bại interpretation: route phụ thuộc vào
client navigation hoặc stored state. Monitoring window: Immediate. Rollback
trigger: route hoạt động chỉ sau khi entering qua homepage.
Kiểm thử lỗi status xử lý
Kiểm thử để chạy: yêu cầu known-không hợp lệ route trực tiếp và inspect cả hai status và
được kết xuất directives. Dự kiến kết quả: genuine lỗi status, hoặc được ghi lại
fallback của được kết xuất noindex/chuyển hướng để lỗi phản hồi. thất bại
interpretation: SPA là generating soft 404s. Monitoring window: Immediate.
Rollback trigger: Không hợp lệ paths ship as indexable 200 các trang.
Kiểm thử metadata isolation
Kiểm thử để chạy: So sánh thô và được kết xuất tiêu đề, robots tag, và canonical trên ít nhất three routes. Dự kiến kết quả: mỗi route có một dự kiến, internally consistent đặt. thất bại interpretation: shared shell leaks metadata giữa routes hoặc JS overwrites nó cũng muộn. Monitoring window: Immediate locally và sau khi recrawl trong URL Inspection. Rollback trigger: bất kỳ route inherits một route canonical hoặc restrictive shell directive.
Tự kiểm tra: SPA SEO
Five nhanh các câu hỏi on đang làm single-trang applications crawlable và indexable. Pick câu trả lời cho mỗi, sau đó kiểm tra.
các tài nguyên worth của bạn time
My related writing
- JavaScript SEO Các vấn đề & Thực hành tốt nhất — my đầy đủ JS-SEO hướng dẫn, including đó “don’t use fragments in URLs” (bản dịch) «không dùng fragments trong URLs» và app-shell/duplicate-nội dung sections đó này bài viết xây dựng on.
- Đó Beginner Hướng dẫn để SEO kỹ thuật — nơi kết xuất và crawling fit trong đó bigger picture.
My speaking
- Cách Tìm kiếm Hoạt động (SlideShare) — my walkthrough of crawling, kết xuất, lập chỉ mục, và xếp hạng, mà là đó pipeline an SPA có để survive. (My standing disclaimer áp dụng: “This is my understanding of systems… not going to be 100% complete or accurate.” (bản dịch) «Này là my understanding of các hệ thống… không going để là 100% hoàn tất hoặc chính xác.»)
Từ khoảng đó ngành
- Google — Understand đó JavaScript SEO basics — đó app-shell model và “Use the History API instead of fragments.” (bản dịch) «Dùng đó History API thay vì fragments.»
- Google — Cách sửa Tìm kiếm-related JavaScript các vấn đề — đó SPA soft-404 section và đó History API khuyến nghị.
- Google — Deprecating của chúng ta AJAX crawling scheme (2015) — đó formal end of hash-bang crawling.
- Bing — bingbot Series: JavaScript, Dynamic Kết xuất, và Cloaking. Oh My! — bingbot JS-kết xuất stance.
- đây là Không Cloaking Để Dùng MỘT Pre-Render Service Cho Blank HTML Các trang Cho SPA Development (Công cụ tìm kiếm Roundtable, 2015) — coverage of Gary Illyes on pre-kết xuất SPAs không đang cloaking. (SER paraphrase of Illyes, dated 2015 — phụ.)
- SEO cho Single Trang Applications (Nuxt SEO) — một framework-side walkthrough of đó giống nhau các vấn đề.
- Cách Optimize Single Trang Applications Cho SEO (DebugBear) — đó kết xuất/performance angle on SPA indexability.
- SPA (glossary) (MDN) — một neutral definition of đó single-trang-application pattern.
Videos
- Google Search Central (YouTube) — Martin Splitt JavaScript SEO series covers kết xuất, History API, và SPA thất bại modes discussed ở đây. Channel
Nhật ký thay đổi
Đã cập nhật 8 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 18 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.
-
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.