Panduan SPA SEO

cara membuat single-halaman applications (React Router, Vue Router, Angular Router) dapat di-crawl dan dapat diindeks — app-shell dan soft-404 masalah, History API vs hash routing, per-route HTML melalui SSR/prerendering, per-route canonicals dan judul, dan sitemap generation untuk client-side routes.

Pertama kali diterbitkan: 3 Jul 2026 · Terakhir diperbarui: 3 Agu 2026 · Lanjutan

sebuah single-halaman application memuat satu document dan swaps views dengan JavaScript alih-alih requesting sebuah baru halaman dari server. banyak SPAs implement itu dengan sebuah 'app shell' — server mengembalikan satu URL's worth dari nyata HTML, sebuah near-empty shell, until JS renders setiap route — tetapi itu's sebuah umum implementation pilihan, tidak sebuah aturan setiap SPA mengikuti; periksa apa sebuah direct permintaan untuk setiap route sebenarnya mengembalikan sebelum assuming ini. perbaiki, di mana sebuah bare app-shell setup adalah masalah, memiliki dua independent halves: addressability (History API, tidak hash/#! fragments, so setiap view memiliki sebuah nyata URL — sebuah browser soft navigation melalui History API perubahan URL dan UI tetapi tidak itself buat baru server respons) dan konten availability (SSR, prerendering, atau sebuah meta-kerangka kerja so setiap dari itu URLs dapat kembalikan unique HTML pada permintaan). pada top dari itu, verify setiap route's judul, canonical, dan robots state keduanya pada direct entry dan setelah rendering, tangani soft 404s (routers tend untuk pertahankan sebuah 200 status untuk 'tidak ditemukan' views — redirect untuk sebuah URL itu itself 404s, atau tambahkan sebuah dirender noindex, though sebuah awal noindex dapat penyebab rendering untuk menjadi skipped), dan pastikan setiap route di Anda sitemap sebenarnya resolves untuk nyata, dapat diindeks HTML — ada Tidak special SPA sitemap format.

TL;DR — sebuah SPA’s core SEO risk tidak “JavaScript” (terjemahan) “JavaScript” di abstract — ini adalah itu client-side routing perubahan apa pengguna sees tanpa mengubah apa server akan kembalikan. perbaiki memiliki dua independent halves orang constantly conflate: addressability (History API, unique per-route URLs, Tidak #! fragments) dan konten availability (SSR, prerendering/SSG, atau sebuah meta-kerangka kerja). melakukan hanya pertama dan Anda mendapatkan sebuah tidy sitemap dari URLs itu semua render yang sama shell. pada top dari keduanya, setiap route perlu -nya own canonical/judul/deskripsi di dirender DOM, Anda memiliki untuk tangani soft 404s itu pertahankan sebuah 200, dan setiap route di sitemap memiliki untuk independently resolve untuk nyata HTML.

Apa SPA SEO sebenarnya adalah

SPA adalah sebuah application architecture, tidak sebuah guaranteed SEO failure state. Bukti untuk klaim ini Primary standard or official documentation supporting the adjacent article claim. Cakupan: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Tingkat keyakinan: tinggi · Diverifikasi: MDN: Single-page application server rendering, prerendering, atau dengan hati-hati implemented client rendering dapat expose konten, tetapi none jaminan pengindeksan. Bukti untuk klaim ini Primary standard or official documentation supporting the adjacent article claim. Cakupan: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Tingkat keyakinan: tinggi · Diverifikasi: Google: JavaScript SEO basics

ini adalah deep dive pada single-halaman applications secara khusus — client-side routing dengan React Router, Vue Router, atau Angular Router, di mana setelah pertama muat server tidak pernah mengirim lainnya penuh halaman. ini sits alongside lebih luas JavaScript SEO guide (yang covers parity, lazy-memuat, infinite scroll, dan JS rendering umumnya) dan per-kerangka kerja tulis-ups untuk React, Berikutnya.js, Nuxt, Angular, Vue, Svelte, dan Astro. Di sini I’m hanya interested di routing layer dan apa ini melakukan untuk crawling dan pengindeksan.

Mengapa bare client-side-routed SPAs fail di SEO

Satu URL, satu HTML respons — app-shell masalah

«Google describes the failure mode 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.” In a bare SPA, the “app shell” is all the server ever returns. Fetch /products fresh and you get the same near-empty document you’d get for /about. The content only diverges after the browser runs your JavaScript and your router decides what to show. » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian dua puluh dua, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

itu’s seluruh masalah di satu kalimat: client-side routing perubahan apa pengguna sees tanpa mengubah apa server akan kembalikan. Semuanya downstream — soft 404s, duplicate konten, missing judul — adalah sebuah symptom dari itu.

server tidak pernah sees yang “halaman” (terjemahan) “halaman” adalah requested (mengapa hash routing fails)

Older SPAs muat views dari URL fragments — example.com/#/products. Google’s tepat kata: “A SPA mungkin gunakan URL fragments (misalnya https://example.com/#/products) untuk loading berbeda views.” (terjemahan) “sebuah SPA dapat gunakan URL fragments (misalnya tidak terdefinisi untuk memuat berbeda views.” ini fails untuk sebuah spesifik, mechanical alasan: browser tidak pernah kirim fragment (apa pun setelah #) untuk server di HTTP permintaan. server secara harfiah cannot know yang “halaman” (terjemahan) “halaman” adalah requested, so ini dapat’t kembalikan berbeda konten atau sebuah berbeda kode status untuk ini. sebuah crawler itu fetches URL setelah mendapatkan identical HTML regardless dari fragment.

itu’s juga mengapa Google formally deprecated -nya 2009 AJAX-crawling scheme back di October 2015: “Di short: We adalah Tidak lebih lama recommending itu AJAX crawling proposal we membuat back di 2009.” (terjemahan) “di pendek: kami adalah Tidak lebih lama recommending AJAX crawling proposal kami dibuat back di 2009.” old _escaped_fragment_ workaround let server pre-render fragment routes pada permintaan — tetapi ini patched sekitar routing masalah alih-alih memperbaiki ini. Google’s own recommendation replacing ini adalah History API, covered di bawah.

Soft 404s — client-side routers pertahankan sebuah 200 untuk semuanya

«This one is close to inevitable in a bare SPA. Client-side routers, by design, keep the original page’s 200 status for every virtual navigation — including “not found” states. Google flags it 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.” And the why: “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.” » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian dua puluh delapan, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

hasil adalah empty atau error views getting terindeks sebagai thin 200 halaman. Google documents persis dua strategies di sini, precisely: redirect (atau membuat sebuah penuh permintaan) untuk sebuah URL milik siapa server mengembalikan sebuah nyata 404/error status, atau tambahkan sebuah noindex tag untuk error view dengan JavaScript. Watch kedua satu: jika itu noindex adalah present dari very pertama paint alih-alih ditambahkan setelah app decides route adalah tidak valid, ini dapat penyebab Google untuk skip rendering halaman altogether — so verify apa sebuah direct permintaan mengembalikan sebelum apa pun JS berjalan, tidak hanya apa menampilkan up di dirender DOM afterward.

Bukti untuk klaim ini For a client-rendered not-found view, Google documents two approaches: redirect to a URL whose server returns a 404 response, or add noindex with JavaScript; an initial noindex can cause rendering to be skipped, so raw and rendered directives require separate testing. Cakupan: SPAs Tingkat keyakinan: tinggi · Diverifikasi: Fix Search-related JavaScript problems

Shared-shell duplicates — sebuah mungkin penyebab, tidak sebuah automatic diagnosis

«There’s a second, sneakier failure mode: distinct routes get indexed as duplicates of each other because they render down to the same shared header/nav/footer boilerplate. Gary Illyes described one way this 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.” His fix: “Try to restructure the js calls such that the content (including marginal boilerplate) loads first.” (Relayed via a LinkedIn post, which resists automated verification; treat as high-confidence secondary.) » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian tiga puluh satu, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

«Notice Illyes frames it as “my most likely explanation,” not a confirmed diagnosis — that’s worth taking literally. “Render timeout” isn’t something you can diagnose from symptoms alone; you need request-level evidence (what a direct fetch actually returns), rendered-output evidence (whether the centerpiece content is present after rendering), and Search Console evidence (duplicate/indexing signals) before attributing a route’s duplicate-content problem to a timeout specifically, rather than to a bug, a blocked resource, or a genuinely identical shell. There’s also no fixed, published timeout to design around — Google’s own basics doc says a page “may stay on this queue for a few seconds, but it can take longer than that,” without committing to a number. Don’t plan around an assumed render-queue duration; instead, make sure primary content loads as early as possible regardless of how long rendering ends up taking. » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian tiga puluh dua, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

perbaiki: mendapatkan nyata HTML per route

penuh perbaiki memiliki dua independent bagian itu orang conflate constantly:

  1. URL addressability — History API, unique per-route URLs, Tidak fragments.
  2. konten availability — SSR, static prerendering/SSG, atau sebuah meta-kerangka kerja.

melakukan hanya #1 dan Anda mendapatkan bersih, shareable URLs itu semua masih mengembalikan sama blank shell. untuk sebuah route Anda sebenarnya ingin terindeks, Anda perlu keduanya.

itu’s tidak sebuah universal mandate untuk tambahkan SSR di mana-mana, though. SSR dan prerendering reduce bagaimana banyak Anda bergantung pada sebuah crawler successfully executing Anda JavaScript — mereka jangan become mandatory instant sebuah situs adalah technically sebuah SPA. sebelum picking sebuah architecture untuk sebuah diberikan route, periksa three hal: apakah itu route perlu untuk peringkat di semua (sebuah internal admin panel tidak), apa sebuah direct, JS-free permintaan untuk ini sudah mengembalikan (beberapa setups sudah kirim bermakna HTML), dan yang crawler Anda sebenarnya perlu untuk satisfy (Google renders JS fairly reliably; Bing dan sebagian besar AI crawler lebih sedikit so — see di bawah). CSR itu sudah melewati itu memeriksa tidak perlu untuk become SSR hanya karena situs adalah sebuah SPA.

rendering sisi server (SSR)

server berjalan Anda app untuk setiap permintaan dan mengembalikan fully-formed HTML untuk itu route, lalu client “hydrates” (terjemahan) “hydrates” ini ke sebuah langsung SPA. ini adalah paling robust option karena crawler mendapatkan menyelesaikan konten pada pertama fetch, Tidak JS execution diperlukan.

Static prerendering / SSG

alih-alih rendering per permintaan, Anda bangun setiap route’s HTML ahead dari time di deploy. Perfect untuk konten itu tidak perubahan per pengguna. dari my own JavaScript SEO writing: apa pun jenis dari SSR, static rendering, dan prerendering setup adalah going untuk menjadi fine untuk penelusuran mesin — hal untuk hindari adalah leaving konten locked behind client-hanya rendering.

atau: jangan hand-roll ini — gunakan meta-kerangka kerja

untuk sebuah baru bangun, honest recommendation adalah untuk tidak hand-roll React-Router-hanya atau Vue-Router-hanya client-side routing di semua. sebuah meta-kerangka kerja — Berikutnya.js, Nuxt, Angular dengan -nya SSR package, SvelteKit, Remix — memberikan Anda SSR/SSG dan route-based metadata out dari box, yang sidesteps ini entire category dari masalah. (setiap dari itu memiliki -nya own deep dive pada ini situs.) menjadi honest tentang flip side: retrofitting SSR onto sebuah existing hand-rolled SPA adalah nyata engineering berfungsi — sebuah migration project, tidak sebuah config toggle.

Dynamic rendering sebagai sebuah stopgap, tidak sebuah destination

«You can serve crawlers a separately-rendered HTML snapshot (dynamic rendering). Both Google and Bing accept it — Bing “recommend[s] dynamic rendering as a great alternative for websites relying heavily on JavaScript” (relayed from Bing’s 2018 blog; the two short bingbot-capability quotes below are directly verified, this longer one is not independently re-checked) — but Google is clear it’s a 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.” (Relayed from Google’s dynamic-rendering doc; wording matches what’s already cited in this site’s broader JavaScript SEO guide.) It’s a bridge, not an architecture. » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian empat puluh lima, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

«## History API vs. hash routing (#!) » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian empat puluh enam, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

Five status, tidak dua

Testing sebuah SPA route mendapatkan confusing karena “melakukan it berfungsi” (terjemahan) “melakukan ini berfungsi” sebenarnya spans five berbeda, secara terpisah-verifiable status:

StateApa ini adalah
server responsbytes sebuah fresh, JS-free HTTP permintaan untuk sebuah URL sebenarnya mendapatkan back.
Dirender DOMApa sebuah browser (atau Googlebot’s renderer) membangun setelah executing JavaScript terhadap itu server respons.
Penelusuran processingBagaimana Google secara terpisah melakukan crawl server respons, kemudian renders halaman, dan indeks berdasarkan keduanya.
browser penuh-document navigationsebuah sebenarnya baru HTTP permintaan untuk sebuah URL — satu-satunya state itu dapat perubahan server respons atau kode status.
browser soft navigationsebuah History API transition (pushState/replaceState) itu perubahan terlihat URL, browser history, dan pada-screen UI.

satu everyone conflates: sebuah soft navigation perubahan URL dan UI, tetapi ini melakukan tidak oleh itself buat baru respons HTTP atau status — itu hanya happens pada sebuah penuh navigation (atau sebuah equivalent direct permintaan, like curl). Testing sebuah route oleh clicking melalui app dari homepage exercises soft navigation; testing ini oleh requesting URL secara langsung exercises server respons. Keduanya penting, dan mereka dapat disagree.

Apa History API memberikan Anda

Google’s recommendation adalah unambiguous: “We merekomendasikan menggunakan itu History API untuk load berbeda konten based pada itu URL di a SPA.” (terjemahan) “kami merekomendasikan menggunakan History API untuk muat berbeda konten berdasarkan URL di sebuah SPA.” (di langsung doc “History API” (terjemahan) “History API” adalah sebuah tautan, so ini deep tautan targets lead-di clause.) History API (pushState/replaceState) lets Anda router perubahan terlihat URL untuk sebuah nyata, bookmarkable path — /products, tidak /#/products — tanpa sebuah penuh reload. itu memperbaiki addressability half: setiap view now memiliki sebuah URL sebuah server dapat respond untuk differently.

Mengapa fragment/hash URLs adalah invisible untuk server — dan untuk Google

«Because the fragment never reaches the server (see above), the History API is the only way to give each route a URL the server can actually serve. Google puts a floor under it in its 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.” This is the same guidance I’ve written elsewhere — use normal-looking URLs like /products, not hash URLs like /#/products, because Google can’t reliably index the hash ones. » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian lima puluh empat, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

2015 deprecation, briefly

«The hash-bang (#!) era ended with Google’s 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,” and “you can use the History API pushState() to ensure accessibility for a wider range of browsers (and our systems).” One nuance worth keeping: Google didn’t instantly deindex old hash-bang sites — “we’ll generally crawl, render, and index the #! URLs” — but “we can still try” is not “you should still do this.” » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian lima puluh enam, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

membuat setiap route independently dapat diindeks

Bersih URLs dan nyata HTML mendapatkan Anda di-crawl. untuk mendapatkan terindeks correctly, setiap route perlu -nya own sinyal.

Per-route tag canonical — dan “most restrictive directive” (terjemahan) “sebagian besar restrictive directive” trap

«Every route needs its own rel=canonical in the rendered DOM. The trap: if a placeholder canonical (or a noindex) ships in the raw HTML shell and JavaScript is supposed to overwrite it later, you can get a conflict. Google resolves conflicts between the raw and rendered versions by taking the more restrictive signal — as I’ve put it before, Google will choose the most restrictive statements between the HTML and the rendered version of a page. A stray noindex or wrong canonical baked into the shell can silently suppress the whole route even after your JS “fixes” it. » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian enam puluh, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

Per-route judul dan deskripsi meta melalui JavaScript

«Setting these in JS is fine — Google says so directly: “You can use JavaScript to set or change the meta description as well as the <title> element.” The requirement is that they land in the rendered DOM Google evaluates, not just flash briefly. Give each page its own title and description that update when the route changes. » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian enam puluh dua, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

nyata <a href> tautan antara routes

«Make your inter-route links real anchors with href attributes, not click handlers on <div>s. Google discovers URLs by extracting hrefs; a <div onClick> that navigates via the router is invisible to the crawler’s link extraction. And don’t lean on client-side state to carry content across those navigations — Google’s 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.” » (Terjemahan) (Ringkasan bahasa Indonesia untuk bagian enam puluh empat, bagian kecil satu: teks sumber dipertahankan agar dapat diperiksa dalam tinjauan penutur asli.)

Sitemap generation untuk SPA routes

ada Tidak special “SPA sitemap” (terjemahan) “SPA sitemap” format — ini adalah sebuah accounting masalah

sitemap protocol adalah unchanged untuk SPAs. sebenarnya berfungsi adalah membuat sure setiap route Anda list independently resolves untuk nyata, unique, dirender HTML. sebuah sitemap dari 500 client-side routes adalah worthless jika itu routes semua mengembalikan sama shell. So sitemap adalah downstream dari Anda rendering strategy, tidak sebuah substitute untuk ini.

Menangani dynamic/parameterized routes (/product/:id)

untuk apps dengan parameterized routes, Anda dapat’t hand-maintain list. sitemap generator memiliki untuk jalankan terhadap yang sama data sumber app menggunakan — sebuah bangun script atau sebuah server endpoint itu enumerates setiap id — so sitemap dan app tidak pernah drift apart. itu enumerated URLs adalah hanya worth listing setelah mereka’re SSR’d atau prerendered.

Mempertahankan sitemap di sync dengan apa server-resolvable

Regenerate sitemap sebagai bagian dari Anda bangun atau pada sebuah schedule tied untuk Anda konten sumber. sebuah route itu 404s (atau worse, soft-404s di 200) tetapi sits di Anda sitemap adalah sebuah crawl-budget dan quality sinyal Anda tidak ingin.

Testing apa Google sebenarnya sees

jangan spot-periksa satu URL. seluruh poin dari SPA masalah adalah itu routes dapat differ di browser tetapi tidak pada wire, so test multiple routes di mentah-HTML tingkat:

  • Fetch mentah HTML per route dengan curl (sebuah Googlebot pengguna-agent di mana relevant) dan konfirmasi konten adalah unique per URL, tidak shared shell.
  • pemeriksaan URL di Search Console — bandingkan di-crawl/dirender HTML untuk several routes, dan konfirmasi per-route judul, deskripsi, dan canonical adalah present.
  • Konfirmasi error routes mengembalikan right sinyal — sebuah “tidak ditemukan” (terjemahan) “tidak ditemukan” view seharusnya either redirect untuk sebuah nyata error status atau carry noindex di dirender DOM.

umum myths tentang SPA SEO

  • “Google tidak dapat index SPAs di all.” (terjemahan) “Google dapat’t indeks SPAs di semua.” Outdated. Google berjalan evergreen Chromium dan umumnya renders SPA konten. nyata risks adalah spesifik: soft 404s, hash routing, render timeouts, dan non-Google crawler (Bing lebih sedikit reliably, sebagian besar AI crawler tidak di semua).
  • “Adding itu History API memperbaiki SPA SEO.” (terjemahan) “menambahkan History API memperbaiki SPA SEO.” ini memperbaiki addressability hanya. jika server masih mengembalikan yang sama shell untuk setiap route, Google masih memiliki untuk execute JS untuk see apa pun.
  • “Hash routing masih berfungsi sebagai a fallback.” (terjemahan) “Hash routing masih berfungsi sebagai sebuah fallback.” Deprecated since 2015 dan recommended terhadap ever since.
  • “Client-side rendering adalah a ranking penalty.” (terjemahan) “rendering sisi klien adalah sebuah peringkat penalty.” Tidak direct CSR penalty ada. damage adalah indirect — failed/delayed rendering, soft 404s, dan duplicate clustering dari render timeouts reduce apa mendapatkan terindeks.
  • “Pre-rendering untuk bots adalah cloaking.” (terjemahan) “Pre-rendering untuk bot adalah cloaking.” Tidak ketika konten matches apa pengguna eventually see — hanya ketika/di mana dari rendering differs, tidak konten.
  • “Anda perlu a special SPA sitemap generator.” (terjemahan) “Anda perlu sebuah special SPA sitemap generator.” Tidak special format ada; berfungsi adalah membuat setiap listed route resolve untuk nyata HTML.

FAQs

dapat Google indeks sebuah single-halaman application? Ya, jika setiap route resolves untuk nyata, unique HTML (melalui SSR/prerendering) di sebuah nyata URL. Bare client-hanya SPAs sering jangan.

melakukan I perlu SSR, atau adalah rendering sisi klien ever okay? CSR dapat berfungsi untuk konten itu tidak perlu untuk peringkat, tetapi untuk apa pun Anda ingin terindeks reliably, prerender atau SSR ini — jangan bet pada renderer executing Anda JS di time.

adalah hash (#!) routing buruk untuk SEO? Ya — fragment tidak pernah reaches server, so Google dapat’t reliably resolve itu URLs. gunakan History API.

melakukan setiap route perlu -nya own tag canonical? Ya, di dirender DOM — dan membuat sure tidak ada apa pun lebih restrictive (sebuah stray noindex atau wrong canonical) ships di mentah shell.

adalah sebuah prerendering service cloaking? Tidak, disediakan konten disajikan untuk bot matches apa pengguna see.

seharusnya I gunakan meta-kerangka kerja alih-alih membangun routing myself? untuk sebuah baru bangun, biasanya Ya — Berikutnya.js/Nuxt/Angular-SSR/SvelteKit memberikan Anda SSR/SSG dan per-route metadata untuk free.

Mengapa melakukan my SPA kembalikan 200 untuk halaman itu jangan exist? Karena client-side router mempertahankan original 200 status untuk virtual navigations. Perbaiki ini dengan sebuah JS redirect untuk sebuah nyata error status atau sebuah dirender noindex.

Tambahkan catatan pakar

Sematkan kutipan pakar

Orang baru? Buat profilnya yang belum diklaim di /admin/experts/ → Sematkan kutipan pakar terlebih dahulu.