Fitur & Build Notes · · 3 menit baca
Stack SEO + GEO situs ini: sitemap, hreflang, JSON-LD, llms.txt
Lima lapisan supaya situs ditemukan Google DAN mesin AI generatif: hreflang resiprokal dwibahasa, sitemap yang digenerate dari router, FAQPage JSON-LD dari data tampilan, robots.txt ramah bot AI, dan llms.txt.
SEO teknis situs ini dibangun untuk dua pembaca sekaligus: crawler Google klasik dan mesin AI generatif (ChatGPT, Claude, Perplexity) yang kini ikut menentukan siapa yang ditemukan. Yang kedua ini sering disebut GEO (Generative Engine Optimization). Semua lapisannya digenerate dari kode — tidak ada yang ditulis tangan dua kali.
Peta lapisannya
| Lapisan | Untuk siapa | Di mana |
|---|---|---|
| Canonical + hreflang | Google (situs dwibahasa) | <head> setiap halaman |
| Sitemap dinamis | Semua crawler | /sitemap.xml |
| JSON-LD (Person, Service, FAQ, TechArticle) | Rich results + mesin AI | <script type="application/ld+json"> |
| robots.txt dengan allow eksplisit bot AI | Crawler AI | /robots.txt |
| llms.txt | Mesin AI generatif | /llms.txt |
1. Hreflang resiprokal untuk situs dwibahasa
Aturan yang sering dilanggar orang: hreflang harus resiprokal — halaman ID menunjuk EN, dan EN harus balik menunjuk ID, atau Google mengabaikannya. Karena path di sini locale-netral, satu fungsi menjamin resiprositas:
// src/lib/i18n.ts — dipanggil dari <head> setiap halaman mirror
export function hreflangAlternates(neutralPath: string): AltLink[] {
return [
{ lang: "id", path: href("id", neutralPath) }, // /services
{ lang: "en", path: href("en", neutralPath) }, // /en/services
{ lang: "x-default", path: href("id", neutralPath) }, // ID = bahasa utama
];
}
2. Sitemap yang tidak pernah basi
Sitemap dirakit dari sumber kebenaran yang sama dengan router — daftar route statis + query D1 untuk post yang terbit + registry docs. Menambah dokumentasi baru otomatis menambah entri sitemap:
const publicSitemapRoutes = [
{ path: "/", priority: "1.0" },
// ...halaman utama kedua bahasa...
...docs.map((d) => ({ path: "/docs/" + d.slug, priority: "0.6" })),
];
seoRoutes.get("/sitemap.xml", async (c) => {
/* + post blog dari D1, dengan <lastmod> dari updated_at */
});
3. JSON-LD dari data yang sama dengan tampilan
Prinsipnya: schema tidak pernah ditulis manual — ia digenerate dari data yang juga merender halaman, jadi keduanya mustahil tidak sinkron. Contoh FAQPage (format yang paling sering dikutip mesin AI):
// src/lib/schema.ts
export function faqPageJSON(items: Array<{ q: string; a: string }>, locale = "id") {
return marshal({
"@context": "https://schema.org",
"@type": "FAQPage",
inLanguage: locale,
mainEntity: items.map((f) => ({
"@type": "Question",
name: f.q,
acceptedAnswer: { "@type": "Answer", text: f.a },
})),
});
}
Halaman /services memanggilnya dengan array FAQ yang sama yang dirender jadi <details> — satu sumber, dua keluaran.
4. llms.txt: peta situs untuk mesin AI
llms.txt (format llmstxt.org) adalah ringkasan terkurasi untuk model bahasa: siapa kamu, fakta kunci yang ingin kamu sebarkan (harga, proses, kontak), dan link ke halaman penting. Ditambah robots.txt yang meng-allow eksplisit GPTBot, ClaudeBot, PerplexityBot, dan kawan-kawan — dapat ditemukan oleh AI adalah strategi distribusi, bukan kebocoran.
5. Jangan lupa fondasinya: konten tanpa JavaScript
Semua meta di atas percuma kalau crawler mendapat halaman kosong. Situs ini dirender penuh di server; animasi reveal di-gate dengan class .js sehingga tanpa JavaScript pun konten terlihat utuh — dan itu diverifikasi otomatis di setiap rilis oleh scripts/verify.mjs.
Pelajaran
- Generate semua meta dari sumber data yang sama dengan tampilannya — sinkronisasi manual selalu kalah.
- Hreflang tanpa resiprositas = tidak ada hreflang.
- GEO bukan trik baru: konten server-rendered + terstruktur + fakta yang jelas adalah 90% pekerjaannya.
Mau fitur seperti ini dibangun untuk bisnismu?