โ† Semua pembelajaran / Astro Nol โ†’ Portal Berita
Fase 1 ยท Routing, Layout & Model Rendering

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.

Sumber asli docs.astro.build Resmi Rangkuman ~7 menit baca

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.url di 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.