April 10, 2025

Content-First Development with Astro - SSG and SSR Strategies

A comprehensive guide to Astro's rendering strategies and development approaches

astro web-development ssg ssr javascript frontend

Content-First Development with Astro: SSG and SSR Strategies

This comprehensive guide covers Astro’s rendering strategies, development approaches, and best practices. It’s designed for developers looking to understand when and how to use Astro effectively and to familiarize themselves with SSR, SSG, and Hybrid rendering strategies.

Table of Contents

Introduction

Quick Start For comprehensive documentation and guides, visit the official Astro documentation.

Astro is a modern web framework that enables developers to build fast, content-focused websites with their preferred UI components and tools. Its core philosophy revolves around “Islands Architecture” and “Zero JavaScript by default,” making it particularly efficient for content-heavy websites. Released in 2021 by the team at Snowpack (now known as the Astro Technology Company), Astro has quickly gained popularity in the web development community, with over 50k GitHub stars (as of April 2025) and a growing ecosystem of integrations and tools.

Key Features and Benefits

Zero JavaScript by Default

Performance First Astro’s zero-JavaScript approach leads to significantly faster page loads and better Core Web Vitals scores.

Islands Architecture

Islands Architecture Think of your page as an ocean of static HTML with islands of interactivity. Only the interactive parts load JavaScript, while the rest stays as pure HTML.

Understanding Partial Hydration

Unlike traditional frameworks that hydrate the entire page, Astro uses partial hydration (also known as “islands architecture”). This means:

For example, a page with a static header, dynamic counter, and static footer would only load JavaScript for the counter component, while the header and footer remain pure HTML.

Multi-page Application (MPA)

.astro Components

Astro introduces a unique component format with the .astro extension, which combines the best aspects of templating languages and component frameworks:

Example of a basic .astro component:

---
// Component frontmatter (JavaScript/TypeScript)
const { title } = Astro.props;
---

<!-- Component template (HTML) -->
<div class="card">
  <h2>{title}</h2>
  <slot />
</div>

