Cara Mengkonversi Website HTML Restaurant ke Astro Framework: Panduan Lengkap untuk Pemula

Halo teman-teman! Kali ini kita akan bahas tentang Astro Framework dan bagaimana cara mengkonversi website HTML statis menjadi project Astro yang lebih modern dan powerfull. Mungkin banyak dari kalian yang sudah familiar dengan HTML, CSS, dan JavaScript biasa, tapi merasa ada yang kurang dari segi performa atau developer experience. Nah, Astro ini bisa jadi solusi yang tepat buat kalian.

Apa itu Astro Framework?

Astro adalah sebuah modern web framework yang dirancang khusus untuk membuat website yang super cepat. Berbeda dengan framework lain seperti React atau Vue yang mengirim banyak JavaScript ke browser, Astro punya pendekatan unik yaitu mengirim HTML statis sebanyak mungkin dan hanya menambahkan JavaScript kalau memang benar-benar dibutuhkan. Konsep ini disebut dengan partial hydration atau islands architecture. Jadi website kita tetap interaktif tapi gak berat karena kebanyakan JavaScript yang gak diperlukan.

Yang menarik dari Astro adalah kita bisa menggunakan component dari berbagai framework sekaligus. Mau pakai React, Vue, Svelte, atau bahkan vanilla JavaScript? Semuanya bisa digabungin dalam satu project Astro. Fleksibel banget kan? Tapi untuk tutorial kali ini, kita akan fokus menggunakan Astro component murni aja biar lebih simple dan gampang dipahami.

Kenapa Harus Migrate dari HTML Murni ke Astro?

Mungkin kalian bertanya-tanya, kalau HTML biasa udah jalan dengan baik, kenapa harus repot-repot pindah ke Astro? Well, ada beberapa alasan kuat kenapa migrasi ini worth it banget.

Pertama dari segi performa. Website yang dibuat dengan Astro itu jauh lebih cepat karena konsep partial hydration tadi. Bayangkan kalau kita punya website dengan banyak komponen interaktif. Dengan HTML biasa, semua JavaScript harus di-load sekaligus. Tapi dengan Astro, hanya komponen yang butuh interaktivitas aja yang akan di-hydrate. Sisanya tetap jadi HTML statis yang ringan. Hasilnya? Loading time lebih cepat, user experience lebih bagus, dan tentunya SEO score juga meningkat.

Kedua, developer experienc yang jauh lebih baik. Dengan Astro, kita bisa memecah website jadi component-component kecil yang reusable. Misalnya komponen button, card, atau navbar bisa kita pakai berulang-ulang tanpa harus copy-paste code. Kalau ada perubahan desain, kita tinggal edit satu file component aja, otomatis semua tempat yang pakai component itu ikut berubah. Effisien banget kan?

Ketiga, SEO optimization yang sudah built-in. Astro secara default menghasilkan HTML statis yang sangat SEO-friendly. Search engine bot bisa crawl konten kita dengan mudah karena semua konten sudah ada di HTML, gak perlu tunggu JavaScript dijalankan dulu seperti di beberapa framework lain. Plus, dengan performa yang cepat, ranking di search engine juga cenderung lebih bagus.

Overview Project Baksoku Restaurant Website

Untuk praktek kali ini, kita akan mengkonversi website restaurant bernama Baksoku. Website ini adalah salah satu template HTML dari ShaynaKit yang bisa kalian temukan di shaynakit.com. Template ini dipilih karena punya berbagai komponen menarik yang sering dipakai di website modern seperti navigation dengan dropdown, image slider menggunakan Swiper, card components, dan interactive tabs.

Website Baksoku ini menggunakan Tailwind CSS untuk styling, jadi tampilannya sudah modern dan responsive. Ada beberapa section utama yang akan kita konversi: hero section dengan slider carousel, section keunggulan restaurant dengan feature cards, gallery restaurant populer dengan hover effects, section restaurant terdekat, dan yang terakhir adalah section chef profesional dengan tab switching. Semua komponen ini punya interaktivitas yang cukup kompleks, jadi cocok banget buat belajar bagaimana menangani JavaScript di Astro.

Yang menarik dari template ini adalah penggunaan warna hijau natural yang cocok banget dengan tema food industry. Primary color-nya adalah hijau muda seger, sedangkan secondary color pakai hijau tua yang memberikan kesan profesional. Kombinasi warna ini akan kita migrate juga ke Astro menggunakan Tailwind configuration.

Apa yang Akan Dipelajari di Artikel Ini

Di artikel ini, kalian akan belajar step-by-step bagaimana mengkonversi HTML statis menjadi Astro project yang production-ready. Kita akan mulai dari setup project, konfigurasi Tailwind CSS dengan custom theme, sampai membuat component-component yang reusable dan interaktif.

Kalian akan paelajari bagaimana cara memecah HTML monolith menjadi komponen-komponen kecil yang modular. Misalnya, dari satu file index.html yang besar, kita akan pecah jadi Header component, HeroSlider component, FeatureCard component, dan lain-lain. Setiap component akan punya tanggung jawabnya masing-masing, jadi code kita jadi lebih terorganisir dan mudah di-maintain.

Selain itu, kita juga akan explore bagaimana cara menangani interaktivitas dan JavaScript di Astro. Astro punya konsep yang disebut client directives, yang memungkinkan kita mengontrol kapan sebuah komponen harus di-hydrate di client side. Kita akan pelajari kapan harus pakai client:load, client:visible, atau client:idle. Pemahaman tentang ini penting banget supaya website kita tetap cepat tapi tetap interaktif.

Kita juga akan bahas bagaimana cara mengintegrasikan library pihak ketiga seperti Swiper.js di Astro. Ini penting karena di dunia nyata, kita sering banget pakai library eksternal untuk fitur-fitur tertentu. Kalian akan tau cara yang benar untuk menggunakan library yang butuh DOM manipulation di environment Astro.

Di akhir tutorial, kita akan lihat hasil performance comparison antara versi HTML original dengan versi Astro. Kalian akan surprised dengan improvement yang bisa dicapai hanya dengan migrasi ini. Lighthouse score, bundle size, dan loading time semuanya akan jauh lebih baik. Dan yang paling penting, semua ini dicapai tanpa mengorbankan fungsionalitas atau user experience.

Jadi siapkan kopi atau teh favorit kalian, dan mari kita mulai journey mengkonversi website HTML ke Astro Framework. Tutorial ini dirancang untuk pemula, jadi gak perlu khawatir kalau kalian belum pernah sentuh Astro sebelumnya. Kita akan jalan perlahan tapi pasti. Ready? Let's go!

Persiapan Sebelum Memulai

Sebelum kita mulai ngoding dan mengkonversi website, ada beberapa hal yang perlu disiapkan terlebih dahulu. Gak usah khawatir, persiapannya gak ribet kok. Kita cuma perlu pastikan beberapa tools udah terinstal di komputer kalian dan pemahaman dasar tentang web developmnet.

Prerequisites yang Harus Dipenuhi

Pertama, pastikan Node.js versi 18.14.1 atau lebih tinggi sudah terinstal. Cek dengan perintah node --version di terminal. Kalau belum punya, unduh versi LTS dari nodejs.org. npm biasanya udah otomatis terinstal bersamaan dengan Node.js, verifikasi dengan npm --version.

Kedua, editor teks yang nyaman, saya rekomendasikan VS Code karena punya ekstensi Astro yang kasih penyorotan sintaks dan pelengkapan otomatis. Ditambah lagi gratis dan ringan.

Ketiga, pemahaman dasar HTML, CSS, dan JavaScript. Gak perlu ahli, cukup paham konsep dasar seperti tag HTML, pemilih CSS, dan fungsi JavaScript. Kalau udah pernah bikin website sederhana, itu udah cukup banget.

Tools yang Dibutuhkan

Selain prerequisites di atas, ada beberapa tools tambahan yang akan mempermudah proses development. Pertama adalah Git untuk version control. Meskipun gak wajib, tapi sangat direkomendasikan supaya kalian bisa track perubahan code dan gampang rollback kalau ada kesalahan. Kalian juga bisa upload project ke GitHub sebagai portfolio.

Kedua adalah browser modern seperti Chrome, Firefox, atau Edge. Kita akan sering inspect element dan testing responsive design, jadi browser dengan DevTools yang bagus itu penting. Chrome DevTools khususnya punya fitur Lighthouse yang berguna banget untuk measuring performance website kita nanti.

Ketiga, kalian perlu download template HTML Baksoku Restaurant dari ShaynaKit. Template ini akan jadi base project yang akan kita konversi. Pastikan kalian udah download dan extract semua file termasuk folder assets yang berisi gambar dan icons.

Setup Project Astro Baru

Sekarang kita masuk ke bagian seru, yaitu setup project Astro. Prosesnya sebenarnya gampang banget berkat Astro CLI yang udah dirancang user-friendly. Buka terminal atau command prompt kalian, navigasi ke folder dimana kalian mau simpen project, lalu jalankan command berikut:

npm create astro@latest bwa-resto-astro

Command ini akan menjalankan Astro setup wizard yang akan guide kalian step-by-step. Wizard ini akan nanya beberapa pertanyaan untuk setup project. Pertama, kalian akan ditanya apakah mau instal dependencies sekarang. Pilih yes supaya semua package yang dibutuhkan langsung terinstall.

Kedua, wizard akan tanya apakah mau initialize Git repository. Ini optional tapi saya sarankan pilih yes supaya dari awal project udah tracked dengan Git. Kalian juga akan ditanya apakah mau instal TypeScript. Untuk tutorial ini, kita akan pakai JavaScript aja supaya lebih simple, jadi pilih no atau use strict mode kalau kalian prefer JavaScript biasa.

Selanjutnya adalah template selection. Astro menyediakan beberapa template starter seperti blog, portfolio, atau minimal. Untuk project kita, pilih "Empty" karena kita akan build dari scratch berdasarkan HTML template yang udah ada. Ini memberi kita full control tanpa harus remove boilerplate code yang gak diperlukan.

Setelah semua pertanyaan dijawab, Astro CLI akan membuat folder project baru bernama bwa-resto-astro dan menginstal semua dependencies yang diperlukan. Proses ini mungkin butuh beberapa menit tergantung koneksi internet kalian. Sambil menunggu, kalian bisa bikin kopi dulu atau stretch sebentar.

Kalau prosesnya udah selesai, navigate ke folder project dengan command:

cd bwa-resto-astro

Lalu jalankan development server dengan:

npm run dev

Command ini akan start local development server, biasanya di http://localhost:4321. Buka URL tersebut di browser dan kalian akan melihat welcome page dari Astro. Selamat! Project Astro pertama kalian udah jalan. Sekarang kita siap untuk mulai mengkonversi template HTML Baksoku ke dalam structure Astro yang lebih modern dan optimized.

Satu tips penting: jangan close terminal yang menjalankan dev server. Biarkan tetap running karena Astro punya fitur hot reload yang akan otomatis refresh browser setiap kali ada perubahan code. Ini akan sangat membantu proses develpment supaya lebih cepat dan efficient. Kalau perlu jalankan command lain, buka tab terminal baru aja.

Setup Tailwind CSS Custom Configuration

Setelah project Astro kita jalan, sekarang saatnya setup Tailwind CSS supaya styling dari template HTML Baksoku bisa kita pakai. Tailwind CSS adalah utility-first CSS framework yang udah sangat populer dan untungnya Astro punya integrasi yang smooth banget dengan Tailwind versi terbaru.

Instalasi Tailwind CSS di Astro

Untuk menginstal Tailwind di project Astro, kita akan pakai metode terbaru yang menggunakan plugin Vite. Ini adalah cara yang direkomendasikan oleh dokumentasi resmi Tailwind CSS. Buka terminal baru (biarkan dev server tetap jalan di terminal sebelumnya), lalu jalankan perintah berikut:

npm install tailwindcss @tailwindcss/vite

Perintah ini akan menginstal Tailwind CSS dan plugin Vite-nya sekaligus. Berbeda dengan cara lama yang pakai npx astro add tailwind, metode ini memberi kita kontrol yang lebih baik dan setup yang lebih modern. Proses instalasi mungkin butuh beberapa detik tergantung kecepatan internet kalian.

Setelah instalasi selesai, kita perlu menambahkan plugin Tailwind ke konfigurasi Astro. Buka file astro.config.mjs di root project dan modifikasi seperti ini:

import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  vite: {
    plugins: [tailwindcss()],
  },
});

Konfigurasi ini memberitahu Astro untuk menggunakan plugin Tailwind CSS melalui Vite. Dengan setup seperti ini, Tailwind akan terintegrasi dengan baik dalam build process Astro.

Membuat File CSS Global

Langkah selanjutnya adalah membuat file CSS global yang akan mengimport Tailwind. Buat folder baru bernama styles di dalam folder src, lalu buat file global.css di dalamnya. Path lengkapnya jadi src/styles/global.css. Isi file tersebut dengan:

@import "tailwindcss";

Simpel banget kan? Cukup satu baris doang untuk mengimport semua utility classes Tailwind. Ini adalah cara baru yang lebih efisien dibanding metode lama yang pakai @tailwind base, @tailwind components, dan @tailwind utilities.

Memindahkan Custom Colors dari Template

Sekarang kita perlu memindahkan skema warna dari template HTML original ke konfigurasi Tailwind. Kalau kalian buka file input.css dari template Baksoku, kalian akan liat ada beberapa CSS variables yang didefine di :root. Kita akan tambahkan variables ini langsung di file global.css:

@import url("<https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap>");
@import "tailwindcss";

:root {
  --background: #fafafa;
  --foreground: #232631;
  --primary: #93C572;
  --secondary: #2C5F2D;
  --muted: #656565;
  --star: #ffb800;
  --placeholder: #858585;
}

@theme inline {
  --font-poppins: "Poppins", sans-serif;
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-primary: var(--primary);
  --color-secondary: var(--secondary);
  --color-muted: var(--muted);
  --color-star: var(--star);
  --color-placeholder: var(--placeholder);
  --drop-shadow-custom: 8px 8px 25px rgba(102, 102, 102, 0.04);
  --drop-shadow-custom2: -8px 8px 25px rgba(102, 102, 102, 0.04);
  --shadow-primary: 0 4px 10px 0 rgba(147, 197, 114, 0.8);
  --shadow-secondary: 0 4px 10px 0 rgba(90, 79, 207, 0.1);
}

body {
  @apply bg-background text-foreground font-poppins;
}

Dengan menggunakan directive @theme, kita bisa mendefinisikan custom design tokens yang langsung bisa dipakai di seluruh project. Format ini adalah cara terbaru Tailwind CSS v4 untuk mendefine custom values. Perhatikan juga baris kedua dimana kita import font Poppins langsung dari Google Fonts, jadi gak perlu lagi tambahkan tag <link> di HTML. Sekarang kita bisa pakai bg-primary untuk background hijau muda, text-secondary untuk teks hijau tua, shadow-primary untuk shadow tombol, dan font-poppins untuk font, semuanya langsung tersedia.

Import Global CSS ke Layout

Supaya styling Tailwind bisa dipake di seluruh halaman, kita perlu import file global.css tadi. Buka file src/pages/index.astro dan tambahkan import di bagian atas:

---
import '../styles/global.css';
---

<html lang="id">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <title>Baksoku Restaurant - BuildWithAngga</title>
  </head>
  <body class="bg-background font-poppins">
    <div class="min-h-screen flex items-center justify-center">
      <div class="text-center">
        <h1 class="text-4xl font-bold text-foreground mb-4">
          Baksoku Restaurant
        </h1>
        <p class="text-muted mb-8">
          Website sedang dalam proses konversi ke Astro
        </p>
        <button class="bg-primary text-foreground px-8 py-4 rounded-xl font-semibold shadow-primary hover:shadow-lg transition-all duration-300">
          Mulai Reservasi
        </button>
      </div>
    </div>
  </body>
</html>

Perhatikan baris pertama di dalam frontmatter (bagian yang diapit tiga dash), kita import file global.css. Import ini harus dilakukan di setiap file Astro yang mau pakai Tailwind, atau lebih baik lagi nanti kita akan pindahkan ke layout component supaya gak perlu import berkali-kali. Perhatikan juga bahwa kita gak perlu lagi tambahkan tag <link> untuk Google Fonts di <head> karena font Poppins sudah di-import di file global.css tadi.

Testing Tailwind Configuration

Hasil Setup Tailwind di Astro
Hasil Setup Tailwind di Astro

Sekarang saatnya test apakah semua konfigurasi berjalan dengan baik. Pastikan dev server masih jalan, kalau belum jalankan dengan npm run dev. Buka browser dan akses http://localhost:4321. Kalian harusnya melihat halaman dengan judul "Baksoku Restaurant" di tengah layar dengan background abu-abu muda, teks hitam, dan tombol hijau dengan shadow yang halus.

Coba hover ke tombol "Mulai Reservasi", kalian akan liat efek shadow yang berubah. Kalau semua warna muncul dengan benar, font Poppins keload, dan shadow di tombol terlihat, berarti konfigurasi Tailwind kita udah berhasil dengan sempurna.

Perlu diingat bahwa dengan setup metode terbaru ini, kita punya beberapa keuntungan. Pertama, build time yang lebih cepat karena integrasi Vite yang optimal. Kedua, konfigurasi yang lebih simple tanpa perlu file tailwind.config.js yang ribet. Ketiga, hot module replacement yang lebih responsif, jadi setiap perubahan CSS langsung keliatan di browser tanpa delay.

Setup Tailwind yang baik di awal akan sangat membantu proses development selanjutnya. Dengan custom colors dan shadows yang udah didefinisikan, kita bisa langsung fokus mengkonversi komponen-komponen dari template HTML original tanpa harus bolak-balik cek nilai warna atau shadow. Semua utility class Tailwind standard juga tetap bisa dipakai, jadi kita punya fleksibilitas penuh dalam styling. Konfigurasi yang konsisten seperti ini adalah fondasi penting untuk membuat website yang scalable dan mudah di-maintain kedepannya.

Memahami Struktur Project HTML

Sebelum kita mulai mengkonversi website ke Astro, penting banget untuk memahami struktur dari template HTML yang akan kita migrate. Ini seperti peta jalan yang akan memandu kita dalam proses konversi. Mari kita bedah file index.html dari template Baksoku Restaurant secara mendetail.

Analisis File Index.html

File index.html dari template Baksoku ini adalah file monolith yang berisi semua markup dalam satu file. Ukurannya cukup besar karena menampung seluruh konten website dari header sampai footer. Struktur dasarnya mengikuti pola HTML standar dengan tag <head> untuk metadata dan stylesheet, kemudian tag <body> untuk konten utama.

Di bagian head, kita bisa lihat ada beberapa hal penting. Pertama adalah link ke file output.css yang merupakan hasil compile dari Tailwind CSS. Kedua ada link ke Swiper CSS dari CDN untuk fitur slider carousel. Meta tags untuk charset dan viewport juga udah dikonfigurasi dengan baik untuk memastikan website responsive di berbagai perangkat.

Bagian body dibagi menjadi beberapa section besar yang masing-masing punya fungsi spesifik. Ada header untuk navigasi, main untuk konten utama, dan beberapa section di dalamnya. Setiap section ini nantinya akan kita pecah jadi component terpisah di Astro supaya lebih modular dan reusable.

Identifikasi Komponen yang Bisa Dipecah

Setelah menganalisis keseluruhan struktur, kita bisa identifikasi beberapa komponen utama yang cocok untuk dipecah menjadi Astro components. Pemisahan ini penting supaya code kita lebih organized dan mudah di-maintain kedepannya.

Header dan Navigation

Header dan Navigation
Header dan Navigation

Komponen pertama yang jelas bisa dipisahkan adalah header dengan navigasi. Di template ini, header berisi logo di tengah, menu navigasi di kiri (Beranda, Reservasi, Tentang Kami, dan dropdown Kategori Menu), serta search bar dan tombol login di kanan. Header ini sangat ideal jadi component terpisah karena akan muncul di setiap halaman. Dengan memecahnya jadi component, kita cukup edit satu file kalau ada perubahan di navigasi.

Dropdown menu di navigasi juga punya interaktivitas tersendiri yang perlu dihandle dengan JavaScript. Ini akan jadi tantangan menarik saat kita migrate ke Astro karena kita harus tentukan apakah interaktivitas ini perlu di-hydrate di client side atau bisa dihandle dengan cara lain.

Hero Section dengan Swiper

Hero Section dengan Swiper
Hero Section dengan Swiper

Section berikutnya adalah hero section yang menggunakan Swiper.js untuk membuat carousel slider. Section ini menampilkan dua slide yang masing-masing berisi gambar restaurant dengan info detail yang muncul saat di-hover. Komponen ini cukup kompleks karena menggabungkan library eksternal (Swiper) dengan interaktivitas hover.

Di template original, Swiper diload dari CDN dan diinisialisasi dengan JavaScript di file main.js. Saat kita migrate ke Astro, kita perlu install Swiper sebagai npm package dan mengkonfigurasinya dengan benar supaya bisa jalan di environment Astro. Ini akan melibatkan penggunaan client directives untuk memastikan Swiper di-hydrate di client side.

Feature Cards Section

Feature Cards Section
Feature Cards Section

Section "Mengapa pilih restaurant kami?" berisi empat feature cards yang menampilkan keunggulan restaurant. Setiap card punya struktur yang identik: icon di atas, judul, deskripsi, dan link "View Details" di bawah. Ini adalah kandidat sempurna untuk dibuat jadi reusable component.

Dengan membuat FeatureCard component, kita bisa passing data melalui props seperti icon, title, description, dan link. Kemudian di halaman utama, kita tinggal loop array data untuk render beberapa kartu sekaligus. Ini jauh lebih efisien dibanding copy-paste markup yang sama berulang kali.

Restaurant Cards Section

Restaurant Cards Section
Restaurant Cards Section

Ada dua jenis restaurant cards di template ini. Pertama adalah "Restaurant Paling Populer" yang punya efek hover khusus dimana card bisa expand dan menampilkan informasi detail. Kedua adalah "Restaurant di Sekitar Anda" yang informasinya selalu visible tanpa perlu hover.

Restaurant cards yang populer ini punya JavaScript interaktivitas yang cukup kompleks di file card.js. Saat di-hover, lebar card berubah dari 214px jadi 400px dan info detail muncul. Logika ini perlu kita migrate dengan hati-hati ke Astro, mungkin menggunakan kombinasi CSS dan JavaScript yang di-hydrate di client side.

Chef Section dengan Tab Switching

Chef Section dengan Tab Switching
Chef Section dengan Tab Switching

Section terakhir yang cukup kompleks adalah bagian chef profesional. Section ini menggunakan tab pattern dimana ada empat tab di sebelah kanan, dan konten di sebelah kiri berubah sesuai tab yang dipilih. Data chef seperti nama, lokasi, pengalaman, dan restaurant tempat bekerja semuanya distore dalam array JavaScript di file tab.js.

Interaktivitas tab ini menarik karena melibatkan state management sederhana. Saat user klik tab, JavaScript akan update konten di sebelah kiri termasuk gambar chef, experience years, dan info restaurant. Di Astro, kita bisa implement ini dengan beberapa cara, bisa pakai client-side JavaScript seperti original atau explore opsi lain yang lebih Astro-friendly.

Memahami Dependencies

Template Baksoku ini menggunakan beberapa dependencies penting yang perlu kita pahami sebelum mulai konversi. Pemahaman tentang dependencies ini crucial supaya kita tau cara mengintegrasikannya dengan Astro nanti.

Tailwind CSS

Tailwind adalah framework CSS utility-first yang digunakan untuk semua styling di template ini. File input.css berisi konfigurasi dasar Tailwind dengan beberapa customizations seperti custom colors (primary, secondary, muted, dll) dan custom shadows. Di template original, Tailwind di-compile jadi output.css yang kemudian di-load di HTML.

Saat migrate ke Astro, setup Tailwind akan sedikit berbeda. Kita udah discuss ini di section sebelumnya dimana kita pakai plugin @tailwindcss/vite dan membuat file global.css. Yang penting adalah memastikan semua custom configurations dari input.css original berhasil dipindahkan supaya visual website tetap sama persis.

Swiper.js

Swiper adalah library JavaScript untuk membuat carousel/slider yang powerful dan customizable. Di template ini, Swiper diload dari CDN dengan versi 11. File main.js berisi konfigurasi Swiper yang cukup simple: slidesPerView auto, spaceBetween 40px, grabCursor true, dan centeredSlides false.

Untuk Astro, kita akan install Swiper via npm dengan command npm install swiper. Kemudian kita perlu import Swiper di component yang membutuhkannya dan menambahkan client directive supaya Swiper bisa diinisialisasi di browser. Konfigurasinya akan tetap sama, cuma cara implementasinya yang disesuaikan dengan arsitektur Astro.

Custom JavaScript Files

Template ini punya tiga file JavaScript custom: main.js untuk Swiper initialization, tab.js untuk tab switching di chef section, card.js untuk expand/collapse restaurant cards, dan dropdown.js untuk menu dropdown. Setiap file ini handle interaktivitas spesifik yang membuat website lebih dynamic.

Saat migrate ke Astro, kita perlu evaluate apakah semua JavaScript ini masih diperlukan atau bisa diganti dengan approach yang lebih modern. Misalnya untuk dropdown, mungkin bisa pakai Alpine.js atau bahkan pure CSS. Untuk yang memang perlu JavaScript, kita akan pindahkan logic-nya ke dalam Astro components dengan menggunakan tag <script> dan client directives yang sesuai.

