Skip to content
Back to all articles

How to Render Blogs from API Instead of Hardcoding

TC
The Code Sage
· · 4 min read
How to Render Blogs from API Instead of Hardcoding

Why Move to API-Driven Blogs?

If your blog content is currently stored directly in src/content/blogs/*.md, it works great for small projects.

But when content grows, a remote API workflow can help you:

  • manage posts from a CMS or admin panel
  • update content without rebuilding every time
  • reuse the same content across web and mobile apps

In this guide, we will move from local hardcoded content to an API that returns markdown posts.

API Driven Blog Architecture

Current Pattern (What You Have Today)

Right now your pages likely use something like:

const posts = await getCollection("blogs");

That means Astro reads markdown files from your project directory at build time.

To switch to API mode, we will replace this with HTTP fetches.

Target API Response Shape

First, define a clean API contract. Your backend endpoint (for example /api/blogs) should return an array like this:

[
  {
    "slug": "intro-to-astro-2026-03-07",
    "title": "Intro to Astro",
    "description": "Getting started with Astro",
    "date": "2026-03-07",
    "author": "Raunak Manna",
    "image": "/images/astro/astro-cover.jpeg",
    "markdown": "## What Is Astro?\\nAstro is..."
  }
]

Step 1: Add an API Client Utility

Create src/lib/blogApi.ts:

export interface ApiBlog {
  slug: string;
  title: string;
  description: string;
  date: string;
  author?: string;
  image?: string;
  markdown: string;
}

const BLOG_API_URL = import.meta.env.BLOG_API_URL ?? "http://localhost:4000/api/blogs";

export const fetchAllBlogs = async (): Promise<ApiBlog[]> => {
  const response = await fetch(BLOG_API_URL);

  if (!response.ok) {
    throw new Error(`Failed to fetch blogs: ${response.status}`);
  }

  const blogs = (await response.json()) as ApiBlog[];
  return blogs;
};

Also add .env:

BLOG_API_URL=http://localhost:4000/api/blogs

Step 2: Update the Blog Listing Page

In src/pages/blog/index.astro, replace getCollection("blogs") logic with API fetching.

import BaseLayout from "../../layouts/BaseLayout.astro";
import { fetchAllBlogs } from "../../lib/blogApi";

const posts = await fetchAllBlogs();

const sortedPosts = [...posts].sort(
  (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
);

Then render cards from API fields (post.title, post.description, etc).

Step 3: Update Dynamic Blog Route

In src/pages/blog/[slug].astro, build static paths from API slugs:

import { fetchAllBlogs } from "../../lib/blogApi";

export async function getStaticPaths() {
  const posts = await fetchAllBlogs();

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

Now Astro can still generate static pages, but source content comes from API.

Step 4: Render Markdown from API

Because API returns markdown text, convert it to HTML before rendering.

One common option is marked:

npm install marked

Then in src/pages/blog/[slug].astro:

import { marked } from "marked";

const html = marked.parse(post.markdown);

And in template:

<article class="prose max-w-none" set:html={html} />

If your markdown comes from external users, sanitize HTML before rendering.

Step 5: Keep Existing UI Components

Good news: your current UI can remain mostly unchanged.

You only need to update data source and field access:

  • BlogCard.astro: use API object fields
  • AlsoSee.astro: use API object fields
  • index.astro and [slug].astro: use fetchAllBlogs()

This keeps design and UX intact while changing content delivery.

Step 6: Handle Fallbacks and Errors

Add defensive checks so pages do not break on API issues:

  • show empty state when no posts are returned
  • show fallback text when description/image is missing
  • fail build clearly when API is unreachable in production builds

Example:

let posts: ApiBlog[] = [];

try {
  posts = await fetchAllBlogs();
} catch (error) {
  console.error("Blog API fetch failed", error);
}

Optional: Hybrid Strategy (Best of Both)

A practical strategy is:

  • use API in production
  • keep local markdown as fallback in development

This gives reliability during local work while still supporting dynamic content workflows.

Final Migration Checklist

Use this checklist while migrating:

  • Create API utility (src/lib/blogApi.ts)
  • Add BLOG_API_URL in .env
  • Update listing page to use fetchAllBlogs()
  • Update dynamic route getStaticPaths() to use API slugs
  • Parse markdown string to HTML (marked or remark pipeline)
  • Keep existing UI components, only replace data source
  • Add fallback and error handling

Final Thoughts

Switching from hardcoded local markdown to API-driven markdown is mostly a data-source refactor, not a UI rewrite.

Your existing Astro pages and design system can stay almost the same. Once this is done, your blog becomes much easier to scale and manage.

If you want, the next step can be adding pagination and category filters directly from API response fields.

Share this article:

Continue Reading

Explore more articles you might find interesting