<style>
  /* Scoped styles */
  .card {
    padding: 1rem;
    border-radius: 0.5rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
</style>

Rendering Strategies

Choose Your Strategy Astro offers multiple rendering strategies to accommodate different use cases. Choose based on your specific needs:

  • SSG: For static content that doesn’t change often
  • SSR: For dynamic, personalized content
  • Hybrid: For a mix of both approaches

Astro offers multiple rendering strategies to accommodate different use cases and requirements. The framework’s flexibility allows developers to choose the most appropriate rendering method for each page or component, optimizing performance and user experience.

Static Site Generation (SSG)

SSG is Astro’s default rendering strategy, where pages are pre-rendered at build time into static HTML files. This approach generates a complete set of static HTML files during the build process, which are then served directly to users without requiring server-side processing for each request.

How SSG Works in Astro

When using SSG in Astro, the build process:

  1. Executes all JavaScript in the frontmatter section of each page
  2. Renders the resulting HTML
  3. Generates static files for each route
  4. Creates a complete static site that can be deployed to any static hosting service

This process happens once during build time, not on each request, which is why SSG is extremely fast and efficient.

---
// pages/about.astro
const data = await fetchData();
---

<html>
  <head>
    <title>About Us</title>
  </head>
  <body>
    <h1>About Us</h1>
    <p>{data.description}</p>
  </body>
</html>

SSG Benefits:

SSG Limitations:

SSG Use Cases:

SSG Data Fetching:

---
// pages/blog.astro
import { getCollection } from 'astro:content';

// Fetch data at build time
const posts = await getCollection('blog');
const sortedPosts = posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---

<html>
  <head>
    <title>Blog</title>
  </head>
  <body>
    <h1>Blog Posts</h1>
    <ul>
      {sortedPosts.map(post => (
        <li>
          <a href={`/blog/${post.slug}`}>{post.data.title}</a>
          <p>{post.data.description}</p>
        </li>
      ))}
    </ul>
  </body>
</html>

Server-Side Rendering (SSR)

SSR in Astro allows for dynamic content generation on each request, enabling personalized experiences and real-time data. Unlike SSG, which generates static HTML at build time, SSR generates HTML on each request, allowing for dynamic, user-specific content.

How SSR Works in Astro

When using SSR in Astro:

  1. Each page request is processed by the server
  2. The server executes the page’s JavaScript in the frontmatter
  3. HTML is generated dynamically based on the request context
  4. The generated HTML is sent to the client

This approach enables dynamic content generation while still maintaining the benefits of server-rendered HTML.

---
// pages/dashboard.astro
export const prerender = false;

const user = await getUser();
---

<html>
  <head>
    <title>Dashboard</title>
  </head>
  <body>
    <h1>Welcome, {user.name}</h1>
    <DashboardContent client:load />
  </body>
</html>

SSR Benefits:

SSR Limitations:

SSR Use Cases:

SSR Implementation:

---
// pages/api/user-data.ts
export async function GET({ request, locals }) {
  const user = locals.user;
  const data = await fetchUserData(user.id);

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

SSR with Authentication:

---
// pages/protected.astro
export const prerender = false;

const user = await getUser();
if (!user) {
  return Astro.redirect('/login');
}
---

<html>
  <head>
    <title>Protected Page</title>
  </head>
  <body>
    <h1>Welcome, {user.name}</h1>
    <ProtectedContent user={user} client:load />
  </body>
</html>

Hybrid Rendering

Astro’s hybrid rendering approach allows developers to mix SSG and SSR on the same site, enabling optimal performance for different page types. This flexibility is one of Astro’s most powerful features, allowing developers to choose the most appropriate rendering strategy for each page or component.

How Hybrid Rendering Works

With hybrid rendering, developers can:

  1. Use SSG for content that rarely changes
  2. Use SSR for dynamic, user-specific content
  3. Mix both approaches within the same application
  4. Choose rendering strategy at the page level

This approach provides the best of both worlds, optimizing performance and user experience across different types of content.

---
// pages/products/[id].astro
export async function getStaticPaths() {
  const products = await fetchProducts();
  return products.map(product => ({
    params: { id: product.id },
    props: { product }
  }));
}

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

<html>
  <head>
    <title>{product.name}</title>
  </head>
  <body>
    <h1>{product.name}</h1>
    <ProductDetails product={product} client:visible />
  </body>
</html>

Hybrid Benefits:

Hybrid Implementation Strategies:

  1. Route-based strategy: Use SSG for content pages, SSR for dynamic pages

    • Example: SSG for blog posts, SSR for user dashboard
  2. Component-based strategy: Mix static and dynamic components on the same page

    • Example: Static header/footer with dynamic content in the middle
  3. Data-based strategy: Use SSG for data that rarely changes, SSR for frequently updated data

    • Example: SSG for product catalog, SSR for inventory levels
  4. User-based strategy: Use SSG for public content, SSR for authenticated users

    • Example: SSG for marketing pages, SSR for user account pages

Hybrid Example: E-commerce Site

---
// pages/products/index.astro
// Static product catalog page
const products = await getProducts();
---

<html>
  <head>
    <title>Our Products</title>
  </head>
  <body>
    <h1>Product Catalog</h1>
    <ProductList products={products} />
  </body>
</html>
---
// pages/products/[id].astro
// Static product detail pages with dynamic inventory
export async function getStaticPaths() {
  const products = await getProducts();
  return products.map(product => ({
    params: { id: product.id },
    props: { product }
  }));
}

const { product } = Astro.props;
const inventory = await getInventoryLevel(product.id);
---

<html>
  <head>
    <title>{product.name}</title>
  </head>
  <body>
    <h1>{product.name}</h1>
    <ProductDetails product={product} />
    <InventoryStatus level={inventory} client:load />
  </body>
</html>
---
// pages/cart.astro
// Server-rendered shopping cart
export const prerender = false;

const cart = await getCart(Astro.cookies.get('sessionId')?.value);
---

<html>
  <head>
    <title>Shopping Cart</title>
  </head>
  <body>
    <h1>Your Cart</h1>
    <CartContents items={cart.items} client:load />
  </body>
</html>

Styling Options

Astro supports multiple styling approaches, allowing developers to choose the best option for their project.

CSS Modules

---
// components/Button.module.css
.button {
  background-color: #4CAF50;
  border: none;
  color: white;
  padding: 15px 32px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 4px 2px;
  cursor: pointer;
  border-radius: 4px;
}
---

---
// components/Button.astro
import styles from './Button.module.css';
---

<button class={styles.button}>
  <slot />
</button>

Tailwind CSS

Tailwind CSS is one of the most popular styling solutions for Astro projects.

Installation:

npx astro add tailwind

Usage:

---
// components/Card.astro
---

<div class="max-w-sm rounded overflow-hidden shadow-lg bg-white">
  <div class="px-6 py-4">
    <div class="font-bold text-xl mb-2">Card Title</div>
    <p class="text-gray-700 text-base">
      <slot />
    </p>
  </div>
  <div class="px-6 pt-4 pb-2">
    <span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">#tag1</span>
    <span class="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2">#tag2</span>
  </div>
</div>

Configuration:

// tailwind.config.mjs
/** @type {import('tailwindcss').Config} */
export default {
  content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
  theme: {
    extend: {
      colors: {
        primary: "#4CAF50",
      },
    },
  },
  plugins: [],
};

Styled Components

For React developers, Astro supports styled-components:

---
// components/StyledButton.jsx
import styled from 'styled-components';

const Button = styled.button`
  background-color: #4CAF50;
  border: none;
  color: white;
  padding: 15px 32px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 4px 2px;
  cursor: pointer;
  border-radius: 4px;

  &:hover {
    background-color: #45a049;
  }
`;

export default Button;
---

---
// pages/index.astro
import StyledButton from '../components/StyledButton';
---

<html>
  <head>
    <title>Styled Components Example</title>
  </head>
  <body>
    <StyledButton client:load>Click me</StyledButton>
  </body>
</html>

Global Styles

Astro supports global styles through various methods:

Global CSS:

---
// src/styles/global.css
:root {
  --primary-color: #4CAF50;
  --secondary-color: #2196F3;
}

body {
  font-family: 'Arial', sans-serif;
  line-height: 1.6;
  margin: 0;
  padding: 0;
}
---

---
// src/layouts/Layout.astro
import '../styles/global.css';
---

<html>
  <head>
    <title>Global Styles Example</title>
  </head>
  <body>
    <slot />
  </body>
</html>

CSS Variables:

---
// src/styles/variables.css
:root {
  --primary-color: #4CAF50;
  --secondary-color: #2196F3;
  --font-family: 'Arial', sans-serif;
  --spacing-unit: 8px;
}
---

---
// components/Button.astro
import '../styles/variables.css';
---

<style>
  .button {
    background-color: var(--primary-color);
    font-family: var(--font-family);
    padding: calc(var(--spacing-unit) * 2);
  }
</style>

<button class="button">
  <slot />
</button>

Common Integrations

Astro has a rich ecosystem of integrations that extend its functionality.

UI Frameworks

Astro supports a wide range of UI frameworks, allowing developers to use their preferred tools:

React Integration:

npx astro add react
---
// components/Counter.jsx
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
---

---
// pages/index.astro
import Counter from '../components/Counter';
---

<html>
  <head>
    <title>React Counter</title>
  </head>
  <body>
    <Counter client:load />
  </body>
</html>

Vue Integration:

npx astro add vue
---
// components/Counter.vue
<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++;
    }
  }
}
</script>
---