Yang penting diingat adalah Astro punya konsep islands architecture dimana JavaScript hanya di-load untuk component yang benar-benar membutuhkan interactivity. Ini berbeda dengan template HTML original yang load semua JavaScript di awal. Jadi kita perlu strategis dalam menentukan component mana yang perlu di-hydrate dan kapan (on load, on idle, atau on visible).

Dengan pemahaman mendalam tentang struktur template dan dependencies-nya, sekarang kita udah siap untuk mulai proses konversi sebenarnya. Di section berikutnya, kita akan mulai setup struktur project Astro dan membuat component pertama kita.

Setup Astro Project Structure

Setelah kita paham struktur template HTML original, sekarang saatnya setup project Astro dengan struktur folder yang rapi dan terorganisir. Struktur yang baik dari awal akan sangat membantu proses development dan memudahkan maintenance kedepannya. Mari kita mulai dari yang paling dasar.

Memahami Struktur Folder Astro

Project Astro punya struktur folder yang sudah direkomendasikan dan mengikuti best practices. Folder utama yang akan kita pakai adalah src/ dimana semua source code kita akan berada. Di dalam folder src ini, ada beberapa subfolder penting yang masing-masing punya fungsi spesifik.

Folder pages/ adalah tempat dimana kita menyimpan halaman-halaman website. Setiap file di folder ini otomatis jadi route. Misalnya pages/index.astro akan jadi homepage di route /, pages/about.astro akan jadi /about, dan seterusnya. Ini adalah konsep file-based routing yang membuat routing jadi sangat simple dan intuitif.

Folder components/ digunakan untuk menyimpan komponen-komponen reusable yang bisa dipakai di berbagai halaman. Contohnya Header, Footer, Card, Button, dan komponen-komponen lain yang akan kita pecah dari template HTML. Dengan menaruh komponen di folder terpisah, code kita jadi lebih modular dan mudah dikelola.

Folder layouts/ berisi layout template yang bisa di-wrap ke halaman-halaman kita. Layout biasanya berisi struktur HTML dasar seperti <html>, <head>, dan <body>, plus komponen yang muncul di semua halaman seperti Header dan Footer. Dengan layout, kita gak perlu repeat code yang sama di setiap halaman.

Folder styles/ adalah tempat untuk file CSS global seperti global.css yang udah kita buat tadi. File CSS yang ada di sini bisa di-import di layout atau komponen manapun yang membutuhkan. Kita juga bisa taruh file CSS lain kalau ada styling khusus yang gak pakai Tailwind.

Membuat Struktur Folder yang Tepat

Sekarang kita akan buat struktur folder yang sesuai dengan kebutuhan project Baksoku Restaurant. Buka terminal di root project kalian (pastikan masih di folder bwa-resto-astro), lalu jalankan perintah berikut untuk membuat folder-folder yang diperlukan:

mkdir -p src/components src/layouts src/styles

Perintah mkdir -p akan membuat folder beserta parent folder-nya kalau belum ada. Setelah perintah ini dijalankan, struktur folder kita sekarang akan jadi seperti ini:

bwa-resto-astro/
├── src/
│   ├── components/
│   ├── layouts/
│   ├── pages/
│   │   └── index.astro
│   └── styles/
│       └── global.css
├── public/
├── astro.config.mjs
├── package.json
└── tsconfig.json

Folder public/ udah otomatis dibuat saat kita initialize project Astro. Folder ini digunakan untuk static assets yang gak perlu diproses oleh build system, seperti favicon, robots.txt, atau file-file lain yang pengen kita serve as-is. Nantinya kita akan taruh gambar dan icon di sini.

Setup Static Assets

Template Baksoku punya banyak gambar dan icon yang perlu kita pindahkan ke project Astro. Di template original, semua assets ada di folder assets/images/. Kita perlu copy folder ini ke folder public/ di project Astro kita.

Pertama, copy folder assets dari template HTML original ke folder public/ di project Astro. Struktur akhirnya akan jadi seperti ini:

public/
└── assets/
    └── images/
        ├── icons/
        │   ├── logo-saya.svg
        │   ├── search.svg
        │   ├── arrow_bottom.svg
        │   ├── arrow_right.svg
        │   ├── Location.svg
        │   ├── star.svg
        │   └── ... (icon lainnya)
        └── thumbnails/
            ├── hero-restaurant-1.jpg
            ├── hero-dining-2.jpg
            ├── popular-resto-1.jpg
            └── ... (gambar lainnya)

Yang perlu diperhatikan adalah path untuk mengakses file di folder public/ langsung dimulai dari root. Jadi kalau di HTML original kita pakai ./assets/images/icons/logo-saya.svg, di Astro kita cukup pakai /assets/images/icons/logo-saya.svg (tanpa titik di depan). Astro akan otomatis serve file dari folder public.

Konfigurasi Tambahan untuk Assets

Untuk optimasi gambar, Astro punya fitur built-in image optimization yang sangat powerfull. Tapi untuk sekarang, kita akan pakai cara simple dulu yaitu taruh gambar di public folder. Nanti kalau project udah jalan sempurna, kita bisa explore fitur @astrojs/image untuk optimasi lebih lanjut.

Kalau kalian mau, bisa juga organize assets dengan lebih detail. Misalnya pisahkan icon dan gambar di folder berbeda, atau buat folder khusus untuk font kalau ada custom font yang di-host sendiri. Yang penting adalah konsistensi dalam penamaan dan struktur folder supaya gampang dicari kalau butuh edit atau tambah asset baru.

Verifikasi Struktur Project

Setelah semua folder dibuat dan assets dipindahkan, mari kita verifikasi bahwa struktur project kita udah benar. Buka VS Code atau text editor kalian dan pastikan struktur foldernya seperti ini:

bwa-resto-astro/
├── src/
│   ├── components/          # Untuk component reusable
│   ├── layouts/             # Untuk layout templates
│   ├── pages/               # Untuk halaman website
│   │   └── index.astro
│   └── styles/              # Untuk CSS global
│       └── global.css
├── public/                  # Untuk static assets
│   └── assets/
│       └── images/
│           ├── icons/
│           └── thumbnails/
├── node_modules/            # Dependencies (auto-generated)
├── astro.config.mjs         # Konfigurasi Astro
├── package.json             # Project dependencies
└── package-lock.json        # Lock file dependencies

Struktur ini adalah fondasi yang solid untuk project kita. Dengan folder yang terorganisir dengan baik, kita akan lebih mudah navigate dan maintain code kedepannya. Setiap file punya tempatnya masing-masing dan gak ada yang berantakan.

Tips Organisasi Project

Beberapa tips tambahan untuk menjaga project tetap rapi. Pertama, gunakan naming convention yang konsisten. Untuk component, saya rekomendasikan pakai PascalCase seperti Header.astro, FeatureCard.astro, RestaurantCard.astro. Untuk file utility atau helper, pakai camelCase atau kebab-case.

Kedua, kalau project mulai besar dan komponen makin banyak, pertimbangkan untuk membuat subfolder di dalam components/. Misalnya components/navigation/, components/cards/, components/forms/, dan seterusnya. Ini akan membuat struktur lebih scalable saat project berkembang.

Ketiga, selalu dokumentasikan code kalian dengan comment yang jelas. Tulis comment di bagian-bagian yang kompleks atau yang mungkin membingungkan developer lain (atau diri sendiri di masa depan). Comment yang baik adalah investasi yang akan menghemat waktu debugging nanti.

Dengan setup struktur project yang proper seperti ini, kita udah siap untuk mulai membuat component pertama kita di section berikutnya. Foundation yang kuat akan membuat proses development jadi lebih smooth dan enjoyable. Project yang well-structured juga lebih mudah untuk di-scale kalau kedepannya ada fitur baru yang ditambahkan atau perlu refactoring besar-besaran.

Membuat Layout Utama

Setelah struktur project kita siap, sekarang saatnya membuat layout utama yang akan jadi fondasi untuk semua halaman di website. Layout ini akan berisi struktur HTML dasar yang dipakai berulang-ulang, jadi kita gak perlu nulis ulang di setiap halaman. Konsepnya mirip seperti template master di framework lain.

Membuat File BaseLayout.astro

Mari kita buat file layout pertama kita. Buat file baru dengan nama BaseLayout.astro di dalam folder src/layouts/. File ini akan jadi wrapper utama untuk semua halaman di website Baksoku. Buka file tersebut dan mulai dengan struktur dasar:

---
import '../styles/global.css';

interface Props {
  title?: string;
  description?: string;
}

const {
  title = 'Baksoku Restaurant - Best Restaurant in Jakarta',
  description = 'Restaurant terbaik di Jakarta dengan menu nusantara yang lezat dan suasana nyaman untuk keluarga'
} = Astro.props;
---

<!DOCTYPE html>
<html lang="id">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content={description} />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <title>{title}</title>
  </head>
  <body>
    <slot />
  </body>
</html>

Mari kita bedah code di atas. Bagian yang diapit tiga dash (---) di atas adalah frontmatter, tempat kita nulis JavaScript atau TypeScript. Di sini kita import file global.css yang udah berisi konfigurasi Tailwind dan custom theme kita. Import ini penting supaya semua styling tersedia di seluruh website.

Kita juga define interface Props untuk TypeScript yang kasih tau layout ini bisa menerima props title dan description. Keduanya optional (ditandai dengan tanda tanya) dan punya default value. Jadi kalau halaman gak passing props, akan pakai value default ini. Ini pattern yang bagus untuk SEO karena setiap halaman bisa punya title dan description unik.

Memahami Slot di Astro

Tag <slot /> di dalam body adalah tempat dimana konten dari halaman yang pakai layout ini akan dirender. Konsepnya mirip seperti {children} di React atau <ng-content> di Angular. Semua konten yang kita tulis di halaman akan masuk ke posisi slot ini.

Misalnya nanti kita punya halaman index.astro yang pakai layout ini, semua konten di halaman tersebut akan muncul di posisi slot. Jadi layout cuma define struktur HTML dasar dan common elements, sedangkan konten spesifik datang dari masing-masing halaman.

Menggunakan Layout di Halaman

Sekarang kita update file src/pages/index.astro untuk pakai layout yang baru kita buat. Ganti isi file tersebut dengan:

---
import BaseLayout from '../layouts/BaseLayout.astro';
---

<BaseLayout
  title="Baksoku Restaurant - Reservasi Online Restaurant Terbaik"
  description="Reservasi online di Baksoku Restaurant. Nikmati masakan nusantara dengan harga terjangkau dan pelayanan terbaik."
>
  <div class="min-h-screen flex items-center justify-center">
    <div class="text-center">
      <h1 class="text-4xl font-bold text-foreground mb-4">
        Baksoku Restaurant
      </h1>
      <p class="text-muted mb-8">
        Website sedang dalam proses konversi ke Astro
      </p>
      <button class="bg-primary text-foreground px-8 py-4 rounded-xl font-semibold shadow-primary hover:shadow-lg transition-all duration-300">
        Mulai Reservasi
      </button>
    </div>
  </div>
</BaseLayout>

Perhatikan bagaimana kita import BaseLayout di frontmatter, lalu pakai sebagai wrapper component dengan passing props title dan description. Semua konten di dalam tag <BaseLayout> akan masuk ke slot yang kita define tadi. Dengan pattern ini, kita gak perlu repeat struktur HTML dasar di setiap halaman.

Menambahkan Meta Tags untuk SEO

Layout kita udah bagus, tapi masih bisa ditingkatkan dengan menambahkan meta tags tambahan untuk SEO dan social media sharing. Update bagian <head> di BaseLayout.astro:

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta name="description" content={description} />
  <meta name="generator" content={Astro.generator} />

  <!-- Open Graph / Facebook -->
  <meta property="og:type" content="website" />
  <meta property="og:title" content={title} />
  <meta property="og:description" content={description} />

  <!-- Twitter -->
  <meta property="twitter:card" content="summary_large_image" />
  <meta property="twitter:title" content={title} />
  <meta property="twitter:description" content={description} />

  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
  <title>{title}</title>
</head>

Meta tags Open Graph dan Twitter Card ini penting banget untuk tampilan preview saat link website dibagikan di social media. Kalau gak ada meta tags ini, preview link akan terlihat plain dan kurang menarik. Dengan meta tags yang proper, preview akan menampilkan title, description, dan gambar yang kita tentukan.

Variable Astro.generator adalah fitur built-in yang otomatis nambahin meta tag generator dengan versi Astro yang dipakai. Ini bagus untuk transparancy dan debugging kalau ada issue.

Testing Layout

Testing Layout
Testing Layout

Sekarang coba jalankan dev server kalau belum jalan dengan npm run dev, lalu buka browser ke http://localhost:4321. Kalian harusnya melihat halaman dengan styling yang proper: background abu-abu muda, font Poppins, dan tombol hijau dengan shadow. Semua styling dari global.css udah ter-apply dengan benar.

