SEO Teknis untuk Developer: Canonical, hreflang, JSON-LD

Foto oleh Agence Olloweb

Foto oleh Agence Olloweb
SEO teknis punya masalah reputasi di kalangan developer: terdengar seperti marketing, jadi kita mengabaikannya sampai seseorang bertanya kenapa versi Indonesia sebuah halaman malah ranking di Jerman, atau kenapa Google mengindeks domain staging. Padahal tag canonical, hreflang, structured data, dan sitemap bukan marketing — mereka kontrak level protokol antara HTML Anda dan crawler. Salah mengerjakannya adalah bug, dengan akar penyebab yang sama seperti bug lainnya.
Checklist ini lahir dari merilis portfolio ini sendiri — situs Next.js dwibahasa Inggris-Indonesia dengan ratusan artikel blog — plus beberapa proyek klien. Semua di sini adalah separuh developer dari SEO: hal-hal yang Anda perbaiki di kode, verifikasi dengan curl, dan uji regresi di CI. Tanpa riset keyword, tanpa strategi konten, hanya pipa-pipa yang menentukan apakah konten Anda bahkan layak untuk ranking.
Setiap halaman di situs Anda bisa dicapai lewat lebih banyak URL daripada yang Anda kira: dengan dan tanpa trailing slash, dengan parameter UTM, lewat http sebelum redirect, kadang lewat route alias dan route asli sekaligus. Bagi Google itu halaman-halaman terpisah yang saling bersaing, memecah sinyal ranking dan membakar crawl budget. Tag canonical adalah cara Anda mendeklarasikan alamat mana yang asli — dan menurut dokumentasi Google, redirect adalah sinyal yang lebih kuat lagi, dengan pencantuman di sitemap sebagai yang terlemah.
Di App Router implementasi bersihnya adalah field alternates pada Metadata API, dihitung di generateMetadata sehingga setiap route dinamis mendeklarasikan dirinya. Dua aturan yang saya tegakkan: canonical selalu URL absolut, dan setiap versi locale adalah canonical-nya sendiri. Mengarahkan canonical halaman Indonesia ke halaman Inggris adalah kesalahan klasik — itu memberi tahu Google konten Indonesia adalah duplikat yang harus dibuang, kebalikan persis dari yang diinginkan situs dwibahasa.
// app/[locale]/blog/[slug]/page.tsx — canonical + hreflang in one place
export async function generateMetadata({ params }): Promise<Metadata> {
const { locale, slug } = await params
const base = "https://example.com"
return {
title: post.title,
description: post.description,
alternates: {
// ONE canonical per language version — not one shared
// canonical pointing every locale at English.
canonical: `${base}/${locale}/blog/${slug}`,
languages: {
en: `${base}/en/blog/${slug}`,
id: `${base}/id/blog/${slug}`,
// safety net for every unmatched visitor:
"x-default": `${base}/en/blog/${slug}`,
},
},
}
}hreflang memberi tahu Google versi bahasa mana saja yang ada untuk sebuah halaman sehingga pencari berbahasa Indonesia diarahkan ke /id/ dan lainnya ke /en/. Aturan implementasi dari dokumentasi Google cukup ketat sampai kebanyakan situs salah setidaknya satu:
Kesalahan yang pernah saya buat di versi awal situs ini: anotasi hreflang saya dihasilkan di satu komponen, tapi sebuah refactor membuat satu template hanya mengeluarkan link locale saat itu. Tanpa error, tanpa peringatan — hreflang diam-diam batal di halaman-halaman itu karena kontrak dua arahnya putus. Perbaikan yang awet bersifat struktural: hasilkan canonical dan peta languages lengkap dari satu helper, sehingga sebuah halaman tidak bisa mendeklarasikan yang satu tanpa yang lain.
Kegagalan hreflang tak terlihat di browser maupun di Lighthouse. Satu-satunya tempat ia muncul adalah laporan international targeting di Search Console dan inspeksi HTML mentah. Kalau Anda belum pernah curl halaman produksi dan membaca tag link-nya dengan mata sendiri, Anda tidak tahu hreflang Anda bekerja.
Structured data adalah cara memberi tahu Google apa sebuah halaman itu, bukan hanya apa isinya — ini adalah Article, oleh Person ini, terbit pada tanggal ini. Google merekomendasikan format JSON-LD di atas microdata, dan di Server Component biayanya nol: bangun object polos, serialisasikan ke tag script, selesai. Tanpa JavaScript client, tanpa library.
// JSON-LD in a Server Component — no client JS needed
export default async function BlogPost({ params }) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "Article",
headline: post.title,
datePublished: post.datePublished,
author: { "@type": "Person", name: "Matthews Wong" },
image: `https://example.com${post.image}`,
}
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>...</article>
</>
)
}Jujurlah dalam markup. Kelayakan rich result bergantung pada properti wajib per tipe schema, dan Google memvalidasi dengan agresif — menandai konten dengan rating yang tidak dimilikinya atau kepengarangan yang tidak bisa ditunjukkan adalah cara situs mendapat manual action. Saya menjaganya membosankan: Article untuk artikel, Person untuk halaman about, BreadcrumbList di tempat UI sungguh menampilkan breadcrumb. Validasi setiap template di Rich Results Test sebelum rilis, karena satu blok JSON-LD cacat diam-diam mendiskualifikasi seluruh halaman.
App Router mengubah sitemap dari artefak build yang menyebalkan menjadi fungsi bertipe: app/sitemap.ts mengekspor daftar URL Anda, dihasilkan dari sumber data yang sama yang merender halamannya — di situs ini, registry blog memberi makan keduanya, sehingga artikel baru tidak bisa ada tanpa entri sitemap. Sertakan lastModified yang jujur dari tanggal konten asli; crawler memakainya untuk memprioritaskan crawl ulang, dan sitemap di mana semuanya berubah hari ini terbaca sebagai noise.
robots.txt lewat app/robots.ts mengikuti pola sama. Jaga minimal: tunjuk ke sitemap, blokir path yang sungguh tak berguna seperti API route, dan jangan pernah pakai robots.txt untuk menyembunyikan konten duplikat — halaman yang diblokir tidak bisa di-crawl, artinya Google tidak akan pernah melihat tag canonical yang Anda taruh di sana. Disallow dan canonical adalah alat yang saling eksklusif, kehalusan yang langsung berasal dari dokumentasi konsolidasi Google.
Empat item yang mutlak tugas developer, dan rusak di kebanyakan situs yang saya audit:
Status code yang jujur
Halaman yang hilang harus mengembalikan 404, bukan komponen error bergaya dengan HTTP 200. Soft 404 mencemari indeks, dan rantai redirect membocorkan sinyal — pemindahan permanen mendapat satu 301, bukan 302 sisa testing.
Satu H1 dan pohon heading sungguhan
Crawler merekonstruksi struktur dokumen dari heading, sama seperti screen reader. Audit aksesibilitas dan audit SEO bertemu di sini: memperbaiki hierarki heading melayani dua tuan sekaligus, gratis.
Metadata untuk ekonomi link preview
Tag OpenGraph dan Twitter card menentukan bagaimana setiap share di WhatsApp dan LinkedIn dirender. Untuk audiens Indonesia, di mana berbagi link via WhatsApp mendominasi trafik, og-image yang hilang terukur menekan click-through.
Core Web Vitals sebagai input ranking
Sinyal page experience masuk ke ranking. Kerja performa — LCP, CLS, INP — bukan hal terpisah dari SEO; ia adalah bagian SEO yang hidup sepenuhnya di codebase Anda.
SEO teknis adalah permukaan kontrak, dan kontrak adalah keahlian kita. Canonical mendeklarasikan identitas, hreflang mendeklarasikan perutean bahasa dengan aturan timbal balik yang ketat, JSON-LD mendeklarasikan makna, dan sitemap mendeklarasikan inventaris. Tidak ada yang butuh intuisi marketing — yang dibutuhkan disiplin yang sama dengan API: hasilkan dari satu sumber kebenaran, validasi di CI, verifikasi di produksi dengan curl. Kerjakan pipa membosankan itu sekali, dan konten Anda bersaing berdasarkan mutunya yang sebenarnya.
Tambahkan langkah CI yang mengambil tiga halaman ter-render — home, satu artikel terlokalisasi di tiap bahasa — dan melakukan assert pada canonical, kelengkapan hreflang, dan keterbacaan JSON-LD. Menulisnya butuh satu jam dan sejak itu menangkap setiap regresi SEO di situs ini.