---
// pages/index.astro
import Counter from '../components/Counter.vue';
---

<html>
  <head>
    <title>Vue Counter</title>
  </head>
  <body>
    <Counter client:load />
  </body>
</html>

Content Management Systems

Strapi Integration:

npm install @astrojs/strapi
// astro.config.mjs
import { defineConfig } from "astro/config";
import strapi from "@astrojs/strapi";

export default defineConfig({
  integrations: [
    strapi({
      apiURL: "https://your-strapi-url.com",
      apiToken: process.env.STRAPI_API_TOKEN,
    }),
  ],
});
---
// pages/blog.astro
import { strapi } from '@astrojs/strapi';

const posts = await strapi.getEntries('posts', {
  populate: '*',
  sort: ['publishedAt:desc'],
});
---

<html>
  <head>
    <title>Blog</title>
  </head>
  <body>
    <h1>Blog Posts</h1>
    <ul>
      {posts.data.map(post => (
        <li>
          <h2>{post.attributes.title}</h2>
          <p>{post.attributes.description}</p>
        </li>
      ))}
    </ul>
  </body>
</html>

Contentful Integration:

npm install contentful
---
// pages/blog.astro
import { createClient } from 'contentful';

const client = createClient({
  space: process.env.CONTENTFUL_SPACE_ID,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});

