1. Pengenalan Astro
Astro adalah web framework all-in-one yang dirancang untuk membangun website yang cepat, content-driven. Dibuat oleh Fred K. Schott dan tim, Astro mengambil pendekatan unik: zero JavaScript by default. Semua halaman di-render sebagai HTML murni di server, dan JavaScript hanya dikirim ke browser saat benar-benar dibutuhkan melalui konsep yang disebut Islands Architecture.
Ini menjadikan Astro sangat cocok untuk blog, dokumentasi, portofolio, situs perusahaan, landing page, dan e-commerce β di mana konten dan kecepatan adalah prioritas utama.
Mengapa Memilih Astro?
| Keunggulan | Penjelasan |
|---|---|
| Zero JS Default | Tidak ada JavaScript yang dikirim ke browser kecuali Anda secara eksplisit menambahkannya |
| Framework-Agnostic | Gunakan React, Vue, Svelte, Solid, Preact, atau vanilla JS dalam satu proyek |
| Islands Architecture | Interaktif hanya di bagian yang perlu β sisanya HTML statis |
| Content Collections | Type-safe markdown/MDX content management built-in |
| Super Cepat | Skor Lighthouse 100/100 out of the box β karena HTML murni |
| SSG & SSR | Dukungan static site generation dan server-side rendering |
Astro vs Framework Lain
| Aspek | Astro | Next.js | Nuxt.js | SvelteKit |
|---|---|---|---|---|
| Primary Use | Content websites | Web apps | Web apps | Web apps |
| Default JS | Zero (HTML) | React (client) | Vue (client) | Svelte (client) |
| Multi-framework | β Ya | β React only | β Vue only | β Svelte only |
| Learning Curve | π’ Mudah | π‘ Sedang | π‘ Sedang | π’ Mudah |
| Best For | Blog, docs, porto | Complex web apps | Complex web apps | Fast web apps |
| Performance | β‘ Sangat cepat | π‘ Tergantung SSR | π‘ Tergantung SSR | β‘ Cepat |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β ASTRO ISLANDS ARCHITECTURE β β β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β HTML Page (statik) β β β β β β β β ββββββββββββββββββββββββββββββββββββββββββββ β β β β β π Header β HTML murni, 0 KB JS β β β β β ββββββββββββββββββββββββββββββββββββββββββββ β β β β β β β β ββββββββββββββββββββ ββββββββββββββββββββ β β β β β π Blog Post β β π΄ React Island β β β β β β HTML statis β β (client:idle) β β β β β β 0 KB JavaScript β β 42 KB JavaScript β β β β β ββββββββββββββββββββ ββββββββββββββββββββ β β β β β β β β ββββββββββββββββββββ ββββββββββββββββββββ β β β β β π Sidebar β β π₯ Svelte Island β β β β β β HTML statis β β (client:visible) β β β β β β 0 KB JavaScript β β 2 KB JavaScript β β β β β ββββββββββββββββββββ ββββββββββββββββββββ β β β β β β β β ββββββββββββββββββββββββββββββββββββββββββββ β β β β β π Footer β HTML murni, 0 KB JS β β β β β ββββββββββββββββββββββββββββββββββββββββββββ β β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β β Total JS: 44 KB (bukan 200+ KB seperti SPA biasa) β β Islands = komponen interaktif yang hydrated client β β Sisanya = HTML statis yang sangat cepat β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2. Setup Proyek Astro
Astro dapat di-setup dengan sangat cepat. CLI wizard interaktif akan memandu Anda memilih template, TypeScript, dan integrasi framework.
# Buat proyek Astro baru npm create astro@latest belajar-astro # CLI wizard akan menanyakan: # β Where should we create your new project? β belajar-astro # β How would you like to start your new project? β Include sample files # β Do you plan to write TypeScript? β Yes (recommended) # β Install dependencies? β Yes # β Initialize a new git repository? β Yes # Masuk ke direktori cd belajar-astro # Jalankan development server npm run dev # Output: # astro v4.x # β Local http://localhost:4321/ # β Network http://192.168.1.x:4321/ # Build untuk production npm run build # Preview hasil build npm run preview
Struktur Proyek Astro
belajar-astro/ βββ public/ β File statis (gambar, favicon, robots.txt) β βββ favicon.svg βββ src/ β βββ components/ β Komponen UI (Astro, React, Vue, Svelte) β β βββ Header.astro β β βββ Footer.astro β β βββ Card.astro β β βββ Counter.jsx β React island β β βββ Search.svelte β Svelte island β βββ content/ β Content collections β β βββ blog/ β β β βββ post-1.md β β β βββ post-2.mdx β β βββ config.ts β Schema definisi β βββ layouts/ β Layout wrappers β β βββ BaseLayout.astro β β βββ BlogLayout.astro β βββ pages/ β File-based routing β β βββ index.astro β / β β βββ about.astro β /about β β βββ blog/ β β β βββ index.astro β /blog β β β βββ [slug].astro β /blog/:slug β β βββ api/ β β βββ data.json.ts β /api/data (API endpoint) β βββ styles/ β βββ global.css βββ astro.config.mjs β Konfigurasi Astro βββ package.json βββ tsconfig.json βββ tailwind.config.mjs β (opsional) Tailwind config
Konfigurasi Astro
// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import svelte from '@astrojs/svelte';
import tailwind from '@astrojs/tailwind';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
// https://astro.build/config
export default defineConfig({
// Site URL (untuk sitemap & SEO)
site: 'https://belajar-astro.pages.dev',
// Integrations: framework & fitur yang ingin ditambahkan
integrations: [
react(),
svelte(),
tailwind(),
mdx(),
sitemap(),
],
// Output mode
// 'static' (default) β SSG, semua halaman jadi HTML statis
// 'server' β SSR, render di server setiap request
// 'hybrid' β campuran (sebagian statis, sebagian SSR)
output: 'static',
// Vite config (Astro menggunakan Vite di balik layar)
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: `$accent: #6366f1;`
}
}
}
},
// Markdown config
markdown: {
shikiConfig: {
theme: 'catppuccin-mocha',
wrap: true,
},
},
});
Salah satu keunggulan terbesar Astro adalah dukungan multi-framework. Anda bisa menggunakan komponen React, Vue, Svelte, Solid, Preact, dan Lit dalam satu proyek yang sama. Cukup tambahkan integrasinya di astro.config.mjs.
3. Komponen Astro (.astro)
File .astro adalah format komponen unik Astro. Ia menggunakan sintaks yang mirip dengan JSX tetapi memiliki beberapa perbedaan penting: frontmatter script di bagian atas (di antara fence ---) dan template expressions di bagian bawah.
Anatomi Komponen Astro
---
// ====== FRONTMATTER SCRIPT ======
// Kode JavaScript dieksekusi di BUID TIME (server-side)
// Tidak pernah dikirim ke browser!
// Import komponen
import Button from '../components/Button.astro';
import Card from '../components/Card.astro';
// Akses props
interface Props {
judul: string;
deskripsi?: string;
warna?: string;
}
const { judul, deskripsi = 'Tidak ada deskripsi', warna = '#6366f1' } = Astro.props;
// Fetch data (build-time)
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// Logika JavaScript
const daftarItem = ['Roti', 'Susu', 'Telur', 'Kopi'];
const tanggal = new Date().toLocaleDateString('id-ID', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
});
// Helper functions
function formatRupiah(angka: number): string {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(angka);
}
---
{judul}
{tanggal}
{deskripsi}
2 + 2 = {2 + 2}
Harga: {formatRupiah(150000)}
{data.length > 0 ? (
{data.map((item: any) => (
- {item.nama}
))}
) : (
Tidak ada data
)}
{daftarItem.map(item => - {item}
)}
Default footer content
Layout Components
---
// ====== src/layouts/BaseLayout.astro ======
interface Props {
judul: string;
deskripsi?: string;
}
const { judul, deskripsi = 'Website tutorial BeebaneLabs' } = Astro.props;
---
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{judul} | BeebaneLabs</title>
<meta name="description" content={deskripsi}>
<link rel="stylesheet" href="/styles/global.css">
</head>
<body>
<header>
<nav>
<a href="/">Beranda</a>
<a href="/blog">Blog</a>
<a href="/about">Tentang</a>
</nav>
</header>
<main>
<slot /> <!-- Konten halaman di-render di sini -->
</main>
<footer>
<p>© 2026 BeebaneLabs</p>
</footer>
</body>
</html>
// ====== src/layouts/BlogLayout.astro ======
---
import BaseLayout from './BaseLayout.astro';
interface Props {
judul: string;
deskripsi: string;
tanggal: string;
gambar?: string;
tags?: string[];
}
const { judul, deskripsi, tanggal, gambar, tags = [] } = Astro.props;
---
<BaseLayout judul={judul} deskripsi={deskripsi}>
<article class="blog-post">
{gambar && <img src={gambar} alt={judul} class="hero-image" />}
<h1>{judul}</h1>
<time>{tanggal}</time>
{tags.length > 0 && (
<div class="tags">
{tags.map(tag => <span class="tag">{tag}</span>)}
</div>
)}
<div class="content">
<slot />
</div>
</article>
</BaseLayout>
// ====== Menggunakan layout di halaman ======
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout judul="Tentang Kami">
<h1>Tentang BeebaneLabs</h1>
<p>Kami adalah portal tutorial teknologi Indonesia.</p>
</BaseLayout>
4. Islands Architecture
Islands Architecture adalah konsep inti Astro. Di halaman yang sebagian besar statis, komponen interaktif dianggap sebagai "pulau" (island) yang hidup di lautan HTML statis. Setiap island bisa hydrated secara independen dengan strategi yang berbeda.
Client Directives
--- // Import komponen dari framework manapun import ReactCounter from '../components/Counter.jsx'; import VueGallery from '../components/Gallery.vue'; import SvelteSearch from '../components/Search.svelte'; import SolidSidebar from '../components/Sidebar.tsx'; import LitButton from '../components/my-button.js'; --- <!-- ====== 1. client:idle β hydrated saat browser idle ====== --> <!-- Cocok untuk: komponen non-urgent yang tidak perlu langsung interaktif --> <ReactCounter client:idle /> <!-- ====== 2. client:visible β hydrated saat terlihat di viewport ====== --> <!-- Cocok untuk: komponen di bawah fold, lazy loading --> <VueGallery client:visible /> <!-- ====== 3. client:load β hydrated segera saat halaman dimuat ====== --> <!-- Cocok untuk: komponen yang harus langsung interaktif --> <SvelteSearch client:load /> <!-- ====== 4. client:media β hydrated berdasarkan media query ====== --> <!-- Cocok untuk: komponen responsif yang berbeda per ukuran layar --> <SolidSidebar client:media="(max-width: 768px)" /> <!-- ====== 5. client:only β skip rendering server, render di client saja ====== --> <!-- Cocok untuk: komponen yang hanya bisa di-render di browser --> <LitButton client:only="lit" /> <!-- Komponen TANPA client directive = HTML statis, 0 JS! --> <ReactCounter /> <!-- Hanya HTML, tidak interaktif di client -->
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β CLIENT HYDRATION STRATEGIES β β β β client:load βββββββ π§ Hydrate segera saat load β β β Urgent, interaktif langsung β β β Contoh: search bar, form login β β β β β client:idle βββββββ π€ Hydrate saat browser idle β β β Tidak urgent, bisa tunggu β β β Contoh: footer chat, widget β β β β β client:visible βββ ποΈ Hydrate saat visible di viewportβ β β Lazy load, hemat bandwidth β β β Contoh: gallery, komentar β β β β β client:media βββββ π± Hydrate berdasarkan media query β β β Responsive interactivity β β β Contoh: mobile menu, sidebar β β β β β client:only ββββββ π₯οΈ Render & hydrate di client β β Skip SSR sepenuhnya β β Contoh: WebGL, browser-specific β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
5. Routing & Pages
Astro menggunakan file-based routing. Setiap file di src/pages/ secara otomatis menjadi rute. Astro mendukung static routes, dynamic routes, dan bahkan API endpoints.
// ====== Static Routes ======
// src/pages/index.astro β /
// src/pages/about.astro β /about
// src/pages/blog/index.astro β /blog
// src/pages/blog/archive.astro β /blog/archive
// ====== Dynamic Routes ======
// src/pages/blog/[slug].astro β /blog/:slug
// src/pages/produk/[id].astro β /produk/:id
// src/pages/[...path].astro β /* (catch-all)
// ====== src/pages/blog/[slug].astro ======
---
import BlogLayout from '../../layouts/BlogLayout.astro';
// getStaticPaths WAJIB untuk static output
// Mendefinisikan semua kemungkinan nilai slug
export async function getStaticPaths() {
const posts = await fetch('https://api.example.com/posts')
.then(r => r.json());
return posts.map((post: any) => ({
params: { slug: post.slug },
props: { post },
}));
}
// Terima props dari getStaticPaths
const { post } = Astro.props;
---
<BlogLayout
judul={post.title}
deskripsi={post.excerpt}
tanggal={post.date}
tags={post.tags}
>
<div set:html={post.content} />
</BlogLayout>
// ====== src/pages/produk/[...slug].astro (Catch-all) ======
---
// [...slug] bisa menangkap /produk/a, /produk/a/b, /produk/a/b/c
export async function getStaticPaths() {
return [
{ params: { slug: 'elektronik/laptop' } },
{ params: { slug: 'elektronik/hp' } },
{ params: { slug: 'pakaian/kaos' } },
];
}
const { slug } = Astro.params;
const parts = slug?.split('/') || [];
---
<h1>Breadcrumb: {parts.join(' > ')}</h1>
// ====== src/pages/api/data.json.ts (API Endpoint) ======
---
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ request }) => {
const url = new URL(request.url);
const limit = Number(url.searchParams.get('limit') || 10);
const data = [
{ id: 1, nama: 'Produk A', harga: 150000 },
{ id: 2, nama: 'Produk B', harga: 250000 },
].slice(0, limit);
return new Response(JSON.stringify(data), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
};
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
return new Response(JSON.stringify({ success: true, data: body }), {
status: 201,
});
};
6. Content Collections
Content Collections adalah fitur Astro untuk mengelola konten Markdown, MDX, dan data. Collections memberikan type-safe frontmatter validation dan API query yang powerful.
// ====== src/content/config.ts β Definisikan schema ======
import { defineCollection, z } from 'astro:content';
// Collection: blog posts
const blogCollection = defineCollection({
type: 'content', // 'content' untuk .md/.mdx, 'data' untuk .json/.yaml
schema: z.object({
judul: z.string(),
deskripsi: z.string(),
tanggal: z.string().transform(str => new Date(str)),
gambar: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
kategori: z.enum(['tutorial', 'artikel', 'berita']).default('artikel'),
penulis: z.string().default('Admin'),
}),
});
// Collection: produk
const produkCollection = defineCollection({
type: 'data',
schema: z.object({
nama: z.string(),
harga: z.number().positive(),
kategori: z.string(),
stok: z.number().int().min(0),
gambar: z.string().url(),
}),
});
// Export collections
export const collections = {
blog: blogCollection,
produk: produkCollection,
};
// ====== File: src/content/blog/membuat-website-astro.md ======
// ---
// judul: "Membuat Website dengan Astro"
// deskripsi: "Panduan lengkap membuat website cepat dengan Astro framework"
// tanggal: "2026-06-26"
// tags: ["astro", "web", "tutorial"]
// kategori: "tutorial"
// penulis: "Budi Santoso"
// ---
//
// ## Pengenalan
//
// Astro adalah framework yang...
//
// ## Setup
//
// Jalankan perintah berikut...
// ====== Menggunakan collections di halaman ======
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
// Ambil semua post yang bukan draft
const posts = (await getCollection('blog'))
.filter(post => !post.data.draft)
.sort((a, b) => b.data.tanggal.getTime() - a.data.tanggal.getTime());
---
<BaseLayout judul="Blog">
<h1>Blog</h1>
<div class="posts-grid">
{posts.map(post => (
<article class="post-card">
{post.data.gambar && <img src={post.data.gambar} alt={post.data.judul} />}
<h2><a href={`/blog/${post.slug}`}>{post.data.judul}</a></h2>
<p>{post.data.deskripsi}</p>
<div class="meta">
<time>{post.data.tanggal.toLocaleDateString('id-ID')}</time>
<span>oleh {post.data.penulis}</span>
</div>
<div class="tags">
{post.data.tags.map(tag => <span class="tag">#{tag}</span>)}
</div>
</article>
))}
</div>
</BaseLayout>
// ====== src/pages/blog/[slug].astro β Detail post ======
---
import { getCollection, render } from 'astro:content';
import BlogLayout from '../../layouts/BlogLayout.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await render(post);
---
<BlogLayout judul={post.data.judul} deskripsi={post.data.deskripsi} tanggal={post.data.tanggal.toLocaleDateString('id-ID')}>
<Content />
</BlogLayout>
7. Data Fetching & API
Di Astro, data fetching terjadi di frontmatter script (bagian ---) yang dieksekusi saat build time (untuk SSG) atau setiap request (untuk SSR). Astro mendukung fetch API native dan bisa mengakses database, API, atau file lokal.
// ====== Fetch dari External API ======
---
const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10');
const posts = await response.json();
---
{posts.map(post => (
<article>
<h2>{post.title}</h2>
<p>{post.body}</p>
</article>
))}
// ====== Fetch dengan Error Handling ======
---
let data = [];
let error = null;
try {
const response = await fetch('https://api.example.com/products');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
data = await response.json();
} catch (err) {
error = err.message;
console.error('Gagal fetch data:', err);
}
---
{error ? (
<div class="error">Gagal memuat data: {error}</div>
) : (
<div class="grid">
{data.map(item => <Card {...item} />)}
</div>
)}
// ====== Fetch dengan Environment Variables ======
---
// Pastikan env variable tersedia di .env
// ASTRO_API_KEY=your_secret_key
// PUBLIC_API_URL=https://api.example.com
const apiKey = import.meta.env.ASTRO_API_KEY; // Server-only (tidak exposed ke client)
const apiUrl = import.meta.env.PUBLIC_API_URL; // Public (exposed ke client)
const response = await fetch(`${apiUrl}/data`, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
const data = await response.json();
---
// ====== Fetch dari Local Files ======
---
import fs from 'node:fs';
import path from 'node:path';
// Baca file JSON lokal
const configPath = path.resolve('./src/data/config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
// Atau gunakan import
import teamData from '../data/team.json';
---
// ====== SSR: Fetch dari database ======
---
// Pastikan output: 'server' di astro.config.mjs
import { db } from '../lib/database';
// Kode ini berjalan di server setiap request (SSR)
const user = await Astro.locals.user;
const orders = await db.orders.findMany({
where: { userId: user.id },
orderBy: { createdAt: 'desc' },
});
---
8. Integrations & Adapters
Astro memiliki ekosistem integrasi yang kaya. Integrations menambahkan fitur seperti framework UI, CSS tools, dan optimizer. Adapters memungkinkan deployment ke berbagai hosting platform.
# Tambahkan integrasi menggunakan CLI Astro
npx astro add react # React components
npx astro add vue # Vue components
npx astro add svelte # Svelte components
npx astro add tailwind # Tailwind CSS
npx astro add mdx # MDX support
npx astro add sitemap # Auto sitemap
npx astro add partytown # Third-party script optimization
npx astro add prefetch # Link prefetching
# Tambahkan adapter untuk deployment
npx astro add node # Node.js server
npx astro add vercel # Vercel deployment
npx astro add netlify # Netlify deployment
npx astro add cloudflare # Cloudflare Pages
npx astro add deno # Deno Deploy
# Contoh konfigurasi dengan beberapa integrations
# astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
integrations: [react(), tailwind(), mdx(), sitemap()],
output: 'server',
adapter: vercel(),
});
Contoh Integrasi Lengkap
// ====== Menggunakan React + Svelte + Tailwind dalam satu proyek ======
// src/components/ReactCounter.jsx (React Island)
import { useState } from 'react';
export default function ReactCounter() {
const [count, setCount] = useState(0);
return (
<div className="p-4 bg-slate-800 rounded-lg">
<p className="text-xl text-white">React Counter: {count}</p>
<button onClick={() => setCount(c => c + 1)} className="btn-primary">
Tambah
</button>
</div>
);
}
// src/components/SvelteSearch.svelte (Svelte Island)
<script>
let query = '';
$: results = query.length > 2 ? ['Hasil 1', 'Hasil 2'] : [];
</script>
<input bind:value={query} placeholder="Cari..." />
{#each results as result}
<p>{result}</p>
{/each}
// src/pages/index.astro β Gabungan keduanya!
---
import BaseLayout from '../layouts/BaseLayout.astro';
import ReactCounter from '../components/ReactCounter.jsx';
import SvelteSearch from '../components/SvelteSearch.svelte';
---
<BaseLayout judul="Beranda">
<h1 class="text-4xl font-bold text-white">Selamat Datang</h1>
<!-- React island: hydrated saat visible -->
<ReactCounter client:visible />
<!-- Svelte island: hydrated saat load -->
<SvelteSearch client:load />
<!-- Semua di luar islands = HTML statis, 0 JS! -->
</BaseLayout>
9. Deployment
Astro bisa di-deploy ke hampir semua platform hosting. Untuk SSG (static), cukup upload folder dist/. Untuk SSR, perlu adapter yang sesuai.
# Build untuk production npm run build # Folder output: dist/ # βββ _astro/ β CSS & JS assets # βββ index.html β Halaman utama # βββ about.html β Halaman about # βββ blog/ β Halaman blog # βββ ... # ====== Deploy ke Cloudflare Pages ====== # 1. Push ke GitHub # 2. Connect repo di Cloudflare Dashboard # 3. Settings: # Build command: npm run build # Build output directory: dist # 4. Tambahkan adapter: npm i @astrojs/cloudflare # 5. Update astro.config.mjs: # output: 'server', # adapter: cloudflare(), # ====== Deploy ke Vercel ====== npx vercel # ====== Deploy ke Netlify ====== # Netlify auto-detect Astro project # Atau manual: drag & drop folder dist/ ke Netlify dashboard # ====== Deploy ke GitHub Pages ====== # 1. Tambahkan site URL di astro.config.mjs # 2. Jalankan: npm run build # 3. Push folder dist/ ke branch gh-pages # ====== Preview build secara lokal ====== npm run preview # β http://localhost:4321/
10. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Astro: