Build an Astro Blog with Headless CMS

Are you looking to build an Astro blog with a headless CMS? In this Astro blog tutorial, I’m going to walk you through all the steps of building an Astro blog using Hyvor Blogs as the headless CMS.

Are you looking to build an Astro blog with a headless CMS? In this Astro blog tutorial, I’m going to walk you through all the steps of building an Astro blog using Hyvor Blogs as the headless CMS.

First of all, let’s talk a little bit about headless CMSs.

What is a Headless CMS, and why is it used?

A headless CMS is a content management system that focuses on content management and data delivery through API. In a headless CMS, the "head" (the "head" refers to the front-end or presentation layer of a website or app) is decoupled, meaning you can use any front end to present your content, giving you maximum flexibility.

Here is a simple infographic of how headless CMS works.

Build an Astro Blog with Headless CMS
Headless CMS - The Architecture

Here are the major reasons why you should use a headless CMS.

  1. Flexibility: Can be built with any frontend technology.

  2. Scalability: Easy to handle on multiple platforms (web, mobile, AR/VR, etc.).

  3. Future-Proof: Frontend and backend are independent, making updates easier.

  4. Performance: Optimized delivery through APIs.

  5. Custom Workflows: Developers have full control over design and functionality.

Why Hyvor Blogs?

Hyvor Blogs is an all-in-one headless CMS tailored for blogging. It provides you the perfect space to write, and manage your blog posts, including features like memberships, comments, and newsletters with Hyvor Talk, and offers an easy API approach to integrate your blog with any front-end framework or platform.

As a Hyvor Blogs user, you get to experience the following features, especially from Hyvor Blogs. For example,

  • Multi-language blogging including RTL language blogging.

  • User-friendly, team-friendly console with markdown-friendly rich text editor

  • Data API for headless cms

  • Hosting privileges: subdomain, custom domain, sub-directory, or anywhere you like.

  • Developer-friendly: console API, delivery API, webhooks, etc.

  • Fast blogging and so much more.

This is the Hyvor Blogs console. This is where you will write, edit, collaborate with your team, and manage all your blog content.

Hyvor Blogs console

Astro by itself is a light, fast static site generator. On the other hand, Hyvor Blogs is fast and easy to use. Together, Astro and Hyvor Blogs enable you to create a high-performing, static/dynamic astro blog where content management and front-end customization work seamlessly. This duo will be a perfect integration for your headless CMS.

Now, let’s move to our Astro-react headless blog tutorial.

Step 1: Create an Astro Blog Project

There are several methods to create an Astro blog project according to Astro documentation. The create astro CLI command is the fastest way to start a new Astro project from scratch. It will walk you through every step of setting up your new Astro project and allow you to choose from a few different official starter templates. There is manual installation too. We are using create astro CLI command which can be run anywhere.

  1. Create your new Astro blog project

1npm create astro@latest

Proceed with “y” to install the necessary dependencies. Then, answer the prompts appearing. Here are my answers to those prompts.

Although I have chosen “A basic, minimal starter” as the start of my Astro blog project, you can choose any other option, such as using an Astro blog template.

astro blog project setup wizard
Answers to Astro Installation Prompts
  1. Start the dev server: This is to run the blog in development mode.

1npm run dev

It will run your blog locally at http://localhost:4321 . It will be something like this.

Astro blog in dev mode initially -  Astro blog build with Hyvor Blogs headless cms - Astro blog template
Astro blog in dev mode

We’ve completed the first step of building an Astro blog using headless CMS.

This will be the project file hierarchy initially.

1├── README.md
2├── astro.config.mjs
3├── node_modules
4├── package-lock.json
5├── package.json
6├── public
7│ └── favicon.svg
8├── src
9│ ├── assets
10│ ├── components
11│ │ └── Welcome.astro
12│ ├── layouts
13│ │ └── Layout.astro
14│ └── pages
15│ └── index.astro
16└── tsconfig.json

Step 2: Create your Blog at Hyvor Blogs

We have to create a blog space to start content writing and content management for our blog. To do that, go to the Hyvor Blogs console and create your blog.

get your blo subdomain  - Astro blog build with Hyvor Blogs headless cms-

Once completed Go to Console -> Settings -> Hosting -> Subdomain. Copy your blog’s subdomain.

For this tutorial, my blog subdomain is headless-cms .

For our next steps, we are going to use Hyvor Blogs Data API to fetch and render blog data. Here is the Data API documentation of Hyvor Blogs for your reference.

The Hyvor Blogs Data API provides responses in JSON format, with all endpoints using the HTTP GET method. The base path for the API is https://blogs.hyvor.com/api/data/v0/{subdomain}, in this case, https://blogs.hyvor.com/api/data/v0/headless-cms.

Let’s continue.

Step 3: Index page/homepage

This is the step in which we create the blog's homepage/index page, where you showcase all its posts, including featured posts, pinned posts, etc.

Remove all the dummy codes that initially existed right after you set up the project. You can remove the Welcome.astro file in the components folder.

First, let’s write a function to fetch post data from the Hyvor Blogs Data API.

  1. Create a folder named utils under the src folder and create a file named fetchPosts.ts inside the utils folder.

1const BASE_URL = "https://blogs.hyvor.com/api/data/v0";
2const SUBDOMAIN = "headless-cms";
3
4export async function fetchPosts() {
5 const API_URL = `${BASE_URL}/${SUBDOMAIN}/posts`;
6
7 try {
8 const response = await fetch(API_URL);
9
10 if (!response.ok) {
11 throw new Error("Failed to fetch posts from Hyvor Blogs API");
12 }
13
14 const data = await response.json();
15 return data;
16 } catch (error) {
17 console.error(error);
18 }
19}

Next, create an Astro component to show post data.

  1. Create a file named PostList.astro in the components folder.

1---
2export interface Post {
3 id: number;
4 slug: string;
5 title: string;
6 description: string;
7}
8
9interface Props {
10 posts: Post[];
11}
12
13const { posts } = Astro.props;
14---
15
16<ul>
17 {posts.map((post) => (
18 <li>
19 <div>
20 <a href={`/${post.slug}`}>{post.title}</a>
21 <p>{post.description}</p>
22 </div>
23 </li>
24 ))}
25</ul>
  1. Now go to index.astro file and import the PostList component we’ve just created.

1---
2import PostList from '../components/PostList.astro';
3import Layout from '../layouts/Layout.astro';
4import { fetchPosts } from '../utils/fetchPosts';
5const posts = await fetchPosts();
6---
7
8<Layout>
9<h1>Astro blog with Hyvor Blogs</h1>
10{posts ? <PostList posts={posts.data} /> : <p>No posts available</p>}
11</Layout>

Save the changes and this is what we’ll see in our development environment.

Index page of Astro blog created with Hyvor Blogs Headless CMS
Index page of the Astro blog

Step 4: Post Page

In this step, we are going to display post data when clicking on a particular post on the index page we created. For example, the post content will be shown under the URL /post-slug .

This is where we use Astro routing.

  1. Create a file called [slug].astro for dynamic routes in our page folder.

  2. Go to fetchPosts.ts and let’s write a new function (fetchPost) to fetch post data by slug , using the single endpoint post.

In Astro, getStaticPaths is a special function that enables Static Site Generation (sSSG) for dynamic routes, like blog posts. I suggest you give it a reference about getStaticPaths on Astro docs.

1// function to fetch a single post
2export const fetchPost = async (slug: string) => {
3 const API_URL = `${BASE_URL}/${SUBDOMAIN}/post?slug=${slug}`;
4
5 try {
6 const response = await fetch(API_URL);
7 if (!response.ok) {
8 throw new Error(`Failed to fetch post for slug: ${slug}`);
9 }
10
11 const data = await response.json();
12 return data;
13 } catch (error) {
14 console.error("Error fetching post:", error);
15 return null;
16 }
17};
18
19export const getStaticPaths = async () => {
20 const API_URL = `${BASE_URL}/${SUBDOMAIN}/posts`;
21
22 try {
23 const response = await fetch(API_URL);
24 const postsData = await response.json();
25
26 return postsData.data.map((post: { slug: string }) => ({
27 params: { slug: post.slug },
28 }));
29 } catch (error) {
30 console.error("Error fetching static paths:", error);
31 return [];
32 }
33};

Now we are going to create a component to display posts.

  1. Create a file named Post.astro in the components folder.

1---
2import Layout from '../layouts/Layout.astro';
3import { fetchPost } from '../utils/fetchPosts';
4
5// Get the slug from Astro.params
6const { slug } = Astro.params;
7
8if (!slug) {
9 throw new Error('Slug is required');
10}
11
12// Fetch the post data
13const post = await fetchPost(slug);
14
15if (!post) {
16 throw new Error('Post not found');
17}
18---
19
20<Layout title={post.title}>
21 <article>
22 <h1>{post.title}</h1>
23 <p>{post.description}</p>
24 <div set:html={post.content || "<p>This post does not have any content.</p>"} />
25 </article>
26</Layout>
  1. Go back to [slug].astro and import the component we created above.

1---
2// Import the Post component
3import Post from '../components/Post.astro';
4import Layout from '../layouts/Layout.astro';
5
6// Import static path utility
7import { getStaticPaths } from '../utils/fetchPosts';
8
9// Export static paths
10export { getStaticPaths };
11---
12
13<Layout>
14 <Post />
15</Layout>

That’s it. Here is how it looks in the development environment.

