DEV Community

Alex Spinov
Alex Spinov

Posted on

Astro 4 Has a Free API — Content-Driven Sites with Zero JavaScript by Default

Astro is the web framework for content-driven websites. It ships zero JavaScript by default, supports React/Vue/Svelte components, and has the best performance scores of any framework.

Why Astro?

  • Zero JS by default — HTML-first, JS only where you need it
  • Islands architecture — hydrate only interactive components
  • Any UI framework — React, Vue, Svelte, Solid — mix and match
  • Content Collections — type-safe Markdown/MDX content layer

Quick Start

npm create astro@latest mysite
cd mysite
npm run dev
Enter fullscreen mode Exit fullscreen mode

Pages

---
// src/pages/index.astro
const title = 'My Site';
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---

<html>
<head><title>{title}</title></head>
<body>
  <h1>{title}</h1>
  <ul>
    {posts.map(post => (
      <li><a href={`/posts/${post.slug}`}>{post.title}</a></li>
    ))}
  </ul>
</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
---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';

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

const { post } = Astro.props;
const { Content } = await post.render();
---

<h1>{post.data.title}</h1>
<time>{post.data.date.toLocaleDateString()}</time>
<Content />
Enter fullscreen mode Exit fullscreen mode

Islands (Partial Hydration)

---
import ReactCounter from '../components/Counter.jsx';
import VueSearch from '../components/Search.vue';
---

<!-- Static HTML - no JS -->
<h1>My Page</h1>
<p>This paragraph ships zero JavaScript.</p>

<!-- Interactive island - only this hydrates -->
<ReactCounter client:load />

<!-- Lazy island - hydrates when visible -->
<VueSearch client:visible />

<!-- Media query island -->
<MobileMenu client:media="(max-width: 768px)" />
Enter fullscreen mode Exit fullscreen mode

API Routes

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

export const GET: APIRoute = async () => {
  const users = await db.getUsers();
  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.createUser(body);
  return new Response(JSON.stringify(user), { status: 201 });
};
Enter fullscreen mode Exit fullscreen mode

View Transitions

---
import { ViewTransitions } from 'astro:transitions';
---

<html>
<head>
  <ViewTransitions />
</head>
<body>
  <nav>
    <a href="/">Home</a>
    <a href="/about">About</a>
  </nav>
  <main transition:animate="slide">
    <slot />
  </main>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Building a content site that needs fresh data? Check out my Apify actors for web scraping to power your Astro content, or email spinov001@gmail.com for custom solutions.

Astro, Next.js, or 11ty — which do you use for content sites? Share below!

Top comments (0)