development

Astro & shadcn/ui: Panduan Membangun UI Component Berperforma Tinggi

Asep Alazhari

Bangun UI berperforma tinggi dengan Astro & shadcn/ui. Panduan ini membahas integrasi, arsitektur komponen, dan teknik optimasi utama.

Astro & shadcn/ui: Panduan Membangun UI Component Berperforma Tinggi

Pencarian Framework UI Sempurna: Kenapa Gue Pilih shadcn/ui untuk Astro

Sebagai developer yang udah bertahun-tahun ngutak-ngatik web development, gue udah ngeliat evolusi UI framework dari era jQuery plugins sampai component library modern sekarang. Pas udah main-main sama Astro versi terbaru, gue jadi bingung nih: mending tetep pake CSS framework tradisional, atau ikut arus component-driven approach yang lagi nge-hits di dunia React?

Titik baliknya pas gue handle project client yang butuh performa exceptional tapi UI-nya juga harus kece. Client pengen website marketing dengan elemen interaktif, tapi loading speed harus cepet banget buat SEO dan user experience. Solusi React yang heavy kerasa overkill, sementara CSS framework biasa kurang sophisticated untuk component yang dibutuhin user modern.

Nah, di situlah gue nemu kombinasi powerful antara Astro dan shadcn/ui. Pasangan ini kasih yang terbaik dari dua dunia: island architecture-nya Astro buat performa optimal dan component shadcn/ui yang cantik plus accessible buat UI yang rich.

Memahami Integrasi Astro dan shadcn/ui

shadcn/ui udah makin populer banget di ekosistem React, dan alasannya masuk akal sih. Beda sama component library tradisional yang lo install sebagai npm package, shadcn/ui punya pendekatan yang beda: lo copy-paste aja component yang lo butuhin langsung ke project. Ini kasih lo kontrol penuh terhadap code sambil tetep jaga konsistensi dan accessibility.

Dengan peningkatan React integration di Astro dan dukungan framework JavaScript modern, nge-combine teknologi ini makin smooth aja. Keuntungan utamanya terletak di kemampuan Astro untuk render component saat build time kalau nggak butuh interaktivity, sambil secara seamless hydrate component interaktif di client side. Pendekatan ini nggak cuma bagus buat performa, tapi juga buat Search Engine Optimization (SEO), karena search engine bisa crawl konten HTML statis dengan mudah.

Setup Development Environment Lo

Sebelum masuk ke penggunaan component, yuk kita bikin foundation yang proper dulu. Pertama, pastiin lo punya Astro dengan dukungan React:

npm create astro@latest my-astro-project
cd my-astro-project
npx astro add react tailwind

Selanjutnya, initialize shadcn/ui di project lo:

npx shadcn-ui@latest init

Selama initialization, lo bakal diminta configure project. Pilih opsi berikut buat kompatibilitas Astro yang optimal:

  • TypeScript: Yes
  • Style: Default
  • Tailwind CSS: Yes
  • Components directory: ./src/components/ui

astro.config.mjs lo harusnya kelihatan kayak gini:

import { defineConfig } from "astro/config";
import react from "@astrojs/react";
import tailwind from "@astrojs/tailwind";

export default defineConfig({
    integrations: [react(), tailwind()],
});

Menguasai Penggunaan Component: File .astro vs .tsx

Ngerti kapan harus pake component di file .astro versus .tsx itu crucial banget buat performa optimal. Keputusan ini impact loading speed dan user experience lo.

Pake Component di File .astro

Buat component static yang nggak butuh user interaction, pake langsung di file .astro:

---
// Component static di file Astro
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
---

<Card>
    <CardHeader>
        <CardTitle>Selamat Datang di Platform Kita</CardTitle>
    </CardHeader>
    <CardContent>
        <p>Konten ini di-render saat build time buat performa optimal.</p>
        <Button variant="outline">Pelajari Lebih Lanjut</Button>
    </CardContent>
</Card>

Pendekatan ini render component sebagai HTML static saat build time, hasilnya page load lebih cepet dan performa SEO lebih bagus.

Baca Juga: Migrasi dari Bootstrap ke Tailwind CSS: Perjalanan Gue

Bikin Component Interaktif di File .tsx

Kalau lo butuh state management atau user interaction, bikin dedicated React component:

// SearchComponent.tsx
import { useState, useCallback } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Search, X } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";

interface SearchResult {
    id: number;
    title: string;
    description: string;
}

