Lompat ke konten
dokumentasi.id
Commit 120a248
v1
Star0

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

10

SEO dan Metadata

Semua metadata situs berakar pada satu modul bebas dependensi, src/lib/seo.ts. Modul itu menyimpan identitas situs dan menurunkannya menjadi Metadata, BreadcrumbList, serta URL absolut yang dipakai bersama oleh layout, halaman, sitemap, robots, dan manifest src/lib/seo.ts:5-9.

Peta identitas situs ke keluaran metadata

graph TD
  URL["SITE_URL"]
  NAME["SITE_NAME"]
  TAG["SITE_TAGLINE"]
  DESC["SITE_DESCRIPTION"]
  OG["baseOpenGraph"]
  ABS["absoluteUrl(path)"]
  PM["pageMetadata()"]
  LAY["metadata di layout.tsx"]
  SMM["sitemap.ts"]
  RBT["robots.ts"]
  MFT["manifest.ts"]
  JLD["JsonLd dan objek JSON-LD"]
  URL --> ABS
  URL --> LAY
  ABS --> SMM
  ABS --> RBT
  ABS --> JLD
  NAME --> OG
  NAME --> PM
  NAME --> MFT
  NAME --> LAY
  NAME --> JLD
  TAG --> LAY
  DESC --> LAY
  DESC --> MFT
  OG --> PM
  OG --> LAY
  PM --> LAY
  style URL fill:#f59e0b,color:#000
  style NAME fill:#f59e0b,color:#000
  style TAG fill:#f59e0b,color:#000
  style DESC fill:#f59e0b,color:#000
  style OG fill:#8b5cf6,color:#fff
  style ABS fill:#8b5cf6,color:#fff
  style PM fill:#8b5cf6,color:#fff
  style LAY fill:#2d7d46,color:#fff
  style SMM fill:#2d7d46,color:#fff
  style RBT fill:#2d7d46,color:#fff
  style MFT fill:#2d7d46,color:#fff
  style JLD fill:#2d7d46,color:#fff
%% fig: Identitas situs dan keluaran metadata
%% desc: Empat konstanta identitas situs dipakai langsung oleh layout, manifest, JSON-LD, dan pageMetadata, lalu absoluteUrl menurunkan URL absolut untuk sitemap, robots, dan BreadcrumbList.
Fig. 1: Identitas situs dan keluaran metadataEmpat konstanta identitas situs dipakai langsung oleh layout, manifest, JSON-LD, dan pageMetadata, lalu absoluteUrl menurunkan URL absolut untuk sitemap, robots, dan BreadcrumbList.

Diagram itu menunjukkan arah ketergantungan yang sebenarnya: tidak ada keluaran metadata yang menulis nama situs atau domain sendiri, semuanya membaca konstanta di src/lib/seo.ts src/lib/seo.ts:5-9. Tabel berikut memerinci nilai tiap konstanta dan pemakainya.

KonstantaNilaiPemakai utama
SITE_URLhttps://quran-academy.alfath7.workers.devmetadataBase di layout, absoluteUrl, @id JSON-LD
SITE_NAMEFathway Qurantemplate judul layout, baseOpenGraph, manifest, provider JSON-LD
SITE_TAGLINEBelajar Tajwid & Baca Al-Qur'an OnlinehomeTitle di layout, alt gambar Open Graph
SITE_DESCRIPTIONkalimat pembuka Belajar membaca Al-Qur'an dari huruf hijaiyah…deskripsi layout, deskripsi manifest, deskripsi EducationalOrganization

pageMetadata: satu halaman, satu objek Metadata

pageMetadata menerima title, description, path, dan noIndex opsional, lalu mengembalikan objek Metadata yang lengkap src/lib/seo.ts:33-45. Isinya: title apa adanya, description yang sudah dipotong, alternates.canonical berisi path, openGraph hasil sebaran baseOpenGraph ditambah judul penuh, twitter dengan kartu summary_large_image, dan blok robots hanya bila noIndex dikirim.

