Building a Static Site with Astro and Cosmic CMS

작성자

카테고리:

← 피드로
DEV Community · Tony Spiro · 2026-06-11 개발(SW)
Cover image for Building a Static Site with Astro and Cosmic CMS

Tony Spiro

Astro’s HTML-first philosophy is having a moment. A post on Hacker News this week about doubling user growth by going HTML-first cracked the top 10 with 383 points, and the replies were full of developers reconsidering their stacks.

The short version: ship less JavaScript, get faster sites, keep more visitors. Astro is purpose-built for exactly this. And when you pair it with Cosmic as your content layer, you get a stack that is fast to build, fast to run, and easy for non-developers to manage content without touching code.

This tutorial walks through building a production-ready blog with Astro and Cosmic from scratch.

Why Astro + Cosmic

Astro generates zero-JavaScript HTML by default. Every page is a static file until you opt into interactivity with islands. That means:

  • Lighthouse scores in the high 90s out of the box
  • No hydration overhead for content pages
  • Content editors can update copy in Cosmic without a developer involved

Cosmic delivers content via REST API and the @cosmicjs/sdk. Your Astro build fetches content at build time, bakes it into static HTML, and serves it from a CDN. Fast, structured, and zero runtime CMS overhead.

Prerequisites

  • Node.js 18+
  • A free Cosmic account (sign up here)
  • Familiarity with TypeScript basics

Step 1: Create Your Cosmic Bucket

Log in to Cosmic and create a new bucket. Then create a blog-posts Object Type with these metafields:

  • title (text)
  • slug (text, unique)
  • published_date (date)
  • teaser (textarea)
  • content (markdown)
  • image (file, image)

Add a few sample posts so you have content to render.

Step 2: Scaffold the Astro Project

npm create astro@latest cosmic-astro-blog
cd cosmic-astro-blog
npm install @cosmicjs/sdk

Enter fullscreen mode Exit fullscreen mode

Choose the “Empty” starter when prompted. We’ll wire up everything manually so you understand each piece.

Step 3: Initialize the Cosmic Client

Create src/lib/cosmic.ts:

import { createBucketClient } from '@cosmicjs/sdk';

export const cosmic = createBucketClient({
  bucketSlug: import.meta.env.COSMIC_BUCKET_SLUG,
  readKey: import.meta.env.COSMIC_READ_KEY,
});

Enter fullscreen mode Exit fullscreen mode

Add your credentials to .env:

COSMIC_BUCKET_SLUG=your-bucket-slug
COSMIC_READ_KEY=your-read-key

Enter fullscreen mode Exit fullscreen mode

You’ll find both values in your Cosmic bucket under Settings > API Keys.

Step 4: Fetch Posts at Build Time

Create src/pages/blog/index.astro:

---
import { cosmic } from '../../lib/cosmic';

const { objects: posts } = await cosmic.objects
  .find({ type: 'blog-posts' })
  .props('id,title,slug,metadata.teaser,metadata.published_date,metadata.image')
  .sort('-metadata.published_date');
---

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Blog</title>
  </head>
  <body>
    <h1>Blog</h1>
    <ul>
      {posts.map((post) => (
        <li>
          <a href={`/blog/${post.slug}`}>
            <h2>{post.title}</h2>
            <p>{post.metadata.teaser}</p>
          </a>
        </li>
      ))}
    </ul>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

Step 5: Generate Individual Post Pages

Create src/pages/blog/[slug].astro for dynamic routing:

---
import { cosmic } from '../../lib/cosmic';

export async function getStaticPaths() {
  const { objects: posts } = await cosmic.objects
    .find({ type: 'blog-posts' })
    .props('slug');

  return posts.map((post) => ({
    params: { slug: post.slug },
  }));
}

const { slug } = Astro.params;

const { object: post } = await cosmic.objects
  .findOne({ type: 'blog-posts', slug })
  .props('title,metadata');
---

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>{post.title}</title>
  </head>
  <body>
    <article>
      <h1>{post.title}</h1>
      {post.metadata.image && (
        <img
          src={`${post.metadata.image.imgix_url}?w=1200&auto=format`}
          alt={post.title}
          width="1200"
          height="630"
        />
      )}
      <div set:html={post.metadata.content} />
    </article>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

Astro’s getStaticPaths runs at build time, fetches all post slugs from Cosmic, and generates one HTML file per post. Zero runtime API calls.

Step 6: Handle Images with imgix

Cosmic stores all media on imgix, which means you get URL-based image transformations for free. Append query params to any image URL:

// Resize and auto-format for modern browsers
const heroUrl = `${post.metadata.image.imgix_url}?w=1200&h=630&fit=crop&auto=format`;

// Thumbnail
const thumbUrl = `${post.metadata.image.imgix_url}?w=400&h=300&fit=crop&auto=format`;

// WebP with fallback
const webpUrl = `${post.metadata.image.imgix_url}?w=800&fm=webp&q=80`;

Enter fullscreen mode Exit fullscreen mode

No image processing pipeline needed. imgix handles it at the CDN edge.

Step 7: Deploy to Vercel

npm install -g vercel
vercel

Enter fullscreen mode Exit fullscreen mode

Add your environment variables in the Vercel dashboard under Settings > Environment Variables:

  • COSMIC_BUCKET_SLUG
  • COSMIC_READ_KEY

Set up a deploy webhook in Cosmic (Settings > Webhooks) so every content publish triggers a new Vercel build. Your editors publish in Cosmic, the site rebuilds automatically, no developer involvement required.

Step 8: Enable Incremental Builds (Optional)

For larger sites, Astro’s content collections combined with Vercel’s ISR let you rebuild only changed pages. Configure in astro.config.mjs:

import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'hybrid',
  adapter: vercel(),
});

Enter fullscreen mode Exit fullscreen mode

Then mark individual pages for server-side rendering when you need fresh data, while keeping everything else static.

Performance Baseline

A default Astro + Cosmic blog with no optimization effort typically scores:

  • Performance: 95-100
  • Accessibility: 90+
  • Best Practices: 100
  • SEO: 90+

The main lever is images. Use imgix transforms and the loading="lazy" attribute on below-the-fold images and you’ll stay in the green across the board.

Adding an AI Agent (Optional)

Once your Cosmic bucket has content, you can add a Cosmic AI agent to manage it. From the Cosmic dashboard, connect an agent to your bucket. It can draft new posts, update metadata, generate featured images, and publish on a schedule. Your Astro site rebuilds automatically via webhook every time the agent publishes.

This is what the HTML-first HN crowd is discovering: a static frontend plus an API-first CMS is the right architecture for AI-assisted content workflows. The agent writes to the CMS API. The static site rebuilds. No CMS logic in your frontend, no CMS vendor in your runtime.

What’s Next

  • Add a search index with Pagefind (runs at build time, zero runtime JS)
  • Add RSS with @astrojs/rss
  • Add a sitemap with @astrojs/sitemap
  • Add an MCP server so AI assistants like Claude and Cursor can read and write your Cosmic content directly

Get Started

Free Cosmic plan includes 1 bucket, 1,000 objects, and 2 team members, enough to build and launch a full blog.

Start building for free or book a demo with Tony if you want to talk through your architecture.

Questions? Join the Cosmic Discord or check the Astro + Cosmic docs.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다