Layout, slot & kepala halaman
Di CI3, header dan footer adalah dua view terpisah yang harus kamu ingat memanggil keduanya. Layout menghapus seluruh kelas kesalahan itu, dan sekalian jadi tempat yang benar untuk meta SEO.
Intisari
- Layout adalah komponen biasa yang kebetulan berisi
<html>dan satu<slot />. - Karena membungkus, mustahil menghasilkan halaman tanpa penutup โ tidak seperti dua view terpisah.
- Meta SEO, URL kanonik, dan JSON-LD tinggal di layout, diisi lewat props. Satu tempat, bukan tiga puluh.
- Layout boleh bertingkat:
DasarโArtikelโ halaman. Tiap tingkat menambah satu hal. Astro.urldi layout memberimu URL kanonik gratis โ tapi tidak di dalam server island.
Layout dasar
---
// src/layouts/Dasar.astro
interface Props {
judul: string;
deskripsi: string;
gambar?: string;
noindex?: boolean;
}
const { judul, deskripsi, gambar, noindex = false } = Astro.props;
const kanonik = new URL(Astro.url.pathname, Astro.site).href;
---
<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{judul}</title>
<meta name="description" content={deskripsi} />
<link rel="canonical" href={kanonik} />
{noindex && <meta name="robots" content="noindex, nofollow" />}
<meta property="og:title" content={judul} />
<meta property="og:description" content={deskripsi} />
<meta property="og:url" content={kanonik} />
{gambar && <meta property="og:image" content={gambar} />}
<slot name="kepala" />
</head>
<body>
<slot name="header"><HeaderDefault /></slot>
<main><slot /></main>
<Footer />
</body>
</html>
<slot name="kepala" /> itu yang paling berguna. Ia memberi halaman tempat
untuk menyuntikkan sesuatu ke <head> โ JSON-LD, preload gambar sampul, tautan RSS โ
tanpa layout perlu tahu apa isinya. Tanpa slot ini kamu akan tergoda menambahkan prop baru ke layout
setiap kali ada satu halaman yang butuh satu tag tambahan, dan layout-mu akan punya dua belas prop
opsional dalam setahun.
Layout bertingkat
---
// src/layouts/Artikel.astro โ menambah struktur artikel di atas Dasar
import Dasar from "./Dasar.astro";
import type { Artikel } from "@/types/artikel";
interface Props { artikel: Artikel; }
const { artikel } = Astro.props;
const jsonLd = {
"@context": "https://schema.org",
"@type": "NewsArticle",
headline: artikel.judul,
datePublished: artikel.terbitPada.toISOString(),
dateModified: (artikel.diubahPada ?? artikel.terbitPada).toISOString(),
author: { "@type": "Person", name: artikel.penulis },
isAccessibleForFree: !artikel.premium,
};
---
<Dasar
judul={`${artikel.judul} โ Portal Contoh`}
deskripsi={artikel.ringkasan ?? artikel.judul}
gambar={artikel.gambarSampul ?? undefined}
>
<script type="application/ld+json" slot="kepala" set:html={JSON.stringify(jsonLd)} />
<article>
<h1>{artikel.judul}</h1>
<p class="byline">{artikel.penulis} ยท <time datetime={artikel.terbitPada.toISOString()}>
{formatTanggal(artikel.terbitPada)}
</time></p>
<slot />
</article>
</Dasar>
Halaman [slug].astro tinggal memanggil <Artikel artikel={artikel}> dan
mengisi badannya. JSON-LD, kanonik, Open Graph, dan byline sudah beres โ dan kalau formatnya perlu
diperbaiki, satu berkas yang disunting, bukan tiga puluh.
isAccessibleForFree bukan hiasan. Untuk portal berita berbayar, inilah cara
memberitahu Google bahwa artikel ini di balik paywall โ supaya Googlebot boleh mengindeks isi lengkapnya
tanpa kamu dianggap melakukan cloaking. Kalau kamu menyajikan isi berbeda ke crawler tanpa
penanda ini, itu pelanggaran yang bisa menghapus portalmu dari indeks. Dibahas tuntas di Fase 9.
Slot dengan isi cadangan
<slot name="header">
<!-- Dipakai kalau pemanggil tidak mengisi slot "header" -->
<HeaderDefault />
</slot>
Pola ini menggantikan $this->load->view($header_custom ?? 'header') di CI3, tanpa risiko
memuat berkas yang tidak ada.
Memeriksa apakah slot terisi
---
const adaSamping = Astro.slots.has("samping");
---
<div class={adaSamping ? "dua-kolom" : "satu-kolom"}>
<main><slot /></main>
{adaSamping && <aside><slot name="samping" /></aside>}
</div>
Berguna supaya kamu tidak merender <aside> kosong yang tetap memakan lebar kolom di
CSS grid.
Satu jebakan yang akan menggigit di Fase 5
Astro.url di dalam server island bukan URL halaman. Ia bernilai
/_server-islands/<NamaKomponen>, karena island memang diambil lewat request terpisah
ke endpoint itu. Kalau layout atau komponenmu menghitung URL kanonik dari Astro.url dan
komponen itu dipakai di dalam island, hasilnya salah. URL halaman aslinya ada di header
Referer. Ini penyebab bug kanonik yang sangat sulit dilacak โ catat sekarang, kamu akan
bertemu lagi di Fase 5.
Di mana CSS global tinggal
---
// di layout paling luar saja
import "@/styles/global.css";
---
Impor CSS di layout terluar, sekali. Astro akan menggabungkan, meng-hash, dan menyisipkannya. Jangan mengimpor CSS global di banyak komponen โ ia tidak digandakan, tapi membuat asal-usulnya sulit dilacak saat ada aturan yang saling menimpa.
Latihan: susun Dasar.astro dan Artikel.astro bertingkat seperti di
atas. Render satu artikel palsu, lalu buka view source dan verifikasi tiga hal: URL kanonik
absolut dan benar, JSON-LD ada di dalam <head>, dan og:image tidak
muncul sama sekali saat artikel tidak punya gambar sampul โ bukan muncul sebagai
content="undefined".
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.