const posts = await client.getEntries({
  content_type: 'blogPost',
  order: '-sys.createdAt',
});
---

<html>
  <head>
    <title>Blog</title>
  </head>
  <body>
    <h1>Blog Posts</h1>
    <ul>
      {posts.items.map(post => (
        <li>
          <h2>{post.fields.title}</h2>
          <p>{post.fields.description}</p>
        </li>
      ))}
    </ul>
  </body>
</html>

Image Optimization

Astro provides built-in image optimization:

---
// components/OptimizedImage.astro
import { Image } from 'astro:assets';
import myImage from '../images/my-image.jpg';
---

<Image
  src={myImage}
  alt="Description of image"
  width={800}
  height={600}
  format="webp"
/>

SEO Tools

Astro SEO:

npm install astro-seo
---
// layouts/BaseLayout.astro
import { SEO } from 'astro-seo';

const { title, description, image } = Astro.props;
---

<html>
  <head>
    <SEO
      title={title}
      description={description}
      openGraph={{
        basic: {
          title,
          type: "website",
          image,
        },
      }}
      twitter={{
        creator: "@yourusername",
      }}
    />
  </head>
  <body>
    <slot />
  </body>
</html>

Advanced Features

Astro offers several advanced features that enable sophisticated web applications while maintaining its performance benefits.

Content Collections

Content Collections provide a structured way to manage and query content in Astro projects. They offer type safety, validation, and a unified API for accessing content.

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

const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.date(),
    author: z.string(),
    image: z.object({
      url: z.string(),
      alt: z.string(),
    }),
  }),
});

export const collections = { blog };

Benefits of Content Collections:

Example Usage:

---
// pages/blog.astro
import { getCollection } from 'astro:content';

// Get all blog posts
const posts = await getCollection('blog');

// Filter and sort
const featuredPosts = posts
  .filter(post => post.data.featured)
  .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---

<html>
  <head>
    <title>Blog</title>
  </head>
  <body>
    <h1>Featured Posts</h1>
    <ul>
      {featuredPosts.map(post => (
        <li>
          <a href={`/blog/${post.slug}`}>{post.data.title}</a>
          <p>{post.data.description}</p>
        </li>
      ))}
    </ul>
  </body>
</html>

API Routes

Astro’s API routes enable server-side functionality in a simple, file-based approach. They support various HTTP methods and can be used for form handling, data fetching, and more.

// pages/api/subscribe.ts
export async function POST({ request }) {
  const data = await request.json();
  // Handle subscription logic
  return new Response(JSON.stringify({ success: true }), {
    status: 200,
  });
}

API Route Features:

Example with Authentication:

