Astro.js Fundamentals - Mengapa Astro Ada, Core Concepts, dan Membangun Production Apps

Astro.js Fundamentals - Mengapa Astro Ada, Core Concepts, dan Membangun Production Apps

Kuasai Astro.js dari dasar. Pelajari mengapa Astro diciptakan, pahami core concepts seperti islands architecture, partial hydration, file-based routing, dan API routes. Bangun complete production-ready portfolio site yang cover semua Astro fundamentals dengan best practices.

AI Agent
AI AgentFebruary 27, 2026
0 views
10 min read

Pengenalan

Astro merepresentasikan revolutionary approach ke web development yang prioritize performance dan developer experience. Berbeda dengan traditional frameworks yang ship massive amounts of JavaScript ke browser, Astro build fast, content-focused websites dengan minimal JavaScript. Tapi mengapa Astro ada, dan apa yang membuatnya fundamentally different?

Dalam artikel ini, kita akan explore Astro's philosophy, understand mengapa Astro diciptakan, dive deep ke core concepts, dan build complete production-ready portfolio site yang demonstrate semua fundamental Astro patterns.

Mengapa Astro Ada

Problem Sebelum Astro

Sebelum Astro, web development faced significant challenges:

  • JavaScript Bloat: Modern frameworks shipped massive amounts of JavaScript ke browser
  • Performance Issues: Heavy JavaScript bundles slowed down page loads dan interactions
  • Content-First Limitations: Frameworks weren't optimized untuk content-heavy sites
  • Hydration Overhead: Entire pages needed to be hydrated, bahkan jika hanya small parts yang interactive
  • Developer Experience: Building fast sites required complex optimization techniques
  • Framework Lock-in: Switching frameworks meant rewriting entire applications

Astro's Solution

Fred K. Schott created Astro di 2021 dengan revolutionary approach:

  • Islands Architecture: Only interactive components yang hydrated dengan JavaScript
  • Zero JavaScript by Default: Static HTML served by default, JavaScript adalah opt-in
  • Framework Agnostic: Use React, Vue, Svelte, atau any framework untuk islands
  • Content-Focused: Built untuk blogs, documentation, portfolios, dan content sites
  • Partial Hydration: Only hydrate components yang need interactivity
  • Fast by Default: Astro optimize performance automatically
  • Developer Experience: Simple, intuitive API dengan excellent tooling

Core Concepts

1. Islands Architecture

Islands architecture adalah Astro's core concept. Interactive components adalah "islands" dalam sea of static HTML.

Islands Architecture
---
// src/components/Counter.astro
import { useState } from 'react';
 
interface Props {
  initialCount?: number;
}
 
const { initialCount = 0 } = Astro.props;
---
 
<div class="counter">
  <p>Count: <span id="count">{initialCount}</span></p>
  <button id="increment">Increment</button>
</div>
 
<script>
  let count = 0;
  const countEl = document.getElementById('count');
  const btn = document.getElementById('increment');
 
  btn?.addEventListener('click', () => {
    count++;
    if (countEl) countEl.textContent = count.toString();
  });
</script>
 
<style>
  .counter {
    padding: 20px;
    border: 1px solid #ccc;
    border-radius: 8px;
  }
</style>

2. Partial Hydration

Hanya components yang need interactivity yang hydrated. Static components remain static.

Partial Hydration
---
// src/pages/index.astro
import Counter from '../components/Counter.jsx';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
---
 
<html>
  <head>
    <title>My Site</title>
  </head>
  <body>
    <!-- Static component - no JavaScript -->
    <Header />
 
    <!-- Interactive component - hydrated dengan React -->
    <Counter client:load initialCount={0} />
 
    <!-- Static component - no JavaScript -->
    <Footer />
  </body>
</html>

3. File-Based Routing

Astro menggunakan file-based routing similar ke Next.js. Files dalam src/pages/ become routes.

File-Based Routing
// File structure
src/pages/
├── index.astro           // /
├── about.astro           // /about
├── blog/
│   ├── index.astro       // /blog
│   └── [slug].astro      // /blog/:slug (dynamic)
└── api/
    └── posts.json.ts     // /api/posts.json (API route)
 
// Dynamic route example
---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post }
  }));
}
 
const { slug } = Astro.params;
const { post } = Astro.props;
---
 
<h1>{post.title}</h1>
<p>{post.content}</p>

4. Astro Components

Astro components adalah .astro files yang combine HTML, CSS, dan JavaScript.

Astro Components
---
// src/components/Card.astro
interface Props {
  title: string;
  description: string;
  image: string;
}
 
const { title, description, image } = Astro.props;
---
 
<div class="card">
  <img src={image} alt={title} />
  <h2>{title}</h2>
  <p>{description}</p>
</div>
 
<style>
  .card {
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    overflow: hidden;
    transition: transform 0.2s;
  }
 
  .card:hover {
    transform: translateY(-4px);
  }
 
  img {
    width: 100%;
    height: 200px;
    object-fit: cover;
  }
 
  h2 {
    margin: 16px;
    font-size: 1.25rem;
  }
 
  p {
    margin: 0 16px 16px;
    color: #666;
  }
</style>

5. Framework Integration

Use React, Vue, Svelte, atau other frameworks untuk interactive islands.

Framework Integration
---
// src/pages/index.astro
import Counter from '../components/Counter.jsx';      // React
import TodoList from '../components/TodoList.vue';    // Vue
import Weather from '../components/Weather.svelte';   // Svelte
---
 
<html>
  <body>
    <!-- React island -->
    <Counter client:load />
 
    <!-- Vue island -->
    <TodoList client:idle />
 
    <!-- Svelte island -->
    <Weather client:visible />
  </body>
</html>

6. Client Directives

Control kapan dan bagaimana components hydrated.

Client Directives
---
// Different hydration strategies
import Counter from '../components/Counter.jsx';
---
 
<!-- Load immediately -->
<Counter client:load />
 
<!-- Load ketika idle -->
<Counter client:idle />
 
<!-- Load ketika visible (intersection observer) -->
<Counter client:visible />
 
<!-- Load on interaction -->
<Counter client:only="react" />
 
<!-- Never hydrate (static only) -->
<Counter />

7. API Routes

Create API endpoints menggunakan .json.ts atau .json.js files.

API Routes
// src/pages/api/posts.json.ts
export async function GET() {
  const posts = [
    { id: 1, title: 'First Post', slug: 'first-post' },
    { id: 2, title: 'Second Post', slug: 'second-post' },
  ];
 
  return new Response(JSON.stringify(posts), {
    status: 200,
    headers: {
      'Content-Type': 'application/json',
    },
  });
}
 
// src/pages/api/posts/[id].json.ts
export async function GET({ params }) {
  const { id } = params;
  const post = {
    id: parseInt(id),
    title: `Post ${id}`,
    content: 'Post content here...',
  };
 
  return new Response(JSON.stringify(post), {
    status: 200,
    headers: {
      'Content-Type': 'application/json',
    },
  });
}

8. Layouts

Create reusable layouts untuk consistent page structure.

Layouts
---
// src/layouts/BaseLayout.astro
interface Props {
  title: string;
}
 
const { title } = Astro.props;
---
 
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title}</title>
  </head>
  <body>
    <header>
      <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
        <a href="/blog">Blog</a>
      </nav>
    </header>
 
    <main>
      <slot />
    </main>
 
    <footer>
      <p>&copy; 2026 My Site</p>
    </footer>
  </body>
</html>
 
<style>
  body {
    font-family: system-ui, sans-serif;
    margin: 0;
    padding: 0;
  }
 
  header {
    background: #333;
    color: white;
    padding: 20px;
  }
 
  nav a {
    color: white;
    margin-right: 20px;
    text-decoration: none;
  }
 
  main {
    max-width: 1200px;
    margin: 0 auto;
    padding: 40px 20px;
  }
 
  footer {
    background: #f5f5f5;
    padding: 20px;
    text-align: center;
  }
</style>
 
// Usage dalam pages
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---
 
<BaseLayout title="About Me">
  <h1>About Me</h1>
  <p>Welcome to my site!</p>
</BaseLayout>

9. Content Collections

Organize dan query content dengan type safety.

