Step setting gambar untuk astro framework
Astro native pada saat menghandle gambar ada dua cara, satu melalui /src/assets dan satu lagi /public yang membedakan astro default untuk gambar sudah dioptimasi ke .webp dan hanya terjadi difolder /assets jadi folder /public tidak terjadi optimasi, lalu apa yang terjadi? could be mess for Lighthouse Pagespeed.
disinilah peran stack ini yang saya develop dengan DeepSeek hari ini dari jam 2 pagi, tentu dalam perjalannya tidak selalu lancar. saya ga bilang ini stack sudah sempurna karena keterbatasan ilmu dan tools saya.
tapi kenapa saya maksa untuk pakai stack ini? ini semua karena saya akan menulis dari pages CMS yang mana untuk gambar folder output harus selalu /public dan disaat bersamaan lighthouse eksis, alasan lainnya saya mau jual ini template kepada potensialy non dev.
jadi dengan setting ini harapannya adalah:
- pakai Pages CMS untuk menulis, ini termasuk post dan upload gambar dan pages CMS harus pakai folde /public.
- gambar harus bisa optimasi ke .webp dan mendapat srcset karena berhubungan dengan score lighthouse.
- gambar harus bisa optimasi ke .avif dan .webp fallback ke file asli dan harus dapet srcset jadi setiap user bisa dapet size gambar sesuai device yang dipakai.
oke jadi ini Dia stacknya.
ALUR GAMBAR DI FASTNEWS — DARI UPLOAD SAMPAI TAMPIL
====================================================
Dokumen ini menjelaskan alur lengkap gambar di proyek fastnews: stack yang
terlibat, plugin yang dipakai, dan perjalanan gambar mulai dari di-upload
lewat Pages CMS hingga dirender sebagai <img> dengan srcset AVIF/WebP di
browser. Tidak ada Sätteri untuk body gambar; Sätteri hanya dipakai untuk
mengubah <img> di HTML hasil Markdown agar srcset-nya terpasang. Sharp
bekerja sekali di prebuild.
1. STACK YANG TERLIBAT
----------------------
Komponen Peran
-------------------- ------------------------------------------------
Pages CMS UI untuk upload gambar dan menulis post
Sharp Generate varian AVIF/WebP + fallback WebP
Manifest JSON Peta path asli -> varian (dibaca dua konsumen)
ResponsiveImg.astro Konsumen manifest untuk HERO (frontmatter)
hast-responsive-... Konsumen manifest untuk BODY (Markdown)
Sätteri Prosesor Markdown Astro 7, jalankan HAST plugin
Astro Framework statis, build HTML
Cloudflare Pages Hosting statis, sajikan /opt/* dan /uploads/*
Browser Pilih varian dari srcset berdasarkan viewport
2. FILE YANG TERLIBAT
---------------------
.pages.yml
- Konfigurasi upload gambar: media.input & media.output
- Konfigurasi koleksi post
scripts/build-image-manifest.mjs
- Satu-satunya tempat Sharp dipanggil
- Scan public/uploads/, hasilkan varian di public/opt/
- Tulis image-manifest.json
image-manifest.json
- Hasil prebuild, tidak di-commit
- Berisi entry per gambar: sources[], fallback, width, height
public/uploads/
- File asli dari CMS
- URL publik: /uploads/namafile.jpg
public/opt/
- Output Sharp, tidak di-commit
- URL publik: /opt/namafile-480.avif, dst
src/components/ResponsiveImg.astro
- Baca manifest, render <picture> untuk hero
- Fallback ke <img> jika entry tidak ada
src/lib/hast-responsive-images.mjs
- Plugin HAST Sätteri
- Baca manifest, TAMBAH srcset/sizes ke <img> yang sudah ada
- Tidak mengganti node (menghindari srcset hilang)
src/lib/manifest.ts
- Fungsi getManifest() dengan cache
- Dipakai ResponsiveImg.astro
astro.config.mjs
- markdown.processor: satteri({ hastPlugins: [...] })
- image.remotePatterns (untuk URL eksternal Unsplash, opsional)
3. ALUR LENGKAP — UPLOAD VIA PAGES CMS
---------------------------------------
LANGKAH 1 — User upload gambar di Pages CMS
---------------------------------------------
Di editor post, user klik "Upload image" dan pilih file dari HP.
Pages CMS membaca .pages.yml:
media:
input: public/uploads
output: /uploads
File disimpan ke public/uploads/namafile.webp.
Pages CMS otomatis menulis markdown di body post:

Path yang ditulis adalah media.output (/uploads/...), tapi file fisiknya
ada di public/uploads/. Kedua sisi konsisten karena folder public/
disajikan apa adanya oleh Astro di URL /.
LANGKAH 2 — prebuild jalan
---------------------------
npm run build memicu prebuild:
"scripts": {
"prebuild": "node scripts/build-image-manifest.mjs",
"build": "astro build"
}
scripts/build-image-manifest.mjs:
a. walk() rekursif public/uploads/
- Abaikan folder opt/ (output Sharp, bukan sumber)
- Proses SEMUA file .jpg .jpeg .png .webp .avif
- Tidak ada filter nama file; nama apapun diterima
(727.jpg, Jshsh.png, 1-1.webp semuanya diproses)
b. Untuk setiap file:
- Baca dimensi via sharp.metadata()
- Hitung usableWidths = [480, 960, 1440] yang <= lebar asli
- Untuk setiap format (avif q50, webp q80):
untuk setiap lebar:
hasilkan public/opt/name-{w}.{ext}
(skip jika file sudah ada dan lebih baru dari sumber)
- Hasilkan fallback WebP di lebar maksimum (maks 1440)
c. Tulis image-manifest.json:
{
"/uploads/namafile.webp": {
"sources": [
{ "srcset": "/opt/name-480.avif 480w, ...", "type": "image/avif", ... },
{ "srcset": "/opt/name-480.webp 480w, ...", "type": "image/webp", ... }
],
"fallback": "/opt/name-1440.webp",
"width": 1440,
"height": 960
},
...
}
Log yang muncul:
[images] found N source files
[images] ✓ /uploads/namafile.webp
[images] done — N entries in manifest
LANGKAH 3 — astro build render HTML
------------------------------------
Setelah prebuild selesai, astro build jalan. Dua jalur konsumen manifest:
JALUR A — HERO (dari frontmatter image)
----------------------------------------
Post.astro memanggil:
<ResponsiveImg src={image} alt={title} priority={true} />
ResponsiveImg.astro:
1. Baca manifest via getManifest()
2. Cari entry src = "/uploads/namafile.webp"
3. Jika ada, render:
<picture>
<source srcset="/opt/name-480.avif 480w, ..." type="image/avif">
<source srcset="/opt/name-480.webp 480w, ..." type="image/webp">
<img src="/opt/name-1440.webp" width height
loading="eager" fetchpriority="high" decoding="async">
</picture>
4. Jika tidak ada entry, fallback ke <img src={src}> biasa
JALUR B — BODY (dari Markdown )
--------------------------------------------------
Astro memproses .md lewat Sätteri. Sätteri menghasilkan HTML AST (HAST).
Di sini, plugin hast-responsive-images.mjs berjalan:
a. Filter: hanya node <img>
b. Ambil src, normalisasi (terima /uploads/, uploads/, URL absolut)
c. Cari di manifest
d. Jika ada entry:
- setProperty(src, entry.fallback)
- setProperty(srcset, chosen.srcset) // avif diutamakan
- setProperty(sizes, chosen.sizes)
- setProperty(loading, "lazy")
- setProperty(decoding, "async")
- setProperty(width, entry.width)
- setProperty(height, entry.height)
TIDAK mengganti node. Hanya menambah properti.
e. Jika tidak ada entry:
- Biarkan node apa adanya (gambar eksternal, atau belum di-generate)
Hasil HTML:
<img src="/opt/name-1440.avif"
srcset="/opt/name-480.avif 480w, /opt/name-960.avif 960w, ..."
sizes="(max-width: 640px) 100vw, 43rem"
loading="lazy" decoding="async"
width="1440" height="960">
Kenapa pakai Sätteri untuk body?
- Markdown tidak mengenal komponen .astro
- components={{ img: ResponsiveImg }} hanya untuk MDX, bukan .md
- Sätteri adalah satu-satunya cara Astro 7 menyisipkan plugin HAST
yang bisa memodifikasi <img> hasil Markdown
LANGKAH 4 — output dist/ dan deploy
------------------------------------
Astro build menghasilkan:
dist/
index.html
[slug]/index.html
arsip/index.html
opt/name-480.avif, name-480.webp, ... (dari public/opt/)
uploads/namafile.webp (dari public/uploads/)
_astro/* (CSS, JS, font)
pagefind/* (index pencarian)
Cloudflare Pages menyajikan dist/ sebagai situs statis.
public/_headers mengatur cache:
/_astro/* → immutable 1y
/uploads/* → immutable 1y
LANGKAH 5 — browser memilih varian
-----------------------------------
Saat user buka /post-slug/:
a. Browser parse <img srcset="...">:
- Cek format yang didukung (AVIF diutamakan dari <source type>)
- Hitung lebar yang dibutuhkan dari sizes + viewport × DPR
b. Skenario:
- HP 360px DPR 2 → butuh 720px → unduh name-960.avif
- HP 414px DPR 3 → butuh 1242px → unduh name-1440.avif
- Desktop 1440px DPR 1 → unduh name-1440.avif
- Desktop retina 1440 DPR 2 → name-1440.avif (paling besar tersedia)
c. File yang diunduh = hasil Sharp (kecil, WebP/AVIF)
Bukan file asli dari HP (bisa 3-5MB)
4. RINGKASAN SATU ARAH
----------------------
Upload via CMS
→ public/uploads/x.webp
→ prebuild: Sharp
→ public/opt/x-{480,960,1440}.{avif,webp}
→ image-manifest.json
→ astro build:
hero : ResponsiveImg → <picture> + <source srcset>
body : hast plugin → <img srcset="...">
→ dist/
→ Cloudflare Pages
→ browser pilih varian dari srcset
→ user lihat gambar sesuai ukuran layar
5. SETTING PAGES CMS YANG WAJIB
-------------------------------
.pages.yml:
media:
input: public/uploads
output: /uploads
content:
- name: posts
label: Catatan
type: collection
path: src/content/posts
filename: "{primary}.md"
fields:
- { name: title, label: Judul, type: string, required: true }
- { name: description, label: Deskripsi SEO, type: text, required: false }
- { name: image, label: Cover, type: image, required: false }
- name: date
label: Tanggal
type: date
required: true
options: { format: yyyy-MM-dd }
- { name: body, label: Isi, type: rich-text }
Penjelasan:
media.input : folder fisik tempat file disimpan (di repo)
media.output : path yang ditulis ke Markdown
filename : pola nama file post, {primary} = field title
image (Cover) : field untuk hero, mengisi frontmatter image:
body : editor rich-text, hasilnya Markdown
6. KONFIGURASI ASTRO YANG WAJIB
-------------------------------
astro.config.mjs:
markdown: {
processor: satteri({
hastPlugins: [hastResponsiveImages],
}),
},
Tanpa blok ini, body Markdown tidak akan pernah menyentuh plugin,
sehingga <img> dari CMS tidak mendapat srcset.
scripts (package.json):
"prebuild": "node scripts/build-image-manifest.mjs",
"build": "astro build"
Tanpa prebuild, image-manifest.json tidak ada, dan kedua konsumen
(hero & body) tidak punya data varian.
7. TROUBLESHOOTING ALUR
-----------------------
Gejala Cek
---------------------------------------- ---------------------------------
Hero teroptimasi, body tidak Plugin Sätteri terdaftar di
astro.config.mjs?
Body teroptimasi, hero tidak image frontmatter pakai
/uploads/...?
Keduanya tidak teroptimasi image-manifest.json ada?
prebuild jalan?
Sebagian gambar body tidak teroptimasi Path di markdown cocok dengan
key manifest? Cek log [images]
Gambar berubah jadi 404 File asli ada di public/uploads/?
/opt/* 404 di produksi public/opt/ ter-commit atau
di-generate saat build?apakah nanti saya akan ganti stack lagi karena ada yang lebih baik? bisa jadi, tapi untuk sekarang saya pakai ini saja.
tambahan stack guide astro
GUIDE ASTRO FRAMEWORK — FASTNEWS
================================
Dokumen ini adalah panduan proyek fastnews berbasis Astro 7: alur gambar
dari upload sampai render, SEO, canonical, robots, RSS, sitemap, halaman
404, arsip, Tailwind CSS, dan komponen shadcn-style. Ditulis agar mudah
dibaca manusia maupun LLM: struktur tegas, penanda jelas, tanpa asumsi
tersembunyi.
Catatan struktur folder: setiap proyek punya struktur berbeda. Bagian
"STRUKTUR FOLDER" hanya CONTOH. Yang mengikat adalah PERAN file dan
ALURNYA, bukan path literalnya. Jika proyek Anda memakai path lain,
petakan perannya (mis. "script yang memanggil Sharp", "komponen hero
responsif", "layout dasar") lalu sesuaikan.
0. RINGKASAN CEPAT (untuk LLM)
------------------------------
- Framework : Astro 7 (static build)
- Markdown : prosesor Sätteri + HAST plugin kustom
- Gambar : Sharp di prebuild -> manifest JSON -> dua konsumen
(hero via komponen ResponsiveImg, body via HAST plugin)
- Hosting : Cloudflare Pages
- Styling : Tailwind CSS + komponen bergaya shadcn (lokal)
- SEO : canonical, robots.txt, RSS, sitemap, meta OG
-> semua di satu sumber kebenaran (config situs)
- Komponen berulang: komponen presentasi di folder components,
konstanta situs di satu file config
- Aturan emas : hal yang sering berubah (nama situs, URL, menu,
meta default) HANYA di satu file config.
1. STACK YANG TERLIBAT
----------------------
Komponen Peran
------------------------ ----------------------------------------------
Pages CMS UI upload gambar + tulis post
Sharp Generate varian AVIF/WebP + fallback
Manifest JSON Peta path asli -> varian (dibaca 2 konsumen)
Komponen ResponsiveImg Konsumen manifest untuk HERO (frontmatter)
Plugin HAST responsif Konsumen manifest untuk BODY (Markdown)
Sätteri Prosesor Markdown Astro 7, jalankan HAST plugin
Astro Framework statis, build HTML
Cloudflare Pages Hosting statis, sajikan /opt/* dan /uploads/*
Browser Pilih varian dari srcset berdasarkan viewport
Tailwind CSS Utility styling
shadcn-style (lokal) Komponen UI (button, card, badge, dll)
2. STRUKTUR FOLDER (CONTOH SAJA — FLEKSIBEL)
--------------------------------------------
Setiap proyek boleh berbeda. Yang penting PERAN tiap file tersedia,
bukan path literalnya. Contoh di bawah hanya ilustrasi:
<root>/
astro.config.mjs
package.json
.pages.yml
image-manifest.json (generated, tidak di-commit)
scripts/
build-image-manifest.mjs
public/
uploads/ (file asli dari CMS)
opt/ (output Sharp, generated)
_headers
src/
config/site.ts (SITE, menu, meta default)
lib/manifest.ts
lib/hast-responsive-images.mjs
components/
ResponsiveImg.astro
Seo.astro
Header.astro
Footer.astro
PostCard.astro
ui/ (shadcn-style)
layouts/
BaseLayout.astro
PostLayout.astro
pages/
index.astro
[slug].astro
arsip/index.astro
404.astro
rss.xml.ts
sitemap.xml.ts
robots.txt.ts
content/posts/*.md
styles/global.css
tailwind.config.mjs
PETUNJUK PEMETAAN JIKA STRUKTUR BERBEDA:
- "script Sharp" -> file apa pun yang dipanggil prebuild
- "manifest" -> file JSON hasil prebuild, path bebas,
asal dua konsumen membacanya dari sumber sama
- "hero responsif" -> komponen presentasi yang menerima src+alt
- "plugin HAST body" -> file .mjs yang didaftarkan di hastPlugins
- "config situs" -> file tunggal berisi nama, URL, menu, meta
- "layout dasar" -> layout yang membungkus SEO + header + footer
Semua penjelasan di bawah TIDAK bergantung pada path literal di atas.
3. ALUR GAMBAR — UPLOAD SAMPAI TAMPIL
-------------------------------------
3.1 FILE YANG TERLIBAT (per peran, bukan path literal)
Peran Tugas
------------------------ ----------------------------------------------
Konfigurasi CMS Atur media.input & media.output + koleksi
Script Sharp Satu-satunya tempat Sharp dipanggil.
Scan folder upload -> tulis varian ke folder
opt -> tulis manifest JSON
Manifest JSON Hasil prebuild. Entry per gambar: sources[],
fallback, width, height. Tidak di-commit
Folder upload File asli dari CMS. URL publik /uploads/nama
Folder opt Output Sharp. URL publik /opt/nama-{w}.{ext}
Komponen hero responsif Baca manifest, render <picture> + <source>
Plugin HAST body Baca manifest, TAMBAH srcset/sizes ke <img>,
tidak mengganti node (cegah srcset hilang)
Helper manifest getManifest() dengan cache
Konfigurasi Astro markdown.processor: satteri({ hastPlugins })
3.2 LANGKAH 1 — Upload via CMS
User klik "Upload image" di editor post. CMS membaca konfigurasi media:
input : folder fisik di repo (contoh: public/uploads)
output : path yang ditulis ke Markdown (contoh: /uploads)
File tersimpan di folder input. Markdown otomatis:

Path di Markdown (/uploads/...) dan folder fisik konsisten karena
Astro menyajikan folder public apa adanya di URL /.
3.3 LANGKAH 2 — Prebuild (Sharp)
Build memicu prebuild:
"prebuild": "node <script-sharp>",
"build" : "astro build"
Script Sharp melakukan:
a. Walk rekursif folder upload
- Abaikan folder opt (output, bukan sumber)
- Proses semua .jpg .jpeg .png .webp .avif
- Tanpa filter nama file (727.jpg, Jshsh.png, 1-1.webp diproses)
b. Per file:
- sharp.metadata() -> dimensi asli
- usableWidths = [480, 960, 1440] yang <= lebar asli
- Untuk avif q50 dan webp q80, tiap lebar:
hasilkan /opt/name-{w}.{ext}
(skip jika sudah ada dan lebih baru dari sumber)
- Fallback WebP di lebar maksimum (maks 1440)
c. Tulis manifest JSON:
{
"/uploads/namafile.webp": {
"sources": [
{ "srcset": "/opt/name-480.avif 480w, ...",
"type": "image/avif", "sizes": "..." },
{ "srcset": "/opt/name-480.webp 480w, ...",
"type": "image/webp", "sizes": "..." }
],
"fallback": "/opt/name-1440.webp",
"width": 1440,
"height": 960
}
}
Log yang muncul:
[images] found N source files
[images] ok /uploads/namafile.webp
[images] done - N entries in manifest
3.4 LANGKAH 3 — Build render HTML
Setelah prebuild, build Astro jalan. Dua jalur konsumen manifest:
JALUR A — HERO (dari frontmatter image)
---------------------------------------
Layout post memanggil komponen hero responsif:
<ResponsiveImg src={image} alt={title} priority={true} />
Komponen:
1. getManifest()
2. Cari entry dengan key = src
3. Jika ada:
<picture>
<source srcset="/opt/name-480.avif 480w, ..."
type="image/avif">
<source srcset="/opt/name-480.webp 480w, ..."
type="image/webp">
<img src="/opt/name-1440.webp" width height
loading="eager" fetchpriority="high"
decoding="async">
</picture>
4. Jika tidak ada -> fallback <img src={src}> biasa
JALUR B — BODY (dari Markdown )
-------------------------------------------------
Astro memproses .md lewat Sätteri -> HAST -> plugin berjalan:
a. Filter: hanya node <img>
b. Normalisasi src (terima /uploads/, uploads/, URL absolut)
c. Cari di manifest
d. Jika ada entry:
set src = entry.fallback
set srcset = chosen.srcset (avif diutamakan)
set sizes = chosen.sizes
set loading = "lazy"
set decoding = "async"
set width = entry.width
set height = entry.height
TIDAK mengganti node. Hanya menambah properti.
e. Jika tidak ada -> biarkan node apa adanya
(gambar eksternal / belum di-generate)
Hasil HTML body:
<img src="/opt/name-1440.avif"
srcset="/opt/name-480.avif 480w, /opt/name-960.avif 960w, ..."
sizes="(max-width: 640px) 100vw, 43rem"
loading="lazy" decoding="async"
width="1440" height="960">
Kenapa pakai Sätteri untuk body?
- Markdown tidak mengenal komponen Astro
- components={{ img: ResponsiveImg }} hanya untuk MDX, bukan .md
- Sätteri satu-satunya cara Astro 7 menyisipkan plugin HAST yang
bisa memodifikasi <img> hasil Markdown
3.5 LANGKAH 4 — Output build & deploy
Output build (ilustrasi):
dist/
index.html
[slug]/index.html
arsip/index.html
404.html
rss.xml
sitemap.xml
robots.txt
opt/name-480.avif, name-480.webp, ...
uploads/namafile.webp
_astro/* (CSS, JS, font)
pagefind/* (index pencarian)
Cloudflare Pages menyajikan dist. Header cache yang disarankan:
/_astro/* -> immutable, max-age 1 tahun
/uploads/* -> immutable, max-age 1 tahun
/opt/* -> immutable, max-age 1 tahun
3.6 LANGKAH 5 — Browser memilih varian
- Browser parse <img srcset> + <source type>
- Hitung lebar dibutuhkan = sizes x viewport x DPR
- Contoh:
HP 360 DPR 2 -> butuh 720 -> unduh name-960.avif
HP 414 DPR 3 -> butuh 1242 -> unduh name-1440.avif
Desktop 1440 DPR 1 -> unduh name-1440.avif
Desktop 1440 DPR 2 -> name-1440.avif (terbesar tersedia)
- File terunduh = hasil Sharp (kecil), bukan file asli dari HP
3.7 RINGKASAN SATU ARAH
Upload via CMS
-> folder upload/x.webp
-> prebuild: Sharp
-> opt/x-{480,960,1440}.{avif,webp}
-> manifest JSON
-> build Astro:
hero : komponen hero -> <picture> + <source srcset>
body : plugin HAST -> <img srcset="...">
-> dist/
-> Cloudflare Pages
-> browser pilih varian dari srcset
-> user lihat gambar sesuai ukuran layar
4. KONFIGURASI WAJIB
--------------------
4.1 Konfigurasi CMS (contoh .pages.yml)
media:
input: public/uploads
output: /uploads
content:
- name: posts
label: Catatan
type: collection
path: src/content/posts
filename: "{primary}.md"
fields:
- { name: title, label: Judul, type: string, required: true }
- { name: description, label: Deskripsi SEO,
type: text, required: false }
- { name: image, label: Cover, type: image, required: false }
- name: date
label: Tanggal
type: date
required: true
options: { format: yyyy-MM-dd }
- { name: body, label: Isi, type: rich-text }
Penjelasan:
media.input : folder fisik tempat file disimpan (di repo)
media.output : path yang ditulis ke Markdown
filename : pola nama file post, {primary} = field title
image (Cover) : field untuk hero, mengisi frontmatter image:
body : editor rich-text, hasilnya Markdown
4.2 Konfigurasi Astro (contoh astro.config.mjs)
import { defineConfig } from "astro/config";
import satteri from "<paket-satteri>";
import hastResponsiveImages from "<path-plugin-hast>";
import tailwind from "@astrojs/tailwind";
export default defineConfig({
site: "https://fastnews.example.com", // WAJIB
integrations: [tailwind()],
markdown: {
processor: satteri({
hastPlugins: [hastResponsiveImages],
}),
},
image: {
remotePatterns: [
{ protocol: "https", hostname: "images.unsplash.com" },
],
},
});
Tanpa markdown.processor, body Markdown tidak menyentuh plugin,
sehingga <img> dari CMS tidak mendapat srcset.
Tanpa site, canonical/RSS/sitemap tidak akurat.
4.3 Scripts (contoh package.json)
"scripts": {
"dev" : "astro dev",
"prebuild": "node <script-sharp>",
"build" : "astro build",
"preview" : "astro preview"
}
Tanpa prebuild, manifest tidak ada, hero & body tidak punya data
varian.
5. SEO, CANONICAL, ROBOTS, RSS, SITEMAP
---------------------------------------
Prinsip: SATU SUMBER KEBENARAN.
- Semua default (nama situs, URL, menu, meta) di satu file config.
- Semua tag SEO dirender oleh satu komponen Seo.
- Layout dasar memanggil komponen Seo sekali untuk semua halaman.
5.1 File config situs (peran)
Berisi minimal:
name, title, description, url, locale, author, twitter,
ogImageDefault, menu[], buildDate
Contoh isi (TypeScript):
export const SITE = {
name: "Fastnews",
title: "Fastnews - Berita cepat, ringkas, akurat",
description: "Kumpulan catatan dan berita ringkas.",
url: "https://fastnews.example.com",
locale: "id-ID",
author: "Redaksi Fastnews",
twitter: "@fastnews",
ogImageDefault: "/uploads/og-default.webp",
menu: [
{ label: "Beranda", href: "/" },
{ label: "Arsip", href: "/arsip/" },
{ label: "RSS", href: "/rss.xml" },
],
};
Semua komponen mengimpor dari sini. Ganti nama situs / domain cukup
edit satu file.
5.2 Komponen Seo (peran)
Props yang didukung minimal:
title?, description?, image?, canonical?, type? ("website"|"article"),
publishedTime?, noindex?
Yang dirender:
<title>
<meta name="description">
<link rel="canonical"> (absolut)
<meta property="og:type">
<meta property="og:title">
<meta property="og:description">
<meta property="og:url">
<meta property="og:image">
<meta property="og:site_name">
<meta property="og:locale">
<meta name="twitter:card"> (summary_large_image)
<meta name="twitter:title">
<meta name="twitter:description">
<meta name="twitter:image">
<meta name="robots" content="noindex,nofollow"> (jika noindex)
Aturan canonical:
- Selalu absolut
- Dibangun dari URL situs di config + path halaman
- Kecuali prop canonical diisi manual
5.3 robots.txt (peran)
Rute yang mengembalikan text/plain berisi:
User-agent: *
Allow: /
Disallow: /pagefind/
Sitemap: <URL situs>/sitemap.xml
Sitemap harus absolut.
5.4 sitemap.xml (peran)
Iterasi koleksi post, emit <urlset>:
/ -> priority 1.0
/arsip/ -> 0.6
/{slug}/ -> 0.8, lastmod = date, changefreq = monthly
Aturan:
- <loc> absolut (URL situs + path)
- Content-Type: application/xml
- Jangan masukkan halaman 404
5.5 rss.xml (peran)
Pakai paket RSS resmi (contoh @astrojs/rss):
- Sort post berdasarkan date desc
- Item: title, pubDate, description, link absolut
- customData: <language>{locale}</language>
Di layout dasar sisipkan:
<link rel="alternate" type="application/rss+xml" href="/rss.xml">
(disisipkan sekali, bukan per halaman)
5.6 Halaman 404 (peran)
- Pakai layout dasar dengan noindex = true
- Judul "404 - Tidak ditemukan"
- Link ke "/" dan "/arsip/"
- Cloudflare Pages otomatis menyajikan 404.html untuk path asing
- JANGAN masukkan 404 ke sitemap
5.7 Ringkasan SEO - cek cepat
Hal Lokasi (peran) Catatan
---------------------- -------------------------- ------------------
Nama/URL/deskripsi file config situs satu-satunya sumber
Meta + OG + canonical komponen Seo dipanggil layout
Canonical absolut dibangun dari URL situs jangan hardcode
Robots rute robots.txt sebutkan sitemap
Sitemap rute sitemap.xml absolut, no 404
RSS rute rss.xml alternate di layout
404 rute 404 + noindex disajikan otomatis
6. KOMPONEN BERULANG — SATU TEMPAT
----------------------------------
Aturan: komponen presentasi TIDAK menyimpan konstanta situs. Semua yang
berulang diatur di satu tempat.
Jenis Lokasi (peran)
----------------------- --------------------------------------------
Konstanta situs & menu file config situs
Layout dasar layout pembungkus (SEO + header + footer)
Layout post layout post (hero + meta + slot body)
SEO komponen Seo
Header / Footer komponen, impor menu dari config situs
Kartu post satu komponen, dipakai di beranda & arsip
Gambar responsif (hero) komponen ResponsiveImg
UI primitives komponen ui (shadcn-style)
Cara ganti: edit satu file -> semua halaman ikut.
Contoh: ubah menu di config situs -> Header semua halaman berubah.
7. ARSIP
--------
Halaman arsip (peran):
1. Ambil semua post, sort date desc
2. Kelompokkan per tahun (Map<tahun, Post[]>)
3. Render: tiap tahun -> <h2>{tahun}</h2> + daftar kartu post
4. Komponen Seo dengan title "Arsip" dan canonical "/arsip/"
5. Daftarkan "Arsip" di menu (config situs)
6. Masukkan "/arsip/" di sitemap (priority 0.6)
Opsional pagination:
- Pakai paginate() Astro di rute arsip berparameter
- Jaga konsistensi canonical halaman 2+ (path atau query)
8. TAILWIND CSS + SHADCN-STYLE
------------------------------
8.1 Setup Tailwind (contoh)
Install:
npm i -D tailwindcss @astrojs/tailwind postcss autoprefixer
tailwind.config.mjs:
export default {
content: ["./src/**/*.{astro,html,md,mdx,ts,tsx,js,jsx}"],
theme: { extend: {} },
plugins: [],
};
styles/global.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
Impor global.css di layout dasar.
8.2 shadcn-style (lokal, bukan paket npm)
Alasan: generator shadcn berbasis React, tidak langsung kompatibel
dengan Astro. Praktiknya komponen bergaya shadcn disalin ke folder
komponen ui, mis:
button.astro, card.astro, badge.astro
(tambah sesuai kebutuhan)
Aturan:
- Semua varian style didaftarkan di SATU file komponen
(mis. map variants di button)
- Token warna di tailwind.config (theme.extend.colors)
- Komponen ui TIDAK mengimpor config situs (murni presentasi)
8.3 Varian Tailwind untuk gambar
Saat mengisi sizes di manifest / plugin HAST, samakan dengan
breakpoint Tailwind. Contoh default:
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 43rem"
9. TROUBLESHOOTING
------------------
Gejala Cek
------------------------------------ ---------------------------------
Hero teroptimasi, body tidak Plugin Sätteri terdaftar di
konfigurasi markdown Astro?
Body teroptimasi, hero tidak Frontmatter image pakai
/uploads/...?
Keduanya tidak teroptimasi Manifest JSON ada? Prebuild
jalan?
Sebagian gambar body tidak Path di Markdown cocok dengan
teroptimasi key manifest? Cek log [images]
Gambar jadi 404 File asli ada di folder upload?
/opt/* 404 di produksi Folder opt ter-generate saat
build? (Jangan commit)
Canonical salah domain URL situs di config sudah benar?
site: di konfigurasi Astro?
RSS kosong site: di config? getCollection
mengembalikan data?
Sitemap tidak muncul di Google robots.txt menyebut sitemap?
Sitemap absolut?
404 tampil sebagai halaman default Rute 404 ada? Cloudflare Pages
deploy dist/404.html?
Menu berubah tapi tidak konsisten Ada hardcode menu di komponen?
Pindahkan ke config situs
10. CHECKLIST DEPLOY
--------------------
1. URL situs di config = domain produksi
2. site: di konfigurasi Astro = domain produksi
3. Build -> cek di dist: manifest, opt/*, rss.xml, sitemap.xml,
404.html
4. Header cache mengatur /opt/*, /uploads/*, /_astro/*
5. Cloudflare Pages: build command = build, output = dist
6. Verifikasi manual: /, satu post, /arsip/, /rss.xml, /sitemap.xml,
/robots.txt, path acak -> 404
7. Cek <link rel="canonical"> di view-source setiap halaman
8. Cek <img srcset> di body post: minimal 3 kandidat 480w/960w/1440w
11. ATURAN UNTUK LLM / KONTRIBUTOR
----------------------------------
1. Jangan panggil Sharp di luar script Sharp
2. Jangan ganti node <img> di plugin HAST - hanya tambah properti
3. Jangan hardcode domain/nama situs di komponen - impor config situs
4. Jangan commit manifest JSON dan folder opt
5. Semua meta/canonical/OG lewat komponen Seo
6. Menu & link situs lewat config situs
7. Komponen ui murni presentasi (tanpa config situs)
8. Body Markdown: gunakan path /uploads/... agar cocok key manifest
9. 404 harus noindex dan tidak masuk sitemap
10. Perubahan skema konten: update konfigurasi CMS DAN definisi
koleksi konten bersamaan
12. Saat memetakan struktur proyek baru, cocokkan PERAN file (lihat
bagian 2), bukan path literal di contoh.
12. RINGKASAN SATU HALAMAN
--------------------------
- Gambar: upload -> Sharp prebuild -> manifest -> hero <picture>
+ body <img srcset> -> browser pilih varian
- SEO: satu config situs + satu komponen Seo + rute robots,
sitemap, rss, 404
- Komponen berulang: satu tempat, tanpa hardcode
- Styling: Tailwind + komponen shadcn-style lokal
- Struktur folder: FLEKSIBEL, yang mengikat adalah PERAN & ALUR
- Aturan emas: konstanta situs hanya di satu file config