Coba juga inspect element dan liat di tab Network apakah file CSS keload dengan baik. Kalau ada error atau styling gak muncul, cek console browser untuk error messages. Biasanya kalau ada masalah, Astro akan kasih error message yang cukup jelas di terminal atau browser console.

Keuntungan Menggunakan Layout

Dengan membuat layout seperti ini, kita dapat beberapa keuntungan besar. Pertama, konsistensi di seluruh website terjamin karena semua halaman pakai struktur HTML yang sama. Kedua, maintenance jadi jauh lebih gampang karena kalau ada perubahan di struktur dasar, kita cukup edit satu file layout.

Ketiga, SEO optimization jadi lebih mudah karena kita bisa set default meta tags yang baik, dan setiap halaman tinggal override kalau perlu value yang berbeda. Keempat, loading performance lebih baik karena Astro bisa optimize dan reuse common parts dari layout.

Nanti kalau project berkembang, kita bisa buat layout tambahan untuk kebutuhan spesifik. Misalnya BlogLayout untuk halaman blog, DashboardLayout untuk halaman admin, atau AuthLayout untuk halaman login/register. Setiap layout bisa extend atau compose layout lain, memberikan fleksibilitas maksimal dalam organize structure website kita.

Layout yang kita buat ini adalah fondasi penting sebelum kita mulai membuat component-component lain. Di section berikutnya, kita akan mulai konversi Header component yang lebih kompleks dengan navigasi dan dropdown menu.

Konversi Header/Navigation Component

Sekarang kita masuk ke bagian yang lebih seru, yaitu mengkonversi komponen pertama kita dari HTML ke Astro. Header adalah komponen yang sempurna untuk dijadikan starting point karena strukturnya jelas dan akan dipakai di semua halaman. Mari kita mulai dengan membuat file component baru.

Membuat File Header.astro

Buat file baru dengan nama Header.astro di dalam folder src/components/. File ini akan berisi semua markup dan logic untuk navigation bar kita. Copy struktur HTML header dari file index.html original, lalu kita akan modifikasi supaya lebih Astro-friendly:

---
// Header.astro
---

<header class="px-[60px] mt-[60px]">
  <div class="flex justify-between items-center min-w-[1320px] h-14">
    <nav>
      <ul class="flex gap-[30px]">
        <!-- Home -->
        <li class="group flex flex-col justify-between h-[28px]">
          <a href="/" class="text-[16px] leading-[24px] font-medium">Beranda</a>
          <span class="transition-all duration-300 h-0.5 w-full rounded-full bg-secondary opacity-0 group-hover:opacity-100"></span>
        </li>

        <!-- Reservasi -->
        <li class="group flex flex-col justify-between h-[28px]">
          <a href="/reservasi" class="text-[16px] leading-[24px] font-medium">Reservasi</a>
          <span class="transition-all duration-300 h-0.5 w-full rounded-full bg-secondary opacity-0 group-hover:opacity-100"></span>
        </li>

        <!-- About Us -->
        <li class="group flex flex-col justify-between h-[28px]">
          <a href="/tentang-kami" class="text-[16px] leading-[24px] font-medium">Tentang Kami</a>
          <span class="transition-all duration-300 h-0.5 w-full rounded-full bg-secondary opacity-0 group-hover:opacity-100"></span>
        </li>

        <!-- Category Dropdown -->
        <li class="dropdown relative flex flex-col cursor-pointer">
          <div class="flex items-center h-6 w-full">
            <button type="button" class="dropdown-button text-[16px] leading-[24px] font-medium cursor-pointer w-full text-left">
              Kategori Menu
            </button>
            <div class="flex items-center justify-center h-6 w-6 shrink-0">
              <img src="/assets/images/icons/arrow_bottom.svg" alt="" class="dropdown-arrow transition-transform duration-300" />
            </div>
          </div>
          <ul class="dropdown-menu z-50 opacity-0 flex transition-all duration-300 absolute top-full left-0 mt-2 min-w-[150px] flex-col gap-1.5 bg-white rounded-xl py-2.5 shadow-lg">
            <li class="group">
              <a href="#makanan-utama" class="block py-1.5 px-5 text-[16px] leading-[24px] font-medium transition-all duration-300 hover:bg-secondary/5">
                Makanan Utama
              </a>
            </li>
            <li class="group">
              <a href="#minuman" class="block py-1.5 px-5 text-[16px] leading-[24px] font-medium transition-all duration-300 hover:bg-secondary/5">
                Minuman
              </a>
            </li>
            <li class="group">
              <a href="#dessert" class="block py-1.5 px-5 text-[16px] leading-[24px] font-medium transition-all duration-300 hover:bg-secondary/5">
                Dessert
              </a>
            </li>
            <li class="group">
              <a href="#paket" class="block py-1.5 px-5 text-[16px] leading-[24px] font-medium transition-all duration-300 hover:bg-secondary/5">
                Paket Hemat
              </a>
            </li>
          </ul>
        </li>
      </ul>
    </nav>

    <!-- Logo -->
    <a href="/">
      <img
        src="/assets/images/icons/logo-saya.svg"
        alt="Logo Baksoku Restaurant"
        class="h-[38px] w-auto"
      />
    </a>

    <!-- Search and Login -->
    <div class="flex gap-5">
      <form class="group flex w-[322px] gap-2.5 py-4 px-7 bg-white rounded-[12px] transition-all duration-300 focus-within:ring-2 focus-within:ring-secondary hover:ring-2 hover:ring-secondary">
        <div class="flex items-center justify-center h-6 w-6 shrink-0">
          <img src="/assets/images/icons/search.svg" alt="" />
        </div>
        <input
          type="text"
          class="w-full text-[16px] font-medium leading-6 placeholder-placeholder group-hover:outline-none focus:outline-none"
          placeholder="Search your favorite food"
        />
      </form>
      <a
        href="/login"
        class="w-[104px] h-14 flex items-center justify-center bg-secondary/10 text-secondary text-[16px] font-semibold leading-6 rounded-[12px] transition-all duration-300 hover:shadow-secondary"
      >
        Log In
      </a>
    </div>
  </div>
</header>

<script>
  document.addEventListener("DOMContentLoaded", () => {
    const dropdownButtons = document.querySelectorAll(".dropdown-button");
    const dropdownMenus = document.querySelectorAll(".dropdown-menu");
    const dropdownArrows = document.querySelectorAll(".dropdown-arrow");

    dropdownButtons.forEach((button, index) => {
      const menu = dropdownMenus[index];
      const arrow = dropdownArrows[index];

      button.addEventListener("click", () => {
        if (menu.classList.contains("opacity-0")) {
          dropdownMenus.forEach((m) => {
            m.classList.add("opacity-0");
            m.classList.remove("opacity-100");
          });
          dropdownArrows.forEach((a) => (a.style.transform = "rotate(0deg)"));

          menu.classList.remove("opacity-0");
          menu.classList.add("opacity-100");
          arrow.style.transform = "rotate(180deg)";
        } else {
          menu.classList.remove("opacity-100");
          menu.classList.add("opacity-0");
          arrow.style.transform = "rotate(0deg)";
        }
      });
    });

    document.addEventListener("click", (event) => {
      dropdownButtons.forEach((button, index) => {
        const menu = dropdownMenus[index];
        const arrow = dropdownArrows[index];

        if (!button.contains(event.target) && !menu.contains(event.target)) {
          menu.classList.remove("opacity-100");
          menu.classList.add("opacity-0");
          arrow.style.transform = "rotate(0deg)";
        }
      });
    });
  });
</script>

Memahami Struktur Component

Component Header ini pada dasarnya sama dengan HTML original, tapi ada beberapa perbedaan penting. Pertama, kita ubah semua path assets dari relative (./assets) jadi absolute (/assets) karena di Astro file di folder public diakses dari root. Kedua, kita update href links supaya sesuai dengan routing Astro yang akan kita buat nanti.

Perhatikan juga kita tambahkan alt text di logo untuk accessibility. Ini best practice yang sering dilupakan tapi penting banget untuk SEO dan user yang pakai screen reader. Text "Logo Baksoku Restaurant" akan dibaca oleh screen reader saat focus ke elemen logo.

Implementasi Dropdown Menu dengan JavaScript

Bagian yang paling menarik adalah dropdown menu. Di template original, dropdown functionality di-handle oleh file dropdown.js terpisah. Di Astro, kita bisa langsung masukin JavaScript ke dalam component menggunakan tag <script>. JavaScript di dalam tag script akan otomatis di-bundle dan dijalankan di browser.

Script dropdown kita cukup straightforward. Pertama, kita tunggu DOMContentLoaded event supaya semua elemen udah di-render sebelum kita akses. Lalu kita select semua dropdown button, menu, dan arrow. Untuk setiap button, kita tambahkan event listener yang toggle opacity menu dan rotate arrow icon saat diklik.

Yang penting dari script ini adalah kita juga handle click outside untuk menutup dropdown. Ini adalah UX pattern yang udah umum dimana user expect dropdown tertutup kalau klik di luar area dropdown. Event listener document-level akan cek apakah click target ada di dalam dropdown atau gak, kalau gak ada maka dropdown ditutup.

Client-Side Interactivity di Astro

Satu hal yang perlu dipahami tentang tag <script> di Astro adalah secara default script akan di-bundle dan dijalankan di client side. Ini berbeda dengan code di frontmatter (bagian yang diapit ---) yang dijalankan di build time. Astro secara otomatis mengoptimasi script kita, menghilangkan duplikasi kalau component dipakai berkali-kali.

Kalau kalian perhatiin, kita gak perlu tambahkan client:load atau directive lain di component ini karena script sudah otomatis client-side. Directive seperti client:load biasanya cuma diperlukan kalau kita pakai framework component seperti React atau Vue di dalam Astro.

Menggunakan Header Component

Sekarang kita perlu update BaseLayout untuk include Header component ini. Buka file src/layouts/BaseLayout.astro dan modifikasi seperti ini:

---
import '../styles/global.css';
import Header from '../components/Header.astro';

interface Props {
  title?: string;
  description?: string;
}

const {
  title = 'Baksoku Restaurant - Best Restaurant in Jakarta',
  description = 'Restaurant terbaik di Jakarta dengan menu nusantara yang lezat dan suasana nyaman untuk keluarga'
} = Astro.props;
---

<!DOCTYPE html>
<html lang="id">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content={description} />
    <meta name="generator" content={Astro.generator} />

    <meta property="og:type" content="website" />
    <meta property="og:title" content={title} />
    <meta property="og:description" content={description} />

    <meta property="twitter:card" content="summary_large_image" />
    <meta property="twitter:title" content={title} />
    <meta property="twitter:description" content={description} />

    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <title>{title}</title>
  </head>
  <body>
    <Header />
    <slot />
  </body>
</html>

Dengan menambahkan <Header /> di layout, sekarang semua halaman yang pakai BaseLayout akan otomatis punya navigation bar. Ini adalah power of component composition di Astro.

Testing Navigation

BaksokuRestaurant-ReservasiOnlineRestaurantTerbaik-BWA-MicrosoftEdge2025-10-0313-34-29-ezgif.com-video-to-gif-converter.gif
Header Navigation

Sekarang refresh browser kalian dan kalian harusnya melihat navigation bar yang lengkap di bagian atas. Coba klik dropdown "Kategori Menu" dan pastikan menu muncul dengan animasi yang smooth. Arrow icon juga harus rotate 180 derajat saat dropdown dibuka.

Coba juga klik di luar dropdown, menu harus tertutup otomatis. Hover ke menu items lainnya seperti Beranda, Reservasi, Tentang Kami dan pastikan underline animation muncul dengan benar. Kalau semua interaksi berjalan smooth, berarti Header component kita udah berhasil dikonversi dengan sempurna.

Tips Handle Navigation Links

Perhatikan bahwa kita menggunakan href seperti /reservasi dan /tentang-kami untuk navigation links. Di Astro, kalian perlu buat halaman-halaman ini nanti di folder pages. Untuk sekarang link ini akan error 404, tapi itu normal karena kita fokus konversi component dulu.

Kalau mau sementara semua link ke homepage, kalian bisa ganti href-nya jadi / atau # untuk placeholder. Tapi saya rekomendasikan tetap pakai proper URLs dari awal supaya gak lupa update nanti. Plus, ini bagus untuk planning struktur website kedepannya.

Untuk anchor links yang pakai hash seperti #makanan-utama, ini akan work kalau di halaman yang sama ada element dengan id tersebut. Ini adalah standard HTML behavior yang tetap berfungsi di Astro tanpa perlu konfigurasi tambahan.

Header component kita sekarang udah complete dan functional. Di section berikutnya, kita akan konversi Hero Section yang lebih kompleks dengan Swiper slider. Tapi foundasi yang kita bangun di Header ini akan memudahkan proses konversi component-component selanjutnya.

Konversi Hero Section dengan Swiper

Hero section dengan slider adalah salah satu komponen yang paling kompleks dalam template Baksoku karena melibatkan library eksternal dan interaktivitas yang cukup advance. Tapi tenang aja, kita akan bahas step-by-step supaya mudah dipahami. Mari kita mulai dengan instalasi Swiper terlebih dahulu.