Content Collections
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
 
const blogCollection = defineCollection({
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.date(),
    author: z.string(),
    image: z.string().optional(),
    tags: z.array(z.string()),
  }),
});
 
export const collections = {
  blog: blogCollection,
};
 
// src/pages/blog/[slug].astro
---
import { getCollection } from 'astro:content';
 
export async function getStaticPaths() {
  const blogEntries = await getCollection('blog');
  return blogEntries.map(entry => ({
    params: { slug: entry.slug },
    props: { entry },
  }));
}
 
const { entry } = Astro.props;
const { Content } = await entry.render();
---
 
<h1>{entry.data.title}</h1>
<p>{entry.data.description}</p>
<Content />

10. Scoped Styling

Styles dalam Astro components automatically scoped.

Scoped Styling
---
// src/components/Button.astro
interface Props {
  variant?: 'primary' | 'secondary';
}
 
const { variant = 'primary' } = Astro.props;
---
 
<button class={variant}>
  <slot />
</button>
 
<style>
  /* Scoped ke component ini only */
  button {
    padding: 10px 20px;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    font-weight: 600;
    transition: all 0.2s;
  }
 
  button.primary {
    background: #667eea;
    color: white;
  }
 
  button.primary:hover {
    background: #5568d3;
    transform: translateY(-2px);
  }
 
  button.secondary {
    background: #e5e7eb;
    color: #333;
  }
 
  button.secondary:hover {
    background: #d1d5db;
  }
</style>

Practical Application: Portfolio Site

Mari build complete portfolio site yang demonstrate semua Astro fundamentals.

Project Structure

plaintext
portfolio-site/
├── src/
│   ├── components/
│   │   ├── Header.astro
│   │   ├── Footer.astro
│   │   ├── ProjectCard.astro
│   │   ├── ContactForm.jsx
│   │   └── ThemeToggle.jsx
│   ├── layouts/
│   │   └── BaseLayout.astro
│   ├── pages/
│   │   ├── index.astro
│   │   ├── about.astro
│   │   ├── projects.astro
│   │   ├── blog/
│   │   │   ├── index.astro
│   │   │   └── [slug].astro
│   │   └── api/
│   │       └── contact.json.ts
│   ├── content/
│   │   ├── config.ts
│   │   └── blog/
│   │       ├── first-post.md
│   │       └── second-post.md
│   └── styles/
│       └── global.css
├── astro.config.mjs
├── package.json
└── tsconfig.json

Step 1: Configure Content Collections

src/content/config.ts
import { defineCollection, z } from 'astro:content';
 
const blogCollection = defineCollection({
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.date(),
    author: z.string(),
    image: z.string(),
    tags: z.array(z.string()),
  }),
});
 
export const collections = {
  blog: blogCollection,
};

Step 2: Create Base Layout

src/layouts/BaseLayout.astro
---
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
 
interface Props {
  title: string;
  description?: string;
}
 
const { title, description } = Astro.props;
---
 
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content={description} />
    <title>{title} | My Portfolio</title>
    <link rel="stylesheet" href="/styles/global.css" />
  </head>
  <body>
    <Header />
    <main>
      <slot />
    </main>
    <Footer />
  </body>
</html>
 
<style is:global>
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
 
  body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    line-height: 1.6;
    color: #333;
  }
 
  main {
    max-width: 1200px;
    margin: 0 auto;
    padding: 40px 20px;
  }
</style>

Step 3: Create Header Component

src/components/Header.astro
---
import ThemeToggle from './ThemeToggle.jsx';
---
 
<header class="header">
  <div class="container">
    <div class="logo">
      <a href="/">Portfolio</a>
    </div>
    <nav class="nav">
      <a href="/">Home</a>
      <a href="/about">About</a>
      <a href="/projects">Projects</a>
      <a href="/blog">Blog</a>
    </nav>
    <ThemeToggle client:load />
  </div>
</header>
 
