Astro Quick Tutorial for Vue Developers

Astro is a modern web framework that focuses on content-driven websites with zero JS by default. Unlike Vue's SPA approach, Astro uses islands archite

Astro Quick Tutorial for Vue Developers

Astro is a modern web framework that focuses on content-driven websites with zero JS by default. Unlike Vue's SPA approach, Astro uses islands architecture - you ship HTML/CSS by default and add JavaScript only where needed.

Key Concepts Coming from Vue

1. Component Structure

---
// Component Script (runs at build time)
const title = 'Hello World';
const items = ['Vue', 'React', 'Svelte'];
---

<!-- Template (similar to Vue template) -->
<h1>{title}</h1>
<ul>
  {items.map((item) => <li>{item}</li>)}
</ul>

<style>
  h1 {
    color: blue;
  }
</style>

Key Differences from Vue:

β€’ Frontmatter: Code between --- runs at build time (server-side)

β€’ JSX-like syntax: Use {} instead of {{ }} for expressions

β€’ No reactivity by default: Variables are static unless you add client-side JS

2. File-based Routing

src/pages/
β”œβ”€β”€ index.astro          β†’ /
β”œβ”€β”€ about.astro          β†’ /about
β”œβ”€β”€ blog/
β”‚   β”œβ”€β”€ index.astro      β†’ /blog
β”‚   └── [slug].astro     β†’ /blog/hello-world
└── api/
    └── posts.json.js    β†’ /api/posts.json

3. Layouts (Similar to Vue Router layouts)

---
const { title } = Astro.props;
---

<!-- src/layouts/BaseLayout.astro -->
<html>
  <head>
    <title>{title}</title>
  </head>
  <body>
    <nav>...</nav>
    <main>
      <slot />
    </main>
  </body>
</html>
---
import BaseLayout from '../layouts/BaseLayout.astro';
---

<!-- src/pages/about.astro -->
<BaseLayout title="About">
  <h1>About Page</h1>
</BaseLayout>

4. Using Vue Components in Astro

---
import VueCounter from '../components/VueCounter.vue';
---

<!-- Static by default -->
<VueCounter count={5} />

<!-- Interactive (hydrated) -->
<VueCounter count={5} client:load />
<VueCounter count={5} client:idle />
<VueCounter count={5} client:visible />

Client Directives:

β€’ client:load - Hydrate immediately

β€’ client:idle - Hydrate when main thread is free

β€’ client:visible - Hydrate when component is visible

β€’ client:media - Hydrate when media query matches

5. Data Fetching (Build-time)

---
// This runs at build time, similar to Vue's asyncData
const response = await fetch('https://api.example.com/posts');
const posts = await response.json();
---

<div>
  {
    posts.map((post) => (
      <article>
        <h2>{post.title}</h2>
        <p>{post.excerpt}</p>
      </article>
    ))
  }
</div>

6. Dynamic Routes

---
export async function getStaticPaths() {
  const posts = await fetchPosts();
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
---

<!-- src/pages/blog/[slug].astro -->
<h1>{post.title}</h1>
<p>{post.content}</p>

7. Content Collections (for Markdown/MDX)

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    date: z.date(),
    tags: z.array(z.string()),
  }),
});

export const collections = { blog };
---
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
---

Migration Tips from Vue

1. Mindset Shift

β€’ Vue: Runtime, reactive, SPA

β€’ Astro: Build-time, static-first, MPA

2. State Management

β€’ Use Vue components with client:* directives for reactive state

β€’ Consider nanostores for shared state between islands

3. Styling

---
// Can import CSS modules, Sass, etc.
import styles from './Component.module.css';
---

<div class={styles.container}>
  <!-- Scoped styles work similar to Vue -->
  <style>
    .local-class {
      color: red;
    }
  </style>

  <!-- Global styles -->
  <style is:global>
    body {
      font-family: Arial;
    }
  </style>
</div>

4. Environment Variables

// Access like Vue's process.env
const apiUrl = import.meta.env.PUBLIC_API_URL;
const secret = import.meta.env.SECRET_KEY; // Only available server-side

Project Setup

# Create new Astro project
npm create astro@latest my-project

# Add Vue integration
npx astro add vue

# Add other integrations
npx astro add tailwind
npx astro add mdx

Configuration

// astro.config.mjs
import { defineConfig } from 'astro/config';
import vue from '@astrojs/vue';

export default defineConfig({
  integrations: [vue()],
  output: 'static', // or 'server' for SSR
});

Common Patterns

Mixed Static + Interactive

---
// Static data fetching
const products = await fetchProducts();
---

<!-- Static list -->
<div class="products">
  {
    products.map((product) => (
      <div class="product-card">
        <h3>{product.name}</h3>
        <p>{product.price}</p>
        {/* Interactive component */}
        <AddToCart product={product} client:visible />
      </div>
    ))
  }
</div>

API Routes

// src/pages/api/newsletter.js
export async function POST({ request }) {
  const data = await request.formData();
  const email = data.get('email');

  // Process signup
  await addToNewsletter(email);

  return new Response(JSON.stringify({ success: true }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
}

Best Practices

1. Start static, add interactivity where needed

2. Use Vue components for complex interactive features

3. Leverage Astro's build-time data fetching

4. Consider SEO benefits of server-rendered HTML

5. Use content collections for blog/documentation sites

Performance Benefits

β€’ Smaller bundles: Only ship JS for interactive components

β€’ Better Core Web Vitals: Less JavaScript = faster loading

β€’ SEO-friendly: Server-rendered HTML by default

β€’ Progressive enhancement: Site works without JavaScript

This architecture is perfect for content sites, marketing pages, blogs, and e-commerce where you want the performance benefits of static sites but the flexibility to add interactivity where needed.