Instalasi Swiper di Project Astro

Berbeda dengan template HTML original yang load Swiper dari CDN, di Astro kita akan install Swiper sebagai npm package. Ini memberikan kontrol yang lebih baik dan memastikan versi Swiper yang kita pakai konsisten. Buka terminal baru (biarkan dev server tetap jalan) dan jalankan perintah:

npm install swiper

Perintah ini akan menginstal Swiper versi terbaru beserta semua dependensi yang dibutuhkan. Proses instalasi biasanya cepat, cuma butuh beberapa detik. Setelah instalasi selesai, kalian akan melihat Swiper muncul di file package.json di bagian dependencies.

Membuat File HeroSlider.astro

Sekarang kita buat component baru untuk hero slider. Buat file HeroSlider.astro di folder src/components/. Component ini akan berisi markup slider dan logic untuk initialize Swiper. Ini adalah component pertama kita yang pakai library eksternal, jadi akan sedikit berbeda dari Header component sebelumnya:

---
// HeroSlider.astro
---

<section class="relative flex justify-center">
  <div class="swiper !pl-[140px] !pr-[60px]">
    <div class="swiper-wrapper flex py-[60px]">
      <!-- Slide 1 -->
      <div class="group swiper-slide relative !w-[1128px] !h-[564px]">
        <div class="w-full h-full rounded-3xl overflow-clip">
          <img
            src="/assets/images/thumbnails/hero-restaurant-1.jpg"
            alt="Baksoku Restaurant Jember"
            class="object-cover w-full"
          />
        </div>
        <div class="absolute transition-all duration-300 opacity-0 group-hover:opacity-100 flex flex-col gap-1 -left-20 top-[133px] bottom-[134px] h-[297px] w-[390px] rounded-3xl bg-white pl-6 py-6 pr-[140px]">
          <div class="flex items-center h-[27px] w-[210px]">
            <div class="flex gap-[6.5px] -mt-[3px] pr-[3px]">
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
            </div>
            <div class="flex gap-1 items-center">
              <p class="text-lg leading-[27px] font-semibold text-star">5.0</p>
              <p class="text-[16px] text-muted">(5.4K+)</p>
            </div>
          </div>
          <h2 class="text-[22px] font-semibold leading-[33px]">Baksoku Jember</h2>
          <p class="text-lg font-semibold leading-[27px] text-muted">IDR 29.999 - IDR 56.000</p>
          <div class="flex gap-1.5 items-center">
            <div class="h-6 w-6 flex items-center justify-center">
              <img src="/assets/images/icons/Location.svg" alt="" class="h-6 w-6" />
            </div>
            <p class="text-[16px] leading-6 text-muted">Jember, Indonesia</p>
          </div>
          <div class="mt-[30px] flex flex-col gap-3">
            <a href="/reservasi" class="flex items-center justify-center text-[16px] font-semibold leading-6 bg-primary rounded-[12px] w-[226px] h-14 hover:shadow-primary transition-all duration-300">
              <span>Make Reservation</span>
              <img src="/assets/images/icons/arrow_right.svg" alt="" />
            </a>
            <div class="flex gap-1.5 items-center">
              <img src="/assets/images/icons/info_square.svg" alt="" class="h-6 w-6" />
              <p class="text-[16px] leading-6 text-muted">No extra cost</p>
            </div>
          </div>
        </div>
      </div>

      <!-- Slide 2 -->
      <div class="group swiper-slide relative !w-[1128px] !h-[564px]">
        <div class="w-full h-full rounded-3xl overflow-clip">
          <img
            src="/assets/images/thumbnails/hero-dining-2.jpg"
            alt="Baksoku Restaurant Jakarta"
            class="object-cover w-full"
          />
        </div>
        <div class="absolute transition-all duration-300 opacity-0 group-hover:opacity-100 flex flex-col gap-1 -left-20 top-[133px] bottom-[134px] h-[297px] w-[390px] rounded-3xl bg-white pl-6 py-6 pr-[140px]">
          <div class="flex items-center h-[27px] w-[210px]">
            <div class="flex gap-[6.5px] -mt-[3px] pr-[3px]">
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
              <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] shrink-0" />
            </div>
            <div class="flex gap-1 items-center">
              <p class="text-lg leading-[27px] font-semibold text-star">5.0</p>
              <p class="text-[16px] text-muted">(5.2K+)</p>
            </div>
          </div>
          <h2 class="text-[22px] font-semibold leading-[33px]">Baksoku Jakarta</h2>
          <p class="text-lg font-semibold leading-[27px] text-muted">IDR 39.999 - IDR 460.000</p>
          <div class="flex gap-1.5 items-center">
            <div class="h-6 w-6 flex items-center justify-center">
              <img src="/assets/images/icons/Location.svg" alt="" class="h-6 w-6" />
            </div>
            <p class="text-[16px] leading-6 text-muted">Jakarta, Indonesia</p>
          </div>
          <div class="mt-[30px] flex flex-col gap-3">
            <a href="/reservasi" class="flex items-center justify-center text-[16px] font-semibold leading-6 bg-primary rounded-[12px] w-[226px] h-14 hover:shadow-primary transition-all duration-300">
              <span>Make Reservation</span>
              <img src="/assets/images/icons/arrow_right.svg" alt="" />
            </a>
            <div class="flex gap-1.5 items-center">
              <img src="/assets/images/icons/info_square.svg" alt="" class="h-6 w-6" />
              <p class="text-[16px] leading-6 text-muted">No extra cost</p>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</section>

<script>
  import Swiper from 'swiper';
  import 'swiper/css';

  document.addEventListener('DOMContentLoaded', () => {
    new Swiper('.swiper', {
      slidesPerView: 'auto',
      spaceBetween: 40,
      grabCursor: true,
      centeredSlides: false,
    });
  });
</script>

<style>
  .swiper-slide {
    width: auto;
  }
</style>

Memahami Client-Side Script di Astro

Perhatikan bagaimana kita import Swiper di dalam tag <script>. Ini berbeda dengan import biasa di frontmatter. Import di dalam script tag adalah client-side import yang akan di-bundle oleh Vite dan dijalankan di browser. Kita juga import CSS Swiper dengan import 'swiper/css' supaya styling slider berfungsi dengan benar.

Script ini akan dijalankan setelah DOM fully loaded berkat event listener DOMContentLoaded. Kita initialize Swiper dengan konfigurasi yang sama seperti template original: slidesPerView auto, spaceBetween 40px, grabCursor true untuk cursor grab saat drag, dan centeredSlides false supaya slide pertama align ke kiri.

Yang menarik adalah kita gak perlu pakai client:load directive di component ini. Kenapa? Karena script tag di Astro secara default udah client-side. Directive seperti client:load cuma diperlukan kalau kita pakai framework component seperti React atau Vue yang perlu hydration.

Implementasi Hover Effects dengan Tailwind

Hover effect di slider ini cukup elegan. Saat user hover ke slide, card info akan muncul dengan animasi opacity dari kiri. Semua ini dikontrol dengan Tailwind utility classes tanpa perlu JavaScript tambahan. Class group di parent element memungkinkan kita kontrol child elements saat parent di-hover.

Class group-hover:opacity-100 di card info akan trigger opacity change saat parent (yang punya class group) di-hover. Class transition-all duration-300 membuat perubahan opacity jadi smooth dengan durasi 300ms. Ini adalah salah satu kekuatan Tailwind dimana kita bisa buat interaktivitas kompleks hanya dengan utility classes.

Posisi card info menggunakan absolute positioning dengan nilai negatif -left-20 supaya card muncul dari sebelah kiri slide. Kombinasi top, bottom, height, dan width mengatur ukuran dan posisi card dengan presisi pixel-perfect sesuai desain original.

Menggunakan HeroSlider di Halaman

Sekarang kita tambahkan HeroSlider ke halaman index. Buka file src/pages/index.astro dan update seperti ini:

---
import BaseLayout from '../layouts/BaseLayout.astro';
import HeroSlider from '../components/HeroSlider.astro';
---

<BaseLayout
  title="Baksoku Restaurant - Reservasi Online Restaurant Terbaik"
  description="Reservasi online di Baksoku Restaurant. Nikmati masakan nusantara dengan harga terjangkau dan pelayanan terbaik."
>
  <HeroSlider />

  <div class="flex justify-center py-20">
    <div class="text-center">
      <h2 class="text-3xl font-bold text-foreground mb-4">
        Section lainnya akan ditambahkan di sini
      </h2>
      <p class="text-muted">
        Kita akan menambahkan komponen-komponen lain secara bertahap
      </p>
    </div>
  </div>
</BaseLayout>

Simpan file dan check browser kalian. Kalian harusnya melihat slider yang bisa di-drag dengan cursor grab. Coba drag slider ke kanan atau kiri, transisinya harus smooth dan responsive. Hover ke salah satu slide dan card info akan muncul dari kiri dengan animasi yang elegan.

Hero Swiper
Hero Swiper

Troubleshooting Common Issues

Kadang kalian mungkin mengalami beberapa issue saat setup Swiper. Berikut adalah masalah-masalah umum dan solusinya yang sering saya temui saat develop dengan Astro dan Swiper.

Issue 1: Swiper tidak initialize atau error "Swiper is not defined"

Ini biasanya terjadi kalau import Swiper salah atau script dijalankan sebelum DOM ready. Pastikan kalian import Swiper di dalam tag script, bukan di frontmatter. Juga pastikan code initialize Swiper ada di dalam event listener DOMContentLoaded supaya DOM udah fully loaded.

Issue 2: Styling Swiper tidak muncul

Kalau slider jalan tapi styling-nya berantakan, kemungkinan CSS Swiper belum di-import. Pastikan ada baris import 'swiper/css' di dalam script tag. Kalau masih gak work, coba restart dev server karena kadang Vite perlu restart untuk recognize CSS import baru.

Issue 3: Slide width tidak sesuai

Kalau lebar slide gak sesuai dengan yang diharapkan, check apakah kalian udah tambahkan style tag dengan .swiper-slide { width: auto; }. Class !w-[1128px] di markup menggunakan important flag supaya override default Swiper styling, tapi kadang perlu tambahan CSS di style tag.

Issue 4: Hover effect tidak working

Kalau card info gak muncul saat hover, check apakah class group ada di parent dan group-hover:opacity-100 ada di element yang mau di-show. Juga pastikan element tersebut punya opacity-0 sebagai initial state. Kalau masih gak work, coba inspect element dan check apakah Tailwind classes ter-apply dengan benar.

Issue 5: Images tidak muncul

Kalau gambar gak muncul, pastikan path-nya benar. Di Astro, file di folder public diakses dengan path yang dimulai dari root, jadi pakai /assets/images/... bukan ./assets/images/.... Juga pastikan file gambar udah di-copy ke folder public dengan struktur folder yang benar.

Tips Optimasi Swiper

Untuk performa yang lebih baik, kalian bisa tambahkan beberapa optimasi. Pertama, consider lazy loading untuk gambar dengan attribute loading="lazy" di tag img. Kedua, kalau punya banyak slide, pakai option lazy: true di konfigurasi Swiper supaya gambar cuma di-load saat slide visible.

Ketiga, kalau butuh navigation arrows atau pagination, Swiper punya module terpisah yang bisa di-import sesuai kebutuhan. Tapi untuk sekarang, konfigurasi simple yang kita pakai udah cukup dan performant. Yang penting adalah slider jalan dengan smooth tanpa lag saat di-drag atau di-swipe.

Hero slider kita sekarang udah complete dan fully functional. Component ini adalah contoh bagus bagaimana mengintegrasikan library eksternal di Astro dengan cara yang proper. Di section berikutnya, kita akan buat component yang lebih simple yaitu feature cards yang reusable dengan props.

Membuat Reusable Components

Sekarang kita masuk ke salah satu kekuatan terbesar Astro yaitu membuat komponen yang reusable. Dengan komponen reusable, kita bisa menghindari duplikasi kode dan membuat maintenance jadi jauh lebih mudah. Mari kita buat tiga komponen penting: FeatureCard, RestaurantCard, dan ChefSection.

Feature Card Component

Feature card adalah komponen yang menampilkan keunggulan restaurant di section "Mengapa pilih restaurant kami?". Di template original ada empat card yang strukturnya identik, cuma beda di icon, judul, dan deskripsi. Ini kandidat sempurna untuk dijadikan reusable component.

Buat file baru bernama FeatureCard.astro di dalam folder src/components/:

---
// src/components/FeatureCard.astro
interface Props {
  icon: string;
  title: string;
  description: string;
  link?: string;
}

const { icon, title, description, link = '#' } = Astro.props;
---