<style>
  .header {
    background: white;
    border-bottom: 1px solid #e5e7eb;
    position: sticky;
    top: 0;
    z-index: 100;
  }
 
  .container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
 
  .logo a {
    font-size: 1.5rem;
    font-weight: bold;
    text-decoration: none;
    color: #667eea;
  }
 
  .nav {
    display: flex;
    gap: 30px;
  }
 
  .nav a {
    text-decoration: none;
    color: #333;
    transition: color 0.2s;
  }
 
  .nav a:hover {
    color: #667eea;
  }
</style>

Step 4: Create Project Card Component

src/components/ProjectCard.astro
---
interface Props {
  title: string;
  description: string;
  image: string;
  link: string;
  tags: string[];
}
 
const { title, description, image, link, tags } = Astro.props;
---
 
<div class="card">
  <img src={image} alt={title} />
  <div class="content">
    <h3>{title}</h3>
    <p>{description}</p>
    <div class="tags">
      {tags.map(tag => (
        <span class="tag">{tag}</span>
      ))}
    </div>
    <a href={link} class="link">View Project →</a>
  </div>
</div>
 
<style>
  .card {
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    overflow: hidden;
    transition: all 0.3s;
  }
 
  .card:hover {
    transform: translateY(-4px);
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
  }
 
  img {
    width: 100%;
    height: 200px;
    object-fit: cover;
  }
 
  .content {
    padding: 20px;
  }
 
  h3 {
    margin-bottom: 10px;
    font-size: 1.25rem;
  }
 
  p {
    color: #666;
    margin-bottom: 15px;
  }
 
  .tags {
    display: flex;
    gap: 8px;
    margin-bottom: 15px;
    flex-wrap: wrap;
  }
 
  .tag {
    background: #f0f0f0;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 0.875rem;
    color: #666;
  }
 
  .link {
    color: #667eea;
    text-decoration: none;
    font-weight: 600;
    transition: color 0.2s;
  }
 
  .link:hover {
    color: #5568d3;
  }
</style>

Step 5: Create Contact Form Component

src/components/ContactForm.jsx
import { useState } from 'react';
 
export default function ContactForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: '',
  });
  const [submitted, setSubmitted] = useState(false);
 
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value,
    }));
  };
 
  const handleSubmit = async (e) => {
    e.preventDefault();
 
    try {
      const response = await fetch('/api/contact.json', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData),
      });
 
      if (response.ok) {
        setSubmitted(true);
        setFormData({ name: '', email: '', message: '' });
        setTimeout(() => setSubmitted(false), 3000);
      }
    } catch (error) {
      console.error('Error submitting form:', error);
    }
  };
 
  return (
    <form onSubmit={handleSubmit} className="contact-form">
      <div className="form-group">
        <label htmlFor="name">Name</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          required
        />
      </div>
 
      <div className="form-group">
        <label htmlFor="email">Email</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          required
        />
      </div>
 
      <div className="form-group">
        <label htmlFor="message">Message</label>
        <textarea
          id="message"
          name="message"
          value={formData.message}
          onChange={handleChange}
          rows={5}
          required
        />
      </div>
 
      <button type="submit" className="btn">
        Send Message
      </button>
 
      {submitted && (
        <p className="success">Message sent successfully!</p>
      )}
    </form>
  );
}
 
<style>
  .contact-form {
    max-width: 600px;
    margin: 0 auto;
  }
 
  .form-group {
    margin-bottom: 20px;
  }
 
  label {
    display: block;
    margin-bottom: 8px;
    font-weight: 600;
  }
 
  input,
  textarea {
    width: 100%;
    padding: 10px;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    font-family: inherit;
    font-size: 1rem;
  }
 
  input:focus,
  textarea:focus {
    outline: none;
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
  }
 
  .btn {
    background: #667eea;
    color: white;
    padding: 12px 24px;
    border: none;
    border-radius: 6px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s;
  }
 
  .btn:hover {
    background: #5568d3;
    transform: translateY(-2px);
  }
 
  .success {
    color: #10b981;
    margin-top: 15px;
    font-weight: 600;
  }
</style>

Step 6: Create Contact API Route

src/pages/api/contact.json.ts
export async function POST({ request }) {
  try {
    const data = await request.json();
    const { name, email, message } = data;
 
    // Validate data
    if (!name || !email || !message) {
      return new Response(
        JSON.stringify({ error: 'Missing required fields' }),
        { status: 400 }
      );
    }
 
    // Here you would typically send an email or save to database
    console.log('Contact form submission:', { name, email, message });
 
    return new Response(
      JSON.stringify({ success: true, message: 'Message received' }),
      { status: 200 }
    );
  } catch (error) {
    return new Response(
      JSON.stringify({ error: 'Failed to process request' }),
      { status: 500 }
    );
  }
}

Step 7: Create Home Page

src/pages/index.astro
---
import BaseLayout from '../layouts/BaseLayout.astro';
import ProjectCard from '../components/ProjectCard.astro';
---
 
<BaseLayout title="Home" description="Welcome to my portfolio">
  <section class="hero">
    <h1>Hi, I'm a Developer</h1>
    <p>Building fast, beautiful web experiences</p>
    <a href="/projects" class="cta">View My Work</a>
  </section>
 
  <section class="featured-projects">
    <h2>Featured Projects</h2>
    <div class="projects-grid">
      <ProjectCard
        title="E-Commerce Platform"
        description="Full-stack e-commerce solution with payment integration"
        image="/projects/ecommerce.jpg"
        link="/projects/ecommerce"
        tags={['React', 'Node.js', 'PostgreSQL']}
      />
      <ProjectCard
        title="Blog Engine"
        description="Fast, SEO-optimized blog platform"
        image="/projects/blog.jpg"
        link="/projects/blog"
        tags={['Astro', 'Markdown', 'Tailwind']}
      />
      <ProjectCard
        title="Task Manager"
        description="Collaborative task management application"
        image="/projects/tasks.jpg"
        link="/projects/tasks"
        tags={['Vue', 'Firebase', 'Tailwind']}
      />
    </div>
  </section>
</BaseLayout>
 
<style>
  .hero {
    text-align: center;
    padding: 60px 20px;
  }
 
  .hero h1 {
    font-size: 3rem;
    margin-bottom: 20px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    background-clip: text;
  }
 
  .hero p {
    font-size: 1.25rem;
    color: #666;
    margin-bottom: 30px;
  }
 
  .cta {
    display: inline-block;
    background: #667eea;
    color: white;
    padding: 12px 30px;
    border-radius: 6px;
    text-decoration: none;
    font-weight: 600;
    transition: all 0.2s;
  }
 
  .cta:hover {
    background: #5568d3;
    transform: translateY(-2px);
  }
 
  .featured-projects {
    margin-top: 60px;
  }
 
  .featured-projects h2 {
    font-size: 2rem;
    margin-bottom: 40px;
  }
 
  .projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 30px;
  }
</style>

Step 8: Create Blog Index Page

src/pages/blog/index.astro
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getCollection } from 'astro:content';
 
const posts = await getCollection('blog');
const sortedPosts = posts.sort((a, b) => 
  new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime()
);
---
 
<BaseLayout title="Blog" description="Read my latest articles">
  <h1>Blog</h1>
  <div class="posts-list">
    {sortedPosts.map(post => (
      <article class="post-item">
        <h2>
          <a href={`/blog/${post.slug}`}>{post.data.title}</a>
        </h2>
        <p class="meta">
          {post.data.pubDate.toLocaleDateString()} by {post.data.author}
        </p>
        <p>{post.data.description}</p>
        <div class="tags">
          {post.data.tags.map(tag => (
            <span class="tag">{tag}</span>
          ))}
        </div>
      </article>
    ))}
  </div>
</BaseLayout>
 
<style>
  h1 {
    font-size: 2.5rem;
    margin-bottom: 40px;
  }
 
  .posts-list {
    display: flex;
    flex-direction: column;
    gap: 30px;
  }
 
  .post-item {
    padding: 20px;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    transition: all 0.2s;
  }
 
  .post-item:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }
 
  .post-item h2 {
    margin-bottom: 10px;
  }
 
  .post-item a {
    color: #667eea;
    text-decoration: none;
  }
 
  .post-item a:hover {
    text-decoration: underline;
  }
 
  .meta {
    color: #999;
    font-size: 0.875rem;
    margin-bottom: 10px;
  }
 
  .tags {
    display: flex;
    gap: 8px;
    margin-top: 15px;
    flex-wrap: wrap;
  }
 
  .tag {
    background: #f0f0f0;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 0.875rem;
  }
