DEV Community

Alex Spinov
Alex Spinov

Posted on

Astro Has a Free API: The Content-First Framework That Ships Zero JavaScript by Default

Astro is a web framework that ships zero JavaScript by default. It renders HTML on the server and only hydrates interactive components — use React, Vue, Svelte, or Solid in the same project.

Why Astro?

  • Zero JS by default — ships pure HTML, CSS
  • Islands architecture — hydrate only interactive parts
  • Any UI framework — React + Vue + Svelte in one project
  • Content collections — type-safe Markdown/MDX content
  • Blazing fast — consistently tops web framework benchmarks

Quick Start

npm create astro@latest my-site
cd my-site
npm run dev  # http://localhost:4321
Enter fullscreen mode Exit fullscreen mode

Pages (File-Based Routing)

---
// src/pages/index.astro
const title = 'My Site';
const users = await fetch('https://api.example.com/users').then(r => r.json());
---
<html>
  <head><title>{title}</title></head>
  <body>
    <h1>{title}</h1>
    <ul>
      {users.map(user => <li>{user.name}</li>)}
    </ul>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Islands (Partial Hydration)

---
// src/pages/dashboard.astro
import ReactChart from '../components/Chart.tsx';  // React
import VueSearch from '../components/Search.vue';  // Vue
import SvelteCounter from '../components/Counter.svelte';  // Svelte
---
<html>
  <body>
    <h1>Dashboard</h1>

    <!-- Static by default (zero JS) -->
    <p>This paragraph ships no JavaScript.</p>

    <!-- Hydrate on load -->
    <ReactChart client:load data={chartData} />

    <!-- Hydrate when visible (lazy) -->
    <VueSearch client:visible />

    <!-- Hydrate on idle -->
    <SvelteCounter client:idle />

    <!-- Never hydrate (server-render only) -->
    <ReactChart data={chartData} />
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Content Collections

// 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()),
    draft: z.boolean().default(false),
  }),
});

export const collections = { blog };
Enter fullscreen mode Exit fullscreen mode
---
title: "My First Post"
date: 2026-03-29
tags: ["astro", "web"]
---

# My First Post
This is rendered as HTML at build time.
Enter fullscreen mode Exit fullscreen mode
---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';

const posts = await getCollection('blog', ({ data }) => !data.draft);
---
{posts.map(post => (
  <article>
    <h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
    <time>{post.data.date.toLocaleDateString()}</time>
  </article>
))}
Enter fullscreen mode Exit fullscreen mode

API Endpoints

// src/pages/api/users.ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = async () => {
  const users = await db.select().from(usersTable);
  return new Response(JSON.stringify(users), {
    headers: { 'Content-Type': 'application/json' },
  });
};

export const POST: APIRoute = async ({ request }) => {
  const body = await request.json();
  const user = await db.insert(usersTable).values(body).returning();
  return new Response(JSON.stringify(user), { status: 201 });
};
Enter fullscreen mode Exit fullscreen mode

Performance

Astro Next.js Remix
JS shipped (blog) 0 KB ~90 KB ~70 KB
Build time (1000 pages) 3s 15s 12s
Lighthouse score 100 92-98 95-99

Key Features

Feature Details
Rendering SSG, SSR, hybrid
JS shipped Zero by default, opt-in islands
Frameworks React, Vue, Svelte, Solid, Preact, Lit
Content Collections with Zod validation
Deploy Vercel, Netlify, Cloudflare, Node, Deno
i18n Built-in routing

Resources


Building content sites? Check my Apify actors or email spinov001@gmail.com.

Top comments (0)