<div class="flex flex-col gap-[54px] h-fit pt-6 min-h-[347px] w-[315px] bg-white rounded-3xl">
  <div class="flex flex-col gap-6">
    <div class="ml-[117px] mr-[118px] flex items-center justify-center h-20 w-20 rounded-full bg-primary/15">
      <img src={icon} alt={title} class="h-[58px] w-[58px]" />
    </div>
    <h3 class="text-lg leading-[27px] font-semibold text-center">
      {title}
    </h3>
    <p class="w-[267px] -mt-3 mx-6 text-[16px] leading-6 text-muted text-center">
      {description}
    </p>
  </div>
  <a
    href={link}
    class="w-full mt-auto h-[72px] flex items-center justify-center text-[16px] leading-6 font-semibold rounded-t-xl rounded-b-3xl transition-all duration-300 hover:bg-primary"
  >
    <span>View Details</span>
    <img src="/assets/images/icons/arrow_right.svg" alt="" class="h-6 w-6" />
  </a>
</div>

Component ini menerima props untuk icon, title, description, dan optional link. Dengan mendefinisikan interface Props, kita dapat type safety dari TypeScript yang memastikan data yang di-pass sesuai dengan yang diharapkan. Props link punya default value '#' kalau gak di-specify.

Perhatikan bagaimana kita pakai curly braces untuk interpolasi nilai props seperti {title} dan {description}. Untuk attribute HTML seperti src dan href, kita pakai syntax src={icon} tanpa quotes. Ini adalah syntax JSX-like yang dipakai Astro untuk dynamic values.

Menggunakan FeatureCard Component

Sekarang mari kita pakai component ini di halaman index. Update file src/pages/index.astro:

---
import BaseLayout from '../layouts/BaseLayout.astro';
import HeroSlider from '../components/HeroSlider.astro';
import FeatureCard from '../components/FeatureCard.astro';

const features = [
  {
    icon: '/assets/images/icons/promo-special.svg',
    title: 'Promo Spesial Member',
    description: 'Dapatkan diskon hingga 20% untuk member setia kami',
    link: '/promo'
  },
  {
    icon: '/assets/images/icons/reservasi-mudah.svg',
    title: 'Reservasi Mudah',
    description: 'Booking online dalam 2 menit, meja Anda sudah siap saat tiba',
    link: '/reservasi'
  },
  {
    icon: '/assets/images/icons/harga-transparan.svg',
    title: 'Harga Transparan',
    description: 'Tidak ada biaya tersembunyi, harga di menu adalah harga final',
    link: '/menu'
  },
  {
    icon: '/assets/images/icons/kebersihan.svg',
    title: 'Standar Kebersihan Tinggi',
    description: 'Sertifikasi HACCP dan inspeksi rutin untuk keamanan pangan',
    link: '/tentang-kami'
  }
];
---

<BaseLayout>
  <HeroSlider />

  <!-- Why Us Section -->
  <section class="flex justify-center">
    <div class="w-[1320px] m-[60px]">
      <div class="flex w-fit mx-auto flex-col gap-1.5 text-center mb-[41px]">
        <span class="text-[16px] leading-6 font-semibold text-secondary uppercase">
          Keunggulan Kami
        </span>
        <h2 class="text-[28px] leading-[42px] font-semibold">
          Mengapa pilih restaurant kami?
        </h2>
      </div>
      <div class="flex w-full gap-5">
        {features.map((feature) => (
          <FeatureCard
            icon={feature.icon}
            title={feature.title}
            description={feature.description}
            link={feature.link}
          />
        ))}
      </div>
    </div>
  </section>
</BaseLayout>

FeaturedCard
FeaturedCard

Dengan approach seperti ini, kita cukup define data features sebagai array, lalu loop dengan map() untuk render multiple cards. Kalau nanti ada perubahan desain card, kita cukup edit satu file FeatureCard.astro dan semua card akan terupdate otomatis. Ini jauh lebih maintainable dibanding copy-paste markup yang sama empat kali.

Restaurant Card Component

Restaurant card ada dua jenis di template Baksoku. Pertama adalah card untuk section "Restaurant di Sekitar Anda" yang sederhana, dan kedua adalah card untuk "Restaurant Paling Populer" yang punya efek hover expand. Mari kita buat keduanya.

RestaurantCard untuk Near You Section

Buat file baru bernama RestaurantCard.astro di dalam folder src/components/:

---
// src/components/RestaurantCard.astro
interface Props {
  image: string;
  rating: number;
  reviewCount: string;
  name: string;
  priceRange: string;
  link?: string;
}

const { image, rating, reviewCount, name, priceRange, link = '#' } = Astro.props;
---

<div class="relative h-[415px] w-[427px] rounded-3xl overflow-clip shrink-0">
  <img src={image} alt={name} class="object-cover w-full h-full" />
  <div class="flex gap-[35px] p-6 items-center absolute left-6 bottom-[33px] right-6 bg-white rounded-3xl">
    <div class="flex flex-col gap-1 w-[260px]">
      <div class="flex items-center">
        <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] -mt-[3.5px] mr-[3px]" />
        <p class="text-[16px] leading-6 font-semibold text-star mr-1">{rating}</p>
        <p class="text-[16px] leading-6 text-muted">({reviewCount})</p>
      </div>
      <h3 class="text-lg leading-[27px] font-semibold">{name}</h3>
      <p class="text-lg font-semibold leading-6 text-muted">{priceRange}</p>
    </div>
    <a
      href={link}
      class="flex items-center justify-center h-9 w-9 rounded-xl bg-primary shrink-0 transition-all duration-300 hover:shadow-primary"
    >
      <img src="/assets/images/icons/sign_right.svg" alt="" />
    </a>
  </div>
</div>

Component ini lebih straightforward karena info card selalu visible tanpa perlu hover. Card ini menampilkan gambar restaurant, rating dengan bintang, nama restaurant, range harga, dan tombol arrow untuk detail. Semua info ditampilkan dalam card putih yang positioned absolute di bagian bawah gambar.

PopularRestaurantCard dengan Hover Effect

Sekarang buat file PopularRestaurantCard.astro untuk card yang bisa expand:

---
// RestaurantCard.astro
interface Props {
  image: string;
  rating: number;
  reviewCount: string;
  name: string;
  location: string;
  priceRange?: string;
  link?: string;
  className?: string;
}

const {
  image,
  rating,
  reviewCount,
  name,
  location,
  priceRange,
  link = '#',
  className = ''
} = Astro.props;
---

<div class={`relative h-[415px] w-[427px] rounded-3xl overflow-clip shrink-0 ${className}`}>
  <img src={image} alt={name} class="object-cover w-full h-full" />
  <div class="flex gap-[35px] p-6 items-center absolute left-6 bottom-[33px] right-6 bg-white rounded-3xl">
    <div class="flex flex-col gap-1 w-[260px]">
      <div class="flex items-center">
        <img src="/assets/images/icons/star.svg" alt="" class="w-[18px] -mt-[3.5px] mr-[3px]" />
        <p class="text-[16px] leading-6 font-semibold text-star mr-1">{rating}</p>
        <p class="text-[16px] leading-6 text-muted">({reviewCount})</p>
      </div>
      <h3 class="text-lg leading-[27px] font-semibold">{name}</h3>
      {priceRange && (
        <p class="text-lg font-semibold leading-6 text-muted">{priceRange}</p>
      )}
    </div>
    <a
      href={link}
      class="flex items-center justify-center h-9 w-9 rounded-xl bg-primary shrink-0 transition-all duration-300 hover:shadow-primary"
    >
      <img src="/assets/images/icons/sign_right.svg" alt="" />
    </a>
  </div>
</div>

Component ini punya lebih banyak props untuk accommodate semua data yang perlu ditampilkan. Props priceRange optional karena gak semua card menampilkan harga. Kita juga kasih props className supaya bisa customize styling dari luar kalau diperlukan.

Perhatikan conditional rendering {priceRange && (...)} yang hanya render elemen kalau priceRange ada nilainya. Ini adalah pattern umum di JSX untuk conditional rendering. Kalau priceRange undefined atau empty string, element tersebut gak akan di-render.

Menggunakan RestaurantCard di Halaman

Sekarang update file src/pages/index.astro untuk menambahkan section Near You Restaurant. Tambahkan import dan data di bagian frontmatter:

---
import BaseLayout from '../layouts/BaseLayout.astro';
import HeroSlider from '../components/HeroSlider.astro';
import FeatureCard from '../components/FeatureCard.astro';
import RestaurantCard from '../components/RestaurantCard.astro';
import PopularRestaurantCard from '../components/PopularRestaurantCard.astro';

const features = [
  {
    icon: '/assets/images/icons/promo-special.svg',
    title: 'Promo Spesial Member',
    description: 'Dapatkan diskon hingga 20% untuk member setia kami',
    link: '/promo'
  },
  {
    icon: '/assets/images/icons/reservasi-mudah.svg',
    title: 'Reservasi Mudah',
    description: 'Booking online dalam 2 menit, meja Anda sudah siap saat tiba',
    link: '/reservasi'
  },
  {
    icon: '/assets/images/icons/harga-transparan.svg',
    title: 'Harga Transparan',
    description: 'Tidak ada biaya tersembunyi, harga di menu adalah harga final',
    link: '/menu'
  },
  {
    icon: '/assets/images/icons/kebersihan.svg',
    title: 'Standar Kebersihan Tinggi',
    description: 'Sertifikasi HACCP dan inspeksi rutin untuk keamanan pangan',
    link: '/tentang-kami'
  }
];

const nearbyRestaurants = [
  {
    image: '/assets/images/thumbnails/near-resto-1.jpg',
    rating: 4.8,
    reviewCount: '3.2K+',
    name: 'Dapur Nusantara',
    priceRange: 'IDR 35.000 - IDR 150.000',
    link: '/restaurant/dapur-nusantara'
  },
  {
    image: '/assets/images/thumbnails/near-resto-2.jpg',
    rating: 5.0,
    reviewCount: '3.6K+',
    name: 'Split Ascent Restaurant',
    priceRange: 'IDR 49.999 - IDR 560.000',
    link: '/restaurant/split-ascent'
  },
  {
    image: '/assets/images/thumbnails/near-resto-3.jpg',
    rating: 5.0,
    reviewCount: '11K+',
    name: 'Daza Fracture Restaurant',
    priceRange: 'IDR 29.999 - IDR 560.000',
    link: '/restaurant/daza-fracture'
  }
];

const popularRestaurants = [
  {
    image: '/assets/images/thumbnails/popular-resto-1.jpg',
    rating: 4.9,
    reviewCount: '2.3K+',
    name: 'Warung Sunda Ibu Haji',
    location: 'Bandung, Indonesia',
    link: '/restaurant/warung-sunda'
  },
  {
    image: '/assets/images/thumbnails/popular-resto-2.jpg',
    rating: 4.8,
    reviewCount: '1.8K+',
    name: 'Rumah Makan Padang Sederhana',
    location: 'Jakarta Selatan, Indonesia',
    link: '/restaurant/padang-sederhana'
  },
  {
    image: '/assets/images/thumbnails/popular-resto-3.jpg',
    rating: 4.7,
    reviewCount: '3.1K+',
    name: 'Kedai Kopi Filosofi',
    location: 'Yogyakarta, Indonesia',
    link: '/restaurant/kopi-filosofi'
  }
];
---