Field hasilNilai yang ditulisSumber
titletitle dari pemanggil, tanpa nama situsnama situs ditambahkan template layout
descriptiontruncate(description), bawaan maksimum 160 karaktertruncate
alternates.canonicalpath relatifdiselesaikan terhadap metadataBase
openGraph.title${title} · ${SITE_NAME}fullTitle
openGraph.descriptionsummary yang sama dengan descriptiontruncate
twitter.cardsummary_large_imagetetap
robots{ index: false, follow: true }hanya bila noIndex benar

Judul sengaja dikembalikan tanpa nama situs karena template root layout %s · ${SITE_NAME} yang menambahkannya, sedangkan kartu sosial membawa nama situs sendiri lewat fullTitle src/lib/seo.ts:33-45. Template itu dipasang di metadata layout bersama default: homeTitle, metadataBase: new URL(SITE_URL), daftar keywords, authors, category: "education", serta robots yang mengizinkan index dan follow dengan max-image-preview: "large" dan max-snippet: -1 src/app/layout.tsx:28-41.

Pemotongan deskripsi dilakukan truncate, yang lebih dulu merapatkan spasi, mengembalikan teks apa adanya bila panjangnya tidak melewati batas, dan bila melewati batas memotong di batas kata lalu menambahkan … src/lib/seo.ts:22-27.

Siklus satu halaman yang punya data dinamis berjalan seperti ini.

sequenceDiagram
  participant GM as generateMetadata
  participant PM as pageMetadata
  participant TR as truncate
  participant NX as Metadata Next
  GM->>PM: title, description, path
  PM->>TR: description
  TR-->>PM: summary maksimum 160 karakter
  PM-->>NX: title, description, canonical, openGraph, twitter
  NX->>NX: template %s ditambah nama situs dari layout
%% fig: Siklus pembentukan metadata satu halaman
%% desc: generateMetadata menyerahkan judul dan deskripsi, pageMetadata memotong deskripsi lewat truncate, lalu Next menyusun tag akhir dengan template judul dari root layout.
Fig. 2: Siklus pembentukan metadata satu halamangenerateMetadata menyerahkan judul dan deskripsi, pageMetadata memotong deskripsi lewat truncate, lalu Next menyusun tag akhir dengan template judul dari root layout.

Pemanggilnya terbagi dua pola. Halaman daftar menetapkan metadata statis: /pemula src/app/pemula/page.tsx:11-15, /lanjutan src/app/lanjutan/page.tsx:11-15, dan /tes src/app/tes/page.tsx:11-15. Halaman detail memakai generateMetadata karena judul dan deskripsi diambil dari data: latihan Pemula src/app/pemula/[slug]/page.tsx:20-24, materi Lanjutan src/app/lanjutan/[slug]/page.tsx:26-30, dan kategori tes src/app/tes/[kategori]/page.tsx:20-30. Beranda adalah pengecualian: ia hanya menambah alternates: { canonical: "/" } dan mewarisi sisanya dari layout src/app/page.tsx:13.

Kartu sosial dibentuk dari baseOpenGraph yang memuat siteName, locale: "id_ID", type: "website", dan satu gambar og-image.png berukuran 1200×627 dengan alt gabungan nama situs dan tagline src/lib/seo.ts:11. Objek itu dipakai ulang oleh layout dan oleh setiap halaman lewat pageMetadata src/lib/seo.ts:14-19.

⚠️ Objek openGraph milik satu halaman mengganti seluruh objek induknya, bukan menimpanya sebagian. Karena itu baseOpenGraph wajib disebar ulang di setiap pemanggil; menghapus sebaran itu membuat siteName, locale, dan gambar Open Graph hilang src/lib/seo.ts:14-19.

⚠️ SITE_DESCRIPTION di root layout dan manifest tidak melewati truncate, jadi panjangnya utuh. Hanya deskripsi yang lewat pageMetadata yang dipotong ke 160 karakter src/lib/seo.ts:22-27.

Canonical dan URL absolut