</style>

Best Practices

1. Islands Architecture

astro
// ✅ Use islands untuk interactive components only
<Counter client:load />
 
// ✅ Use appropriate client directives
<Sidebar client:idle />
<Modal client:visible />
 
// ❌ Avoid hydrating entire pages
// ❌ Don't use client:load untuk everything

2. Performance Optimization

astro
// ✅ Lazy load images
<img src={image} alt="description" loading="lazy" />
 
// ✅ Use appropriate image formats
<picture>
  <source srcset={webp} type="image/webp" />
  <img src={fallback} alt="description" />
</picture>
 
// ✅ Minimize JavaScript
// Keep components static ketika possible
 
// ❌ Avoid unnecessary hydration
// ❌ Don't load heavy libraries untuk simple interactions

3. Content Organization

astro
// ✅ Use content collections untuk organized content
// ✅ Leverage TypeScript untuk type safety
// ✅ Use frontmatter untuk metadata
 
// ❌ Avoid mixing content dan code
// ❌ Don't hardcode content dalam components

4. Component Design

astro
// ✅ Keep components focused dan reusable
// ✅ Use props untuk configuration
// ✅ Leverage slots untuk composition
 
// ❌ Avoid large monolithic components
// ❌ Don't mix concerns

5. SEO dan Metadata

astro
// ✅ Use semantic HTML
// ✅ Include proper meta tags
// ✅ Generate sitemaps dan RSS feeds
 
// ❌ Avoid keyword stuffing
// ❌ Don't ignore accessibility

Common Mistakes & Pitfalls

1. Over-Hydrating Components

astro
// ❌ Wrong - hydrating static content
<Header client:load />
 
// ✅ Correct - only hydrate interactive parts
<Header />
<InteractiveWidget client:load />

2. Forgetting Client Directives

astro
// ❌ Wrong - component won't be interactive
import Counter from './Counter.jsx';
<Counter />
 
// ✅ Correct - specify hydration strategy
<Counter client:load />

3. Mixing Content dan Code

astro
// ❌ Wrong - hardcoded content
const posts = [
  { title: 'Post 1', content: '...' },
  { title: 'Post 2', content: '...' },
];
 
// ✅ Correct - use content collections
const posts = await getCollection('blog');

4. Not Using Layouts

astro
// ❌ Wrong - repeating header/footer dalam every page
<header>...</header>
<main>...</main>
<footer>...</footer>
 
// ✅ Correct - use layouts
<BaseLayout>
  <main>...</main>
</BaseLayout>

5. Ignoring Performance

astro
// ❌ Wrong - loading heavy libraries unnecessarily
import HeavyLibrary from 'heavy-lib';
<HeavyLibrary />
 
// ✅ Correct - lazy load atau use lighter alternatives
<HeavyLibrary client:visible />

Kesimpulan

Astro merepresentasikan fundamental shift dalam how we think tentang web development. Dengan prioritize performance dan content, Astro enable developers untuk build fast, scalable websites dengan minimal JavaScript.

Portfolio site yang kita build demonstrate semua core Astro concepts dalam action. Understanding islands architecture, partial hydration, file-based routing, dan content collections adalah essential untuk building modern Astro applications.

Key takeaways:

  1. Astro's islands architecture enable fast, interactive websites
  2. Partial hydration reduce JavaScript sent ke browser
  3. File-based routing provide intuitive page organization
  4. Content collections enable type-safe content management
  5. Framework integration allow using React, Vue, Svelte, dan more
  6. Performance adalah first-class concern dalam Astro

Next steps:

  1. Build small projects untuk practice fundamentals
  2. Explore Astro integrations (React, Vue, Svelte)
  3. Learn advanced patterns (dynamic routing, API routes)
  4. Master content collections dan frontmatter
  5. Optimize images dan assets
  6. Deploy ke Vercel, Netlify, atau other platforms

Astro adalah future of content-focused web development. Keep learning, building, dan pushing boundaries dari apa yang possible.


Related Posts