POST PAGE - build astro blog with headless cms- astro routing
Post page

Well, I haven’t added CSS for the post content.

build astro blog using headless cms hyvor blogs

In this step, we are going to add pagination to our Astro blog. In this tutorial, paginated posts will be shown in URLs like /page/[page number] : /page/2 .

  1. Create a folder named page inside pages folder: /src/pages/page .

  2. We will have to use pagination buttons in our index.astro and [page].astro files, and if you extend this project by adding author pages and tag pages, you will need to repeat the pagination UI codes everywhere, thus we are creating a component for that: /components/Pagination.astro.

1---
2interface PaginationProps {
3 prev?: string;
4 next?: string;
5}
6
7const { prev, next }: PaginationProps = Astro.props;
8---
9
10<nav>
11 {prev && (
12 <a href={prev
13 }>
14 Previous
15 </a>
16 )}
17 {next &&
18 <a href = {next}>
19 Next
20 </a>
21 }
22</nav>
  1. In the page folder create a file named [page].astro for dynamic routes: /src/pages/page/[page].astro.

We use the paginate() function provided by Astro to handle pagination logic, including calculating the number of pages and assigning posts to each page.

1---
2import Pagination from '../../components/Pagination.astro';
3import PostList from '../../components/PostList.astro';
4import Layout from '../../layouts/Layout.astro';
5
6
7export async function getStaticPaths({ paginate }: { paginate: any }) {
8 const BASE_URL = "https://blogs.hyvor.com/api/data/v0"; // Hyvor Blogs API base path
9 const SUBDOMAIN = "headless-cms"; // Replace with your blog's subdomain
10 const POSTS_PER_PAGE = 3;
11 const API_URL = `${BASE_URL}/${SUBDOMAIN}/posts`;
12
13 const response = await fetch(API_URL);
14 const postsData = await response.json();
15
16 if (!postsData || !postsData.data) {
17 throw new Error('Failed to fetch posts for pagination');
18 }
19
20 return paginate(postsData.data, { pageSize: POSTS_PER_PAGE });
21}
22
23interface Page {
24 currentPage: number;
25 data: { id: number; slug: string; title: string; description: string }[];
26 url: { prev?: string; next?: string };
27 lastPage: number;
28}
29
30const { page }: { page: Page } = Astro.props;
31---
32
33<Layout title={`Page ${page.currentPage}`}>
34 <PostList posts={page.data} />
35 <Pagination prev={page.url.prev} next={page.url.next} />
36</Layout>
  1. Now go back to the index.astro We have to make some changes to it only to get posts for 1 page.

1import Pagination from '../components/Pagination.astro';
2import PostList from '../components/PostList.astro';
3import Layout from '../layouts/Layout.astro';
4const BASE_URL = "https://blogs.hyvor.com/api/data/v0";
5const SUBDOMAIN = "headless-cms";
6const POSTS_PER_PAGE = 3;
7
8const API_URL = `${BASE_URL}/${SUBDOMAIN}/posts`;
9const response = await fetch(API_URL);
10const postsData = await response.json();
11
12if (!postsData || !postsData.data) {
13 throw new Error('Failed to fetch posts');
14}
15
16const posts = postsData.data.slice(0, POSTS_PER_PAGE); // Get only the first page of posts
17---
18
19<Layout>
20 <PostList posts={posts} />
21 <Pagination prev={null} next="/page/2" />
22</Layout>

And, that’s it! We’ve completed adding pagination to our Astro blog.

Astro blog pagination - Hyvor Blogs headless cms
Simple Pagination

Step 6: Deployment of your Astro Blog

You can simply build your blog by running the following command on your terminal.

1npm run build

By default, the build output will be placed at dist/. This location can be changed using the outDir configuration option.

After building your blog successfully, you can deploy it using a hosting provider you like. Astro supports many hosting providers such as Cloudflare pages, Netlify, Github Pages, Netlify, AWS, Google Cloud and so much more whether your blog is Static or Server-Side Rendered (SSR).

Tips

  1. To add an author's page, utilize the /authors endpoint, which provides detailed information about the authors.

  2. To create a tags page, use the /tags endpoint to retrieve the tags object.

  3. For multi-language blogging, leverage the language parameter and language-specific objects to customize content for different audiences.

Et voila! You've successfully built an Astro blog powered by the Hyvor Blogs Headless CMS.

Take this project further by integrating additional features using our Data API endpoints and objects.

If you encounter any challenges or need help, feel free to reach out by leaving a comment below.

Explore our tutorials on creating headless CMS blogs with other front-end frameworks and libraries: Sveltekit, Next.js, etc.

If a headless setup isn’t your preference, you can always build a traditional Astro blog with Hyvor Blogs.

Happy blogging!

Comments

Published with Hyvor Blogs