<BaseLayout>
  <HeroSlider />

  <!-- Why Us Section -->
  <section class="flex justify-center">
    <div class="w-[1320px] m-[60px]">
      <div class="flex w-fit mx-auto flex-col gap-1.5 text-center mb-[41px]">
        <span class="text-[16px] leading-6 font-semibold text-secondary uppercase">
          Keunggulan Kami
        </span>
        <h2 class="text-[28px] leading-[42px] font-semibold">
          Mengapa pilih restaurant kami?
        </h2>
      </div>
      <div class="flex w-full gap-5">
        {features.map((feature) => (
          <FeatureCard
            icon={feature.icon}
            title={feature.title}
            description={feature.description}
            link={feature.link}
          />
        ))}
      </div>
    </div>
  </section>

  <!-- Popular Restaurant Section -->
  <section class="flex justify-center">
    <div class="flex items-center w-[1320px] m-[60px]">
      <div class="w-[367px]">
        <div class="flex flex-col gap-1.5">
          <span class="text-[16px] leading-6 font-semibold text-secondary uppercase">
            Pilihan Terbaik
          </span>
          <h2 class="w-full text-[28px] leading-[42px] font-semibold">
            Restaurant Paling Populer
          </h2>
        </div>
        <p class="mt-5 w-[360px] pr-5 text-[16px] leading-6 text-muted">
          Restaurant pilihan terbaik berdasarkan rating pelanggan, kualitas
          makanan, suasana nyaman, dan pelayanan yang memuaskan.
        </p>
        <a
          href="/restaurant"
          class="mt-14 flex items-center justify-center text-[16px] leading-6 font-semibold h-[56px] w-[238px] bg-primary rounded-xl transition-all duration-300 hover:shadow-primary"
        >
          <span>Lihat Semua</span>
          <img src="/assets/images/icons/arrow_right.svg" alt="" class="h-6 w-6" />
        </a>
      </div>
      <div class="ml-[85px] flex gap-5">
        {popularRestaurants.map((resto, index) => (
          <PopularRestaurantCard
            image={resto.image}
            rating={resto.rating}
            reviewCount={resto.reviewCount}
            name={resto.name}
            location={resto.location}
            link={resto.link}
            isActive={index === 0}
          />
        ))}
      </div>
    </div>
  </section>

  <!-- Near You Restaurant Section -->
  <section class="flex justify-center">
    <div class="flex flex-col gap-[34px] w-[1321px] m-[60px]">
      <div class="flex w-full items-center justify-between">
        <div class="flex flex-col gap-1.5">
          <span class="text-[16px] leading-6 font-semibold text-secondary uppercase">
            Berdasarkan Lokasi
          </span>
          <h2 class="w-full text-nowrap text-[28px] leading-[42px] font-semibold">
            Restaurant di Sekitar Anda
          </h2>
        </div>
      </div>
      <div class="flex gap-5">
        {nearbyRestaurants.map((resto) => (
          <RestaurantCard
            image={resto.image}
            rating={resto.rating}
            reviewCount={resto.reviewCount}
            name={resto.name}
            priceRange={resto.priceRange}
            link={resto.link}
          />
        ))}
      </div>
      <a
        href="/restaurant"
        class="mt-[22px] mx-auto flex items-center justify-center h-14 w-[238px] bg-primary rounded-xl text-[16px] leading-6 font-semibold transition-all duration-300 hover:shadow-primary"
      >
        <span>Lihat Semua</span>
        <img src="/assets/images/icons/arrow_right.svg" alt="" class="h-6 w-6" />
      </a>
    </div>
  </section>
</BaseLayout>

Restaurant Populer dan Restaurant di Sekitar
Restaurant Populer dan Restaurant di Sekitar

Dengan update ini, halaman index sekarang punya tiga section lengkap: Why Us dengan FeatureCard, Popular Restaurant dengan PopularRestaurantCard yang bisa expand, dan Near You dengan RestaurantCard yang simple. Semua data diorganisir dengan rapi dalam array di frontmatter, making it easy untuk maintain dan update kedepannya.

Implementasi Hover Animations

Untuk restaurant cards yang ada di section popular, kita perlu versi yang bisa expand saat di-hover. Buat file PopularRestaurantCard.astro:

---
// PopularRestaurantCard.astro
interface Props {
  image: string;
  rating: number;
  reviewCount: string;
  name: string;
  location: string;
  link?: string;
  isActive?: boolean;
}

const { image, rating, reviewCount, name, location, link = '#', isActive = false } = Astro.props;
const cardId = `card-${Math.random().toString(36).substr(2, 9)}`;
---

<div
  class={`card group transition-all duration-300 relative h-[475px] rounded-3xl overflow-clip shrink-0 ${isActive ? 'w-[400px]' : 'w-[214px]'}`}
  data-card-id={cardId}
>
  <img src={image} alt={name} class="object-cover w-full h-full" />
  <div
    class={`card-info transition-all duration-300 delay-100 ease-in-out gap-2 p-6 items-center absolute left-6 bottom-6 right-6 bg-white rounded-3xl ${isActive ? 'flex' : 'hidden'}`}
  >
    <div class="flex flex-col gap-1 w-[260px]">
      <div class="flex items-center">
        <img src="/assets/images/icons/star.svg" alt="" class="h-auto w-[18px] -mt-[3px] mr-[3px]" />
        <p class="text-[16px] leading-6 font-semibold text-star mr-1">{rating}</p>
        <p class="text-[16px] leading-6 text-muted">({reviewCount})</p>
      </div>
      <h3 class="text-lg leading-[27px] font-semibold">{name}</h3>
      <div class="flex gap-1.5">
        <img src="/assets/images/icons/Location.svg" alt="" class="h-6 w-6" />
        <p class="text-[16px] leading-6 text-muted">{location}</p>
      </div>
    </div>
    <a
      href={link}
      class="flex items-center justify-center h-9 w-9 rounded-xl bg-primary shrink-0 transition-all duration-300 hover:shadow-primary"
    >
      <img src="/assets/images/icons/sign_right.svg" alt="" />
    </a>
  </div>
</div>

<script define:vars={{ cardId }}>
  document.addEventListener('DOMContentLoaded', () => {
    const card = document.querySelector(`[data-card-id="${cardId}"]`);
    const allCards = document.querySelectorAll('.card');

    if (card) {
      card.addEventListener('mouseenter', () => {
        allCards.forEach(c => {
          c.classList.remove('w-[400px]');
          c.classList.add('w-[214px]');
          const info = c.querySelector('.card-info');
          if (info) {
            info.classList.remove('flex');
            info.classList.add('hidden');
          }
        });

        card.classList.remove('w-[214px]');
        card.classList.add('w-[400px]');
        const info = card.querySelector('.card-info');
        if (info) {
          info.classList.remove('hidden');
          info.classList.add('flex');
        }
      });
    }
  });
</script>

Component ini menggunakan define:vars di script tag untuk pass variable dari Astro ke JavaScript. Kita generate unique cardId untuk setiap instance supaya event listener bisa target card yang spesifik. Saat mouse enter, semua card di-collapse dan hanya card yang di-hover yang expand.

Chef Section Component

Chef section adalah component paling kompleks karena punya tab switching functionality. Buat file baru bernama ChefSection.astro di dalam folder src/components/:

---
// src/components/ChefSection.astro
const chefs = [
  {
    name: 'Budi Santoso',
    category: 'Indonesian Cuisine',
    location: 'Jakarta, Indonesia',
    experience: '15 Tahun',
    restaurant: 'Nusantara Kitchen',
    image: '/assets/images/thumbnails/chef-1.jpg',
    restaurantImage: '/assets/images/thumbnails/resto-4.png'
  },
  {
    name: 'Sarah Wijaya',
    category: 'Pastry & Dessert',
    location: 'Bandung, Indonesia',
    experience: '10 Tahun',
    restaurant: 'Sweet Dreams Bakery',
    image: '/assets/images/thumbnails/chef-2.jpg',
    restaurantImage: '/assets/images/thumbnails/resto-chef-2.jpg'
  },
  {
    name: 'Ahmad Hidayat',
    category: 'Seafood Specialist',
    location: 'Surabaya, Indonesia',
    experience: '12 Tahun',
    restaurant: 'Ocean Pearl Restaurant',
    image: '/assets/images/thumbnails/chef-3.jpg',
    restaurantImage: '/assets/images/thumbnails/resto-chef-3.jpg'
  },
  {
    name: 'Made Wirawan',
    category: 'Grill & BBQ',
    location: 'Bali, Indonesia',
    experience: '18 Tahun',
    restaurant: 'Bali Grill House',
    image: '/assets/images/thumbnails/chef-4.jpg',
    restaurantImage: '/assets/images/thumbnails/resto-chef-4.jpg'
  }
];
---

<section class="mt-[60px] mb-[123px] flex justify-center">
  <div class="flex items-center gap-[64px] w-[1321px]">
    <div class="tab-content relative h-[848px] w-[660px]">
      <div class="absolute left-[60px] right-10 h-full w-[560px] rounded-3xl overflow-clip">
        <img
          id="chef-image"
          src={chefs[0].image}
          alt={chefs[0].name}
          class="object-cover w-full h-full"
          loading="lazy"
        />
      </div>
      <div class="absolute top-10 right-0 px-[46px] py-[30px] flex flex-col items-center h-[203px] w-[179px] bg-white rounded-3xl drop-shadow-custom">
        <div class="mb-3 flex items-center justify-center h-20 w-20 bg-[#FFF7ED] rounded-full">
          <img src="/assets/images/icons/shield.svg" alt="" class="h-[50px] w-[50px] shrink-0" />
        </div>
        <div class="w-[87px] text-center">
          <p id="experience-years" class="text-lg leading-[27px] font-semibold">
            {chefs[0].experience}
          </p>
          <p class="text-[16px] leading-6 text-muted">Experience</p>
        </div>
      </div>
      <div class="p-6 absolute left-0 bottom-10 flex items-center gap-3 h-auto w-[330px] rounded-3xl bg-white drop-shadow-custom2">
        <div class="h-[132px] w-[96px] rounded-xl overflow-clip shrink-0">
          <img
            id="resto-image"
            src={chefs[0].restaurantImage}
            alt={chefs[0].restaurant}
            class="object-cover w-full h-full"
            loading="lazy"
          />
        </div>
        <div class="w-[174px] h-auto">
          <div class="flex flex-col gap-6">
            <div class="flex flex-col gap-1">
              <span class="text-[16px] leading-6 text-muted">Chef at restaurant:</span>
              <p id="resto-name" class="text-lg leading-[27px] font-semibold">
                {chefs[0].restaurant}
              </p>
            </div>
            <a href="#" class="flex text-[16px] leading-6 font-semibold text-primary">
              <span>View Details</span>
              <div class="flex items-center justify-center h-6 w-6 shrink-0">
                <img src="/assets/images/icons/arrow_right.svg" alt="" class="opacity-0" />
              </div>
            </a>
          </div>
        </div>
      </div>
    </div>

    <div class="flex flex-col gap-8 w-[596px]">
      <div class="flex flex-col gap-1">
        <span class="text-[18px] leading-6 font-semibold text-secondary uppercase">
          Chef Profesional Kami
        </span>
        <h2 class="w-full text-nowrap text-[32px] leading-[48px] font-semibold">
          Chef Berpengalaman
        </h2>
      </div>

      <div class="tab-menu flex flex-col gap-5">
        {chefs.map((chef, index) => (
          <div
            class={`tab-item cursor-pointer p-6 flex flex-col gap-1.5 h-[141px] w-[596px] rounded-3xl group transition-all duration-300 hover:bg-white ${index === 0 ? 'bg-white' : ''}`}
            data-index={index}
          >
            <span class="text-[16px] leading-6 font-medium text-muted uppercase">
              {chef.category}
            </span>
            <div class="w-full flex items-center justify-between">
              <h2 class="text-[22px] leading-[33px] font-semibold line-clamp-1 mr-0.5">
                {chef.name}
              </h2>
              <a
                href={`/chef/${chef.name.toLowerCase().replace(' ', '-')}`}
                class={`flex text-[16px] leading-6 font-medium text-primary shrink-0 ${index === 0 ? 'opacity-100' : 'opacity-0'}`}
              >
                <span>Lihat Profil</span>
                <div class="flex items-center justify-center h-6 w-6 shrink-0">
                  <img src="/assets/images/icons/arrow_right.svg" alt="" class="opacity-0" />
                </div>
              </a>
            </div>
            <div class="flex items-center gap-1.5">
              <img src="/assets/images/icons/Location.svg" alt="" class="h-6 w-6" />
              <p class="text-[16px] leading-6 text-muted">{chef.location}</p>
            </div>
          </div>
        ))}
      </div>

      <a
        href="/chef"
        class="mt-[30px] w-fit flex items-center justify-center text-[16px] leading-6 font-semibold px-7 py-4 bg-primary rounded-xl transition-all duration-300 hover:shadow-primary"
      >
        <span>Lihat Semua Chef</span>
        <img src="/assets/images/icons/arrow_right.svg" alt="" class="h-6 w-6 shrink-0" />
      </a>
    </div>
  </div>
</section>

<script>
  const chefData = [
    {
      name: 'Budi Santoso',
      experience: '15 Tahun',
      restaurant: 'Nusantara Kitchen',
      image: '/assets/images/thumbnails/chef-1.jpg',
      restaurantImage: '/assets/images/thumbnails/resto-4.png'
    },
    {
      name: 'Sarah Wijaya',
      experience: '10 Tahun',
      restaurant: 'Sweet Dreams Bakery',
      image: '/assets/images/thumbnails/chef-2.jpg',
      restaurantImage: '/assets/images/thumbnails/near-resto-1.jpg'
    },
    {
      name: 'Ahmad Hidayat',
      experience: '12 Tahun',
      restaurant: 'Ocean Pearl Restaurant',
      image: '/assets/images/thumbnails/chef-3.jpg',
      restaurantImage: '/assets/images/thumbnails/near-resto-2.jpg'
    },
    {
      name: 'Made Wirawan',
      experience: '18 Tahun',
      restaurant: 'Bali Grill House',
      image: '/assets/images/thumbnails/chef-4.jpg',
      restaurantImage: '/assets/images/thumbnails/near-resto-3.jpg'
    }
  ];

  document.addEventListener('DOMContentLoaded', () => {
    const tabItems = document.querySelectorAll('.tab-item');
    const chefImage = document.getElementById('chef-image') as HTMLImageElement;
    const experienceYears = document.getElementById('experience-years');
    const restoName = document.getElementById('resto-name');
    const restoImage = document.getElementById('resto-image') as HTMLImageElement;

    function updateContent(index: number) {
      const chef = chefData[index];
      if (chefImage) chefImage.src = chef.image;
      if (experienceYears) experienceYears.textContent = chef.experience;
      if (restoName) restoName.textContent = chef.restaurant;
      if (restoImage) restoImage.src = chef.restaurantImage;
    }

    function setActiveTab(index: number) {
      tabItems.forEach((tab, i) => {
        if (i === index) {
          tab.classList.add('bg-white');
          const link = tab.querySelector('a');
          if (link) link.classList.replace('opacity-0', 'opacity-100');
        } else {
          tab.classList.remove('bg-white');
          const link = tab.querySelector('a');
          if (link) link.classList.replace('opacity-100', 'opacity-0');
        }
      });
    }

    tabItems.forEach((tab, index) => {
      tab.addEventListener('click', () => {
        setActiveTab(index);
        updateContent(index);
      });
    });
  });