export default function SearchComponent() {
    const [query, setQuery] = useState("");
    const [results, setResults] = useState<SearchResult[]>([]);
    const [isLoading, setIsLoading] = useState(false);

    const handleSearch = useCallback(async () => {
        if (!query.trim()) return;

        setIsLoading(true);
        try {
            // Simulasi API call
            await new Promise((resolve) => setTimeout(resolve, 1000));
            setResults([
                { id: 1, title: "Hasil Contoh 1", description: "Ini adalah hasil pencarian contoh" },
                { id: 2, title: "Hasil Contoh 2", description: "Hasil contoh lainnya" },
            ]);
        } finally {
            setIsLoading(false);
        }
    }, [query]);

    const clearSearch = () => {
        setQuery("");
        setResults([]);
    };

    return (
        <div className="w-full max-w-md mx-auto space-y-4">
            <div className="flex gap-2">
                <Input
                    type="text"
                    placeholder="Cari artikel..."
                    value={query}
                    onChange={(e) => setQuery(e.target.value)}
                    onKeyDown={(e) => e.key === "Enter" && handleSearch()}
                    className="flex-1"
                />
                <Button onClick={handleSearch} disabled={isLoading} size="icon">
                    <Search className="h-4 w-4" />
                </Button>
                {query && (
                    <Button onClick={clearSearch} variant="outline" size="icon">
                        <X className="h-4 w-4" />
                    </Button>
                )}
            </div>

            {isLoading && (
                <Card>
                    <CardContent className="pt-6">
                        <p>Lagi nyari...</p>
                    </CardContent>
                </Card>
            )}

            {results.length > 0 && (
                <div className="space-y-2">
                    {results.map((result) => (
                        <Card key={result.id}>
                            <CardContent className="pt-4">
                                <h3 className="font-semibold">{result.title}</h3>
                                <p className="text-sm text-muted-foreground">{result.description}</p>
                            </CardContent>
                        </Card>
                    ))}
                </div>
            )}
        </div>
    );
}

Terus integrate ke Astro page lo dengan client directive yang sesuai:

---
import SearchComponent from "../components/SearchComponent.tsx";
import Layout from "../layouts/Layout.astro";
---

<Layout title="Demo Pencarian">
    <main class="container py-8 mx-auto">
        <h1 class="mb-8 text-3xl font-bold">Demo Pencarian Interaktif</h1>
        <SearchComponent client:visible />
    </main>
</Layout>

Optimasi Performa dengan Client Directive

Client directive Astro tuh senjata rahasia lo buat optimasi performa. Pilih directive yang tepat berdasarkan tingkat kepentingan component dan pola user interaction:

  • client:load - Load immediately (pake buat elemen interaktif yang critical)
  • client:visible - Load pas component masuk viewport (ideal buat konten below-the-fold)
  • client:idle - Load pas browser idle (perfect buat fitur non-critical)
  • client:media - Load berdasarkan media queries (responsive loading)

Pola Integrasi Advanced

Pas aplikasi lo berkembang, lo bakal nemuin skenario yang lebih kompleks. Ini cara handle secara efektif:

Form Handling dengan shadcn/ui

Bikin form sophisticated yang balance antara user experience dengan performa:

// ContactForm.tsx
import { useState, FC } from "react";
import { useForm, SubmitHandler } from "react-hook-form";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Alert, AlertDescription } from "@/components/ui/alert";

interface FormData {
    name: string;
    email: string;
    message: string;
}