Nilai canonical dan openGraph.url selalu ditulis sebagai path relatif, misalnya /lanjutan/${lesson.slug}, dan diselesaikan terhadap metadataBase di layout src/app/layout.tsx:28-41. Untuk tempat yang butuh string utuh, absoluteUrl menggabungkan path dengan SITE_URL memakai new URL src/lib/seo.ts:47. Fungsi itu dipakai sitemap.ts, robots.ts, logo JSON-LD beranda, dan item pada BreadcrumbList.

sitemap, robots, dan manifest

sitemap() membangun entri dari helper entry(path, priority) yang menyimpan url: absoluteUrl(path) dan priority saja — tanpa lastModified maupun changeFrequency src/app/sitemap.ts:7-17.

Path di sitemappriorityAsal entri
/1literal
/lanjutan0.9literal
/pemula0.9literal
/tes0.6literal
/tes/{kategori}0.5materiCategories, saat ini hanya tajwid
/pemula/{slug} dan /lanjutan/{slug}0.8listAllLessons() lewat lessonHref

Daftar kategori materi berasal dari materiCategories = ["tajwid"] src/lib/categories.ts:7, sedangkan lessonHref memilih /pemula/{slug} untuk latihan dan /lanjutan/{slug} untuk tajwid src/lib/categories.ts:31-33. Sumber seluruh materi adalah listAllLessons() yang menggabungkan latihan lebih dulu, lalu pertemuan tajwid src/lib/lessons.ts:33-35.

robots() mengizinkan seluruh path untuk userAgent: "*" dan mencantumkan sitemap sebagai absoluteUrl("/sitemap.xml") src/app/robots.ts:4-9. manifest() memakai SITE_NAME sebagai name, Fathway sebagai short_name, SITE_DESCRIPTION sebagai description, lang: "id", start_url: "/", display: "standalone", warna #f7f2e8 dan #0d4a3a, serta dua ikon /icon.svg dan /apple-icon.png berukuran 512×512 src/app/manifest.ts:4-18. Warna tema untuk browser diatur terpisah lewat viewport, dengan #f7f2e8 untuk mode terang dan #0e1714 untuk mode gelap src/app/layout.tsx:43-48.

JSON-LD

Semua structured data dicetak oleh komponen JsonLd, yang menulis <script type="application/ld+json"> dan meng-escape setiap < menjadi \u003c supaya isi konten tidak bisa menutup tag skrip src/components/json-ld.tsx:1-4.

HalamanTipe schema.orgObjek sumber
BerandaEducationalOrganization dan WebSite dalam satu @graphsiteJsonLd, dengan @id #organization dan #website
BerandaFAQPagefaqJsonLd dari daftar faqs
Materi LanjutanLearningResource dan BreadcrumbListjsonLd di halaman detail

Beranda menyusun EducationalOrganization dengan logo: absoluteUrl("/apple-icon.png") dan description: SITE_DESCRIPTION, lalu WebSite yang menunjuk publisher ke @id organisasi; keduanya dirender bersama faqJsonLd sebagai dua pemanggilan JsonLd di awal halaman src/app/page.tsx:15-35 src/app/page.tsx:125-133 src/app/page.tsx:143-144.

Halaman materi menambahkan LearningResource berisi name, description, url absolut, inLanguage: "id-ID", learningResourceType: "Materi", educationalLevel: "Pemula", teaches dari label kategori, isAccessibleForFree, dan provider yang memakai SITE_NAME serta SITE_URL src/app/lanjutan/[slug]/page.tsx:50-67. Kedua objek itu dikirim sekaligus dalam satu array ke JsonLd.

⚠️ Nilai educationalLevel pada halaman materi ditulis tetap "Pemula" walaupun halaman berada di bawah /lanjutan, jadi nilai itu tidak membedakan tingkat kesulitan materi src/app/lanjutan/[slug]/page.tsx:50-67.

Breadcrumb dibangun breadcrumbJsonLd dari pasangan [name, path] dengan beranda di urutan pertama; setiap path diubah menjadi URL absolut lewat absoluteUrl dan position dihitung dari indeks src/lib/seo.ts:50-55. Halaman materi mengirim tiga pasang: Beranda, label kategori, dan judul materi src/app/lanjutan/[slug]/page.tsx:65.

Yang perlu dijaga saat menambah halaman