</script>

Component ChefSection ini menggabungkan data chef dengan tab switching functionality. Data chef di-define di frontmatter dan di-loop untuk render tab items. Di script tag, kita duplicate data (karena script tag gak bisa akses frontmatter variables secara langsung) untuk handle tab switching.

Saat user klik tab, function setActiveTab akan update styling tab yang aktif dan function updateContent akan update gambar chef, experience years, nama restaurant, dan gambar restaurant. Attribute loading="lazy" di image tags memastikan gambar cuma di-load saat diperlukan, improving initial page load performance.

Menambahkan ChefSection ke Halaman Index

Sekarang update file src/pages/index.astro untuk menambahkan ChefSection. Tambahkan import di bagian atas frontmatter:

---
import BaseLayout from '../layouts/BaseLayout.astro';
import HeroSlider from '../components/HeroSlider.astro';
import FeatureCard from '../components/FeatureCard.astro';
import RestaurantCard from '../components/RestaurantCard.astro';
import PopularRestaurantCard from '../components/PopularRestaurantCard.astro';
import ChefSection from '../components/ChefSection.astro';

// ... data features, nearbyRestaurants, popularRestaurants tetap sama
---

<BaseLayout>
  <HeroSlider />

  <!-- Why Us Section -->
  <section class="flex justify-center">
    <!-- ... section ini tetap sama -->
  </section>

  <!-- Popular Restaurant Section -->
  <section class="flex justify-center">
    <!-- ... section ini tetap sama -->
  </section>

  <!-- Near You Restaurant Section -->
  <section class="flex justify-center">
    <!-- ... section ini tetap sama -->
  </section>

  <!-- Chef Section -->
  <ChefSection />
</BaseLayout>

ChefSection
ChefSection

Dengan menambahkan <ChefSection /> di akhir, halaman index sekarang sudah lengkap dengan semua komponen utama dari template original. ChefSection ini adalah self-contained component yang sudah include semua data dan functionality-nya sendiri.

Kesimpulan Reusable Components

Ketiga component ini mendemonstrasikan berbagai pattern yang sering dipakai di Astro: props passing untuk FeatureCard dan RestaurantCard, conditional rendering dan unique ID generation untuk PopularRestaurantCard, serta complex client-side interactivity untuk ChefSection. Dengan component-component reusable ini, kita bisa build halaman yang kompleks dengan code yang clean dan maintainable.

Yang paling penting adalah setiap component punya tanggung jawabnya masing-masing. FeatureCard cuma tau cara render satu feature card dengan data yang di-pass. RestaurantCard fokus di tampilan restaurant info. ChefSection handle semua logic tab switching sendiri. Separation of concerns seperti ini membuat code lebih mudah dipahami, di-test, dan di-maintain kedepannya.

Build dan Deployment

Setelah semua komponen berhasil dikonversi dan website berjalan dengan baik di development, sekarang saatnya kita build untuk production dan deploy ke server live. Build process di Astro sangat straightforward dan hasil outputnya udah dioptimasi secara otomatis. Mari kita bahas langkah-langkahnya.

Build untuk Production

Untuk membuat production build, kita cukup jalankan satu perintah simple. Buka terminal di root project kalian dan jalankan:

npm run build

Build astro
Build astro

Perintah ini akan trigger Astro untuk compile semua file, optimize assets, dan generate static HTML yang siap di-deploy. Prosesnya biasanya cepat, tergantung ukuran project. Kalian akan melihat output di terminal yang menunjukkan progress build termasuk berapa halaman yang di-generate dan ukuran bundle JavaScript.

Saat build process berjalan, Astro akan melakukan beberapa optimasi otomatis. Pertama, semua CSS akan di-minify dan unused CSS akan dihapus berkat integrasi dengan Tailwind. Kedua, JavaScript akan di-bundle dan di-minify untuk ukuran file yang lebih kecil. Ketiga, images akan dioptimasi kalau kalian pakai Astro image optimization. Keempat, HTML akan di-minify untuk mengurangi ukuran file.

Hasil build akan tersimpan di folder dist/ di root project. Folder ini berisi semua file static yang siap di-upload ke hosting manapun. Struktur folder dist akan mirip dengan struktur pages kalian, dimana setiap route jadi folder dengan file index.html di dalamnya. Misalnya route /about akan jadi folder dist/about/index.html.

Optimasi Hasil Build

Meskipun Astro udah melakukan banyak optimasi otomatis, ada beberapa hal yang bisa kita lakukan untuk improve performance lebih lanjut. Pertama, pastikan semua images punya alt text yang descriptive untuk SEO. Kedua, consider menggunakan format image modern seperti WebP atau AVIF yang ukurannya lebih kecil tapi kualitas tetap bagus.

Ketiga, check bundle size JavaScript dengan membuka file dist/_astro/ dan lihat ukuran file-file JavaScript. Kalau ada file yang terlalu besar, mungkin ada library yang bisa diganti dengan alternatif yang lebih ringan. Keempat, pastikan semua font di-load dengan efisien, kalau bisa pakai font-display: swap untuk avoid flash of invisible text.

Kelima, enable compression di server kalian. Kebanyakan hosting modern udah support gzip atau brotli compression yang bisa reduce ukuran file sampai 70-80%. Keenam, setup proper caching headers supaya browser bisa cache static assets dan gak perlu download ulang setiap visit.

Preview Production Build

Sebelum deploy ke live server, ada baiknya kita preview production build di local dulu untuk memastikan semuanya berjalan dengan baik. Astro menyediakan command untuk ini:

npm run preview

Astro preview
Astro preview

Command ini akan start local server yang serve files dari folder dist, jadi kalian bisa test production build di environment yang mirip dengan live server. Biasanya server akan jalan di http://localhost:4321 sama seperti dev server.

Saat preview production build, check beberapa hal penting. Pertama, pastikan semua link navigasi berfungsi dengan benar dan gak ada broken links. Kedua, test semua interaktivitas seperti dropdown menu, slider, dan tab switching. Ketiga, check loading speed dengan DevTools Network tab untuk ensure assets di-load dengan efficient.

Keempat, test responsive design di berbagai ukuran layar dari mobile sampai desktop. Kelima, jalankan Lighthouse audit di Chrome DevTools untuk dapat score performance, accessibility, best practices, dan SEO. Kalau ada score yang rendah, Lighthouse akan kasih rekomendasi spesifik untuk improve.

Deploy ke Vercel via GitHub

Vercel adalah salah satu platform hosting paling populer untuk Astro dan punya integrasi yang seamless dengan GitHub. Prosesnya sangat mudah dan gak perlu konfigurasi ribet. Mari kita setup deployment step by step.

Push astro project ke Github
Push astro project ke Github

Langkah 1: Push ke GitHub

Pertama, pastikan project kalian udah di-push ke GitHub repository. Kalau belum punya repository, buat dulu di github.com dengan click tombol New repository. Setelah repository dibuat, initialize git di project local kalian kalau belum:

git init
git add .
git commit -m "Initial commit: Baksoku Restaurant Astro"
git branch -M main
git remote add origin <https://github.com/username/bwa-resto-astro.git>
git push -u origin main

Ganti username dengan username GitHub kalian dan bwa-resto-astro dengan nama repository yang kalian buat. Setelah push berhasil, semua code kalian akan tersimpan di GitHub.

Langkah 2: Connect Vercel dengan GitHub

Connect Vercel ke Github
Connect Vercel ke Github

Buka vercel.com dan sign in dengan akun GitHub kalian. Kalau belum punya akun Vercel, daftar dulu menggunakan GitHub authentication supaya proses integrasinya lebih smooth. Setelah login, kalian akan melihat dashboard Vercel.

Click tombol "Add New Project" atau "Import Project". Vercel akan menampilkan list semua repository GitHub kalian. Cari repository bwa-resto-astro yang tadi dibuat dan click Import. Vercel akan otomatis detect bahwa ini adalah Astro project dan setup build configuration yang tepat.

Langkah 3: Configure Build Settings

Configure Build
Configure Build

Di halaman configure, kalian akan melihat beberapa settings. Framework Preset harusnya udah otomatis detect sebagai Astro. Build Command akan di-set ke npm run build dan Output Directory ke dist. Settings ini udah correct jadi kalian gak perlu ubah apa-apa.

Kalau project kalian butuh environment variables, kalian bisa tambahkan di section Environment Variables. Tapi untuk project Baksoku Restaurant kita, gak ada environment variables yang diperlukan jadi bisa skip step ini.

Langkah 4: Deploy

Deploy ke Vercel
Deploy ke Vercel

Click tombol Deploy dan tunggu process deployment selesai. Vercel akan clone repository kalian, install dependencies dengan npm install, run build command, dan upload hasilnya ke CDN global mereka. Process ini biasanya cuma butuh 1-2 menit untuk project ukuran medium.

Kalian bisa monitor progress deployment di dashboard. Vercel akan menampilkan log real-time dari setiap step: cloning, installing, building, dan deploying. Kalau ada error, log ini sangat helpful untuk debugging.

Langkah 5: Access Website

https://bwa-resto-astro.vercel.app/

Setelah deployment berhasil, Vercel akan generate URL unik untuk website kalian dengan format project-name-username.vercel.app. Click URL tersebut untuk melihat website live kalian. Congratulations, website Baksoku Restaurant udah online dan bisa diakses dari mana aja!

Continuous Deployment

Salah satu kelebihan deploy dengan Vercel adalah continuous deployment. Setiap kali kalian push commit baru ke GitHub, Vercel akan otomatis trigger build dan deploy versi terbaru. Gak perlu manual upload atau run command deploy lagi.

Kalian juga bisa setup preview deployments untuk pull requests. Setiap PR akan dapat unique URL sendiri supaya kalian bisa review changes sebelum merge ke main branch. Ini sangat berguna kalau kerja dalam tim atau mau test fitur baru sebelum go live.

Penutup

Selamat! Kalian udah berhasil menyelesaikan perjalanan panjang mengkonversi website HTML restaurant menjadi project Astro yang modern dan optimized. Dari setup awal sampai deployment ke production, kalian udah belajar banyak konsep penting tentang Astro framework dan best practices dalam web development.

Proses konversi ini bukan cuma soal memindahkan code dari satu format ke format lain. Lebih dari itu, kita belajar bagaimana cara berpikir component-based, bagaimana organize code supaya scalable, dan bagaimana memanfaatkan fitur-fitur modern seperti partial hydration dan islands architecture yang membuat website kita jauh lebih performant.

Dengan mengkonversi template Baksoku Restaurant ke Astro, kalian udah dapat hands-on experience dengan berbagai pattern dan teknik yang applicable ke project lain kedepannya. Skills yang kalian pelajari di sini, mulai dari props passing, conditional rendering, client-side interactivity, sampai deployment workflow, semuanya adalah fundamental yang bisa diterapkan di berbagai framework modern.

Saran Belajar di BuildWithAngga

Kalau kalian tertarik untuk memperdalam kemampuan pengembangan web lebih lanjut, saya sangat rekomendasikan untuk menjelajahi kelas-kelas di BuildWithAngga. Platform ini punya banyak kelas berkualitas yang dibuat oleh praktisi industri dengan pendekatan yang praktikal dan berbasis proyek.

Yang menarik dari BuildWithAngga adalah fokus ke proyek dunia nyata. Kalian gak cuma belajar teori, tapi langsung praktik bikin proyek yang bisa jadi portofolio. Ada berbagai kelas dari frontend tingkat lanjut, pengembangan backend, sampai full-stack yang bisa melengkapi kemampuan Astro yang udah kalian pelajari.

Satu tips terakhir: jangan cuma mengonsumsi tutorial, tapi juga bangun proyek kalian sendiri. Ambil ide yang kalian sukai dan implementasikan dengan kemampuan yang udah dipelajari. Setiap kesalahan adalah kesempatan untuk belajar, dan setiap bug yang diperbaiki membuat kalian jadi pengembang yang lebih baik.

Semoga berhasil dengan perjalanan pengembangan web kalian, dan sampai jumpa di proyek berikutnya!