export default function ContactForm() {
    const [submitStatus, setSubmitStatus] = useState<"idle" | "success" | "error">("idle");

    const {
        register,
        handleSubmit,
        formState: { errors, isSubmitting },
        reset,
    } = useForm<FormData>();

    const onSubmit: SubmitHandler<FormData> = async (data) => {
        setSubmitStatus("idle");
        try {
            // Logic form submission lo di sini
            await new Promise((resolve) => setTimeout(resolve, 2000)); // Simulasi API call
            setSubmitStatus("success");
            reset();
        } catch (error) {
            setSubmitStatus("error");
        }
    };

    return (
        <form onSubmit={handleSubmit(onSubmit)} className="space-y-6 max-w-md mx-auto">
            <div className="space-y-2">
                <Label htmlFor="name">Nama</Label>
                <Input id="name" {...register("name", { required: "Nama wajib diisi" })} placeholder="Nama lo" />
                {errors.name && <p className="text-sm text-destructive">{errors.name.message}</p>}
            </div>

            <div className="space-y-2">
                <Label htmlFor="email">Email</Label>
                <Input
                    id="email"
                    type="email"
                    {...register("email", {
                        required: "Email wajib diisi",
                        pattern: {
                            value: /^\S+@\S+$/i,
                            message: "Format email nggak valid",
                        },
                    })}
                    placeholder="[email protected]"
                />
                {errors.email && <p className="text-sm text-destructive">{errors.email.message}</p>}
            </div>

            <div className="space-y-2">
                <Label htmlFor="message">Pesan</Label>
                <Textarea
                    id="message"
                    {...register("message", { required: "Pesan wajib diisi" })}
                    placeholder="Pesan lo"
                    rows={4}
                />
                {errors.message && <p className="text-sm text-destructive">{errors.message.message}</p>}
            </div>

            <Button type="submit" disabled={isSubmitting} className="w-full">
                {isSubmitting ? "Lagi kirim..." : "Kirim Pesan"}
            </Button>

            {submitStatus === "success" && (
                <Alert>
                    <AlertDescription>Makasih! Pesan lo udah berhasil dikirim.</AlertDescription>
                </Alert>
            )}

            {submitStatus === "error" && (
                <Alert variant="destructive">
                    <AlertDescription>Maaf, ada error pas kirim pesan lo. Coba lagi ya.</AlertDescription>
                </Alert>
            )}
        </form>
    );
}

Baca Juga: Bikin Universal Social Media Embed Component di React Jodit

Best Practice dan Pertimbangan Performa

Buat maksimalin benefit dari kombinasi powerful ini, ikutin practice essential berikut:

Organisasi Component

Struktur component lo secara logical buat maintain scalability:

src/
├── components/
│   ├── ui/              # shadcn/ui components
│   ├── forms/           # Component khusus form
│   ├── layout/          # Layout components
│   └── features/        # Component khusus fitur
├── layouts/             # Astro layouts
└── pages/              # Astro pages

Tips Optimasi Performa

  1. Pake static rendering setiap memungkinkan: Kalau component nggak butuh interactivity, render di file .astro
  2. Pilih client directive dengan bijak: Pake client:visible buat component below the fold
  3. Implement code splitting: Pecah component interaktif besar jadi potongan kecil yang fokus
  4. Optimize bundle size: Cuma import shadcn/ui component yang lo pake aja

Pertimbangan Accessibility

Component shadcn/ui udah dibikin dengan accessibility in mind, tapi lo tetep harus:

  • Test dengan screen reader
  • Pastiin keyboard navigation proper
  • Maintain color contrast yang cukup
  • Kasih alt text yang meaningful buat gambar

Troubleshooting Masalah Umum

Selama development, lo mungkin nemuin beberapa challenge umum:

Konfigurasi TypeScript

Pastiin tsconfig.json lo include path mapping yang proper:

{
    "extends": "astro/tsconfigs/strict",
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "@/*": ["./src/*"]
        }
    }
}

Konflik Styling

Kalau lo nemuin masalah styling, cek konfigurasi Tailwind lo sudah include preset shadcn/ui dan content path lo udah benar.

Kesimpulan: Membangun Masa Depan Web Development

Kombinasi Astro dan shadcn/ui merepresentasikan langkah maju yang signifikan dalam modern web development. Dengan memanfaatkan pendekatan performance-first Astro dengan component shadcn/ui yang cantik dan accessible, lo bisa bikin aplikasi yang cepat dan user-friendly.

Pola arsitektur ini memungkinkan lo mulai dengan page static yang SEO-friendly dan progressively enhance dengan elemen interaktif sesuai kebutuhan. Hasilnya adalah development experience yang produktif dan performant, bikin aplikasi yang satisfy developer dan user.

Entah lo lagi bikin website marketing, dashboard, atau aplikasi web kompleks, kombinasi ini kasih flexibility dan performa yang lo butuhin buat sukses di landscape digital yang competitive sekarang. Kuncinya adalah ngerti kapan pake setiap pendekatan dan optimize sesuai use case spesifik lo.

Sambil web development terus berkembang, pola kayak gini nunjukkiin bagaimana kita bisa achieve balance sempurna antara developer experience, performa, dan user satisfaction. Mulai eksperimen dengan setup ini sekarang deh, dan rasain masa depan web development!

Back to Blog

Related Posts

View All Posts »