// pages/api/protected-data.ts
export async function GET({ request, locals }) {
  // Check authentication
  if (!locals.user) {
    return new Response(JSON.stringify({ error: "Unauthorized" }), {
      status: 401,
    });
  }

  // Fetch protected data
  const data = await fetchProtectedData(locals.user.id);

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

Middleware Support

Astro’s middleware system allows for request/response manipulation at various stages of the request lifecycle.

// middleware.ts
export function onRequest({ request, locals }, next) {
  // Add custom headers
  const response = await next();
  response.headers.set("X-Custom-Header", "value");
  return response;
}

Middleware Capabilities:

Example with Authentication:

// middleware.ts
export async function onRequest({ request, locals, redirect }, next) {
  // Check for authentication token
  const token = request.headers.get("Authorization");

  if (!token && request.url.includes("/dashboard")) {
    // Redirect to login if accessing protected route without token
    return redirect("/login");
  }

  if (token) {
    // Validate token and add user to locals
    try {
      const user = await validateToken(token);
      locals.user = user;
    } catch (error) {
      // Handle invalid token
      return new Response(JSON.stringify({ error: "Invalid token" }), {
        status: 401,
      });
    }
  }

  // Continue processing the request
  return next();
}

View Transitions

Astro’s View Transitions API enables smooth page transitions without full page reloads, similar to SPAs but with better performance.

---
// layouts/Layout.astro
import { ViewTransitions } from 'astro:transitions';
---

<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>

View Transition Features:

Example with Custom Transitions:

---
// components/Header.astro
---

<header transition:animate="slide">
  <nav>
    <a href="/">Home</a>
    <a href="/about">About</a>
    <a href="/blog">Blog</a>
  </nav>
</header>

<style>
  header {
    position: fixed;
    top: 0;
    width: 100%;
    background: white;
    z-index: 100;
  }
</style>

Image Optimization

Astro provides built-in image optimization with support for multiple formats and responsive images.

---
// components/OptimizedImage.astro
import { Image } from 'astro:assets';
import myImage from '../images/my-image.jpg';
---

<Image
  src={myImage}
  alt="Description of image"
  width={800}
  height={600}
  format="webp"
/>

Image Optimization Features:

Example with Responsive Images:

---
// components/ResponsiveImage.astro
import { Image } from 'astro:assets';
import myImage from '../images/my-image.jpg';
---

<Image
  src={myImage}
  alt="Description of image"
  widths={[240, 540, 720, 1080]}
  sizes="(max-width: 360px) 240px, (max-width: 720px) 540px, (max-width: 1080px) 720px, 1080px"
  format="webp"
  loading="lazy"
/>

Internationalization (i18n)

Astro supports internationalization through its routing system and content collections.

i18n Approaches:

Example Route-based i18n:

src/pages/
├── index.astro           → / (default language)
├── en/
│   ├── index.astro       → /en/
│   ├── about.astro       → /en/about
│   └── blog/
│       └── index.astro   → /en/blog
└── es/
    ├── index.astro       → /es/
    ├── about.astro       → /es/about
    └── blog/
        └── index.astro   → /es/blog

Example with Language Detection:

---
// middleware.ts
export function onRequest({ request, redirect }, next) {
  // Get preferred language from Accept-Language header
  const acceptLanguage = request.headers.get('Accept-Language') || 'en';
  const preferredLang = acceptLanguage.split(',')[0].split('-')[0];

  // Redirect to language-specific route if on root
  if (request.url.endsWith('/') && preferredLang !== 'en') {
    return redirect(`/${preferredLang}/`);
  }

  return next();
}

Server-Side Environment Variables

Astro provides secure handling of environment variables with different scopes for client and server code.

// .env
PUBLIC_API_URL=https://api.example.com
PRIVATE_API_KEY=secret_key_here

Environment Variable Features:

Example Usage:

---
// Server-side code (has access to all env variables)
const apiKey = import.meta.env.PRIVATE_API_KEY;
const apiUrl = import.meta.env.PUBLIC_API_URL;

// Fetch data using private API key
const data = await fetch(`${apiUrl}/data`, {
  headers: {
    'Authorization': `Bearer ${apiKey}`
  }
});
---

<html>
  <head>
    <title>Data Page</title>
  </head>
  <body>
    <h1>Data</h1>
    <!-- Client-side code can only access PUBLIC_ variables -->
    <script>
      console.log('API URL:', import.meta.env.PUBLIC_API_URL);
      // This would error: console.log(import.meta.env.PRIVATE_API_KEY);
    </script>
  </body>
</html>

Custom Directives

Astro’s directive system allows for powerful template manipulation with minimal code.

Built-in Directives:

Example with Custom Directive:

---
// components/Counter.astro
---

<div>
  <p>Count: <span id="count">0</span></p>
  <button id="increment">Increment</button>
</div>

<script>
  // This script will only run on the client
  const countEl = document.getElementById('count');
  const button = document.getElementById('increment');
  let count = 0;

  button.addEventListener('click', () => {
    count++;
    countEl.textContent = count;
  });
</script>

Performance Monitoring

Astro provides tools and integrations for monitoring application performance.

Performance Monitoring Features:

Example Integration:

// astro.config.mjs
import { defineConfig } from "astro/config";
import { partytown } from "@astrojs/partytown";

export default defineConfig({
  integrations: [
    partytown({
      config: {
        forward: ["dataLayer.push"], // Forward analytics events
      },
    }),
  ],
});
---
// layouts/BaseLayout.astro
---

<html>
  <head>
    <script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"></script>
    <script type="text/partytown">
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', 'G-XXXXXXXX');
    </script>
  </head>
  <body>
    <slot />
  </body>
</html>

Performance Comparison

Astro

Next.js

Gatsby

Hugo

Eleventy

Note: These metrics are approximate and may vary based on project size, configuration, and optimization level. The comparison focuses on default configurations and typical use cases.

Real-world Performance Metrics

Lighthouse Scores (Average)

Load Time Comparison

Bundle Size Impact

Deployment Strategies

Astro’s flexible rendering strategies make it compatible with a wide range of hosting platforms:

Static Hosting

SSR Hosting

Deployment Process

  1. Build the site: npm run build
  2. Preview locally: npm run preview
  3. Deploy to platform: Most platforms support automatic deployments from Git
  4. Configure environment variables: Set up any required environment variables
  5. Set up custom domains: Configure DNS and SSL certificates

Security Considerations

Built-in Security Features

Best Practices

Common Use Cases

Ideal For

Best Use Cases

  • Content-heavy websites
  • Documentation sites
  • Marketing websites
  • Portfolios
  • E-commerce product pages
  • Multi-language sites

Less Suitable For

Consider Alternatives

  • Highly interactive web applications
  • Real-time applications
  • Applications requiring complex client-side state
  • Full SPAs
  • Real-time dashboards

When to Choose Astro

When to Consider Alternatives

Migration Strategies

From Next.js

  1. Move pages to Astro’s file-based routing
  2. Convert React components to Astro components
  3. Update data fetching methods
  4. Migrate API routes
  5. Update build configuration

From Gatsby

  1. Convert GraphQL queries to Astro’s data fetching
  2. Migrate pages and components
  3. Update image handling
  4. Configure new build process
  5. Update deployment pipeline

Build Output and Routing

Astro’s build system and routing capabilities are designed to be intuitive and flexible, supporting both static site generation and server-side rendering with a unified approach.

File-Based Routing

Astro uses a file-based routing system similar to Next.js and other modern frameworks:

Basic Routing Examples:

src/pages/
├── index.astro         → / (home page)
├── about.astro         → /about
├── blog/
│   ├── index.astro     → /blog
│   ├── [slug].astro    → /blog/:slug (dynamic route)
│   └── category/
│       └── [category].astro → /blog/category/:category
└── products/
    ├── index.astro     → /products
    └── [id].astro      → /products/:id

Dynamic Routes:

---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
  const posts = await getPosts();
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post }
  }));
}

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

