Back to Blog
Tutorial 7 min read

Building an Astro Frontend

Connect your Astro SSR site to the Flare CMS API running on Cloudflare Workers.

JA
Jaime Aleman

Why Astro?

Astro is a natural fit for Flare CMS. Both run on Cloudflare โ€” the CMS as a Worker, the frontend as Pages. Astro's SSR mode with the @astrojs/cloudflare adapter means your pages are rendered at the edge, right next to your CMS data.

The result: sub-50ms page loads globally, with no client-side JavaScript required for content pages.

Setting Up the API Client

Create a typed API client at src/lib/flare.ts:

const API_URL = import.meta.env.PUBLIC_FLARE_API_URL || 'http://localhost:8787'

interface FlareResponse<T> {
  data: T
  meta: {
    count: number
    timestamp: string
    cache: { hit: boolean, source: string }
  }
}

export async function getBlogPosts(): Promise<BlogPost[]> {
  const response = await fetch(
    `${API_URL}/api/collections/blog-posts/content`
  )
  const result: FlareResponse<BlogPost[]> = await response.json()
  return result.data.filter(post => post.status === 'published')
}

Important: The PUBLIC_ Prefix

When running on Cloudflare Pages, environment variables must use the PUBLIC_ prefix for Astro to inline them at build time. Without it, import.meta.env.FLARE_API_URL will be undefined at runtime.

Set this in your wrangler.jsonc:

{
  "vars": {
    "PUBLIC_FLARE_API_URL": "https://admin.flarecms.dev"
  }
}

Creating Routes

Astro's file-based routing makes it simple. For a blog listing page:

// src/pages/blog/index.astro
---
import { getBlogPosts } from '../../lib/flare'
import Layout from '../../layouts/Layout.astro'

const posts = await getBlogPosts()
---

<Layout title="Blog">
  {posts.map(post => (
    <a href={`/blog/${post.data.slug}`}>
      <h2>{post.data.title}</h2>
    </a>
  ))}
</Layout>

Client-Side Filtering

One thing to be aware of: the Flare CMS API's filter parameters are currently broken (inherited from Flare CMS v2.8.0). Query parameters like ?filter[status][equals]=published are ignored server-side.

The workaround is simple โ€” fetch all content and filter in your API client:

return result.data.filter(post => post.status === 'published')

Deploying to Cloudflare Pages

Build and deploy in one command:

pnpm run build
wrangler pages deploy ./dist --branch main

Or let GitHub Actions handle it โ€” push to main and CI/CD does the rest.