<html>
  <head>
    <title>{post.title}</title>
  </head>
  <body>
    <h1>{post.title}</h1>
    <div set:html={post.content} />
  </body>
</html>

Build Output Structure

Astro’s build process generates an optimized output structure:

Static Site Generation (SSG) Output:

dist/
├── assets/             → Optimized assets (images, CSS, JS)
│   ├── _astro/         → Astro-generated assets
│   └── images/         → Optimized images
├── _astro/             → Astro runtime files
├── index.html          → Home page
├── about.html          → About page
├── blog/               → Blog directory
│   ├── index.html      → Blog index
│   ├── post-1.html     → Individual blog post
│   └── post-2.html     → Individual blog post
└── products/           → Products directory
    ├── index.html      → Products index
    └── product-1.html  → Individual product page

Server-Side Rendering (SSR) Output:

For SSR, Astro generates a minimal set of files that work with the Astro runtime:

dist/
├── assets/             → Optimized assets
├── _astro/             → Astro runtime files
├── server/             → Server entry points
│   └── entry.mjs       → Main server entry point
└── client/             → Client-side JavaScript
    └── _astro/         → Hydration scripts

Hybrid Rendering Configuration

Astro allows configuring rendering strategies at multiple levels:

Page-Level Configuration:

---
// Static page (default)
---

<html>
  <head>
    <title>Static Page</title>
  </head>
  <body>
    <h1>This page is static</h1>
  </body>
</html>
---
// Server-rendered page
export const prerender = false;
---

<html>
  <head>
    <title>Dynamic Page</title>
  </head>
  <body>
    <h1>This page is server-rendered</h1>
  </body>
</html>

Route-Level Configuration:

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  output: "hybrid",
  // Configure specific routes for SSR
  server: {
    routes: [
      {
        path: "/api/*",
        prerender: false,
      },
      {
        path: "/dashboard/*",
        prerender: false,
      },
    ],
  },
});

Build Process and Optimization

Astro’s build process includes several optimization steps:

  1. Asset Optimization: Images, CSS, and JavaScript are optimized
  2. Code Splitting: JavaScript is split into smaller chunks
  3. Tree Shaking: Unused code is removed
  4. Minification: Code is minified for production
  5. Hash-based Caching: Assets use hash-based filenames for efficient caching

Build Configuration:

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  site: "https://example.com",
  base: "/",
  build: {
    assets: "_assets",
    server: "./dist/server",
    client: "./dist/client",
    format: "directory",
    clean: true,
  },
  vite: {
    build: {
      cssCodeSplit: true,
      minify: "terser",
      rollupOptions: {
        output: {
          manualChunks: {
            vendor: ["react", "react-dom"],
          },
        },
      },
    },
  },
});

Development Experience

Astro provides an excellent developer experience with modern tooling and workflows:

Project Setup

Astro provides a powerful CLI tool for creating and managing projects:

# Create a new Astro project
npm create astro@latest

# The CLI will guide you through setup options:
# - Project name
# - Template selection
# - TypeScript configuration
# - Package manager preference
# - Git initialization

Astro CLI Commands:

# Start development server
npx astro dev

# Build for production
npx astro build

# Preview production build
npx astro preview

# Add integrations
npx astro add react
npx astro add vue
npx astro add tailwind

# Generate TypeScript types
npx astro sync

# Check for outdated dependencies
npx astro check

Development Features

Project Structure

src/
├── components/     → Reusable UI components
├── layouts/        → Page layouts and templates
├── pages/          → Routes and pages
├── styles/         → Global styles and CSS
├── content/        → Content collections
├── utils/          → Utility functions
└── middleware.ts   → Server middleware

Limitations and Considerations

While Astro is a powerful framework, it’s important to understand its limitations:

Troubleshooting Guide

Common Issues

  1. Build failures
  2. Performance issues
  3. Deployment problems

Future of Astro

Upcoming Features

Community Growth

Conclusion

Astro represents a significant advancement in web development, offering a unique approach to building modern websites. Its focus on performance, developer experience, and flexibility makes it an excellent choice for content-focused websites while maintaining the ability to add interactivity where needed.

The framework’s ability to leverage the best aspects of both static and server-rendered content, combined with its component island architecture, positions it as a compelling solution for modern web development challenges.

References

Official Resources

Back to Blog