How to Build a Next.js Blog with Hyvor Blogs Headless CMS

Learn how to build a powerful, easily customisable Next.js headless blog using Hyvor Blogs headless CMS.

In this tutorial, we’ll explore how to build a Next.js blog with Hyvor Blogs: a Headless CMS for blogging.

Before going straight to our Next.js blog tutorial, here is a brief explanation for beginners about blogging with a headless CMS. If you already know this stuff, skip right away to the tutorial. Note that we are using App Router in Next.js in this tutorial.

What is a Headless CMS?

In simple words, a headless CMS is a content management system that lets you create, store, and display your content anywhere you want as the way you like it. If explained from another perspective, it is called “headless” because it does not have a head - the frontend part that your users will see on your next.js blog- so you have to plug any kind of a head you want. All you need is to fetch and render data from your headless CMS’s API and design your front end as you prefer.

Here is simply how your Next.js blog fetches data from the headless CMS.

Headless CMS architecture - Nextjs headless blog
Headless CMS architecture

Using a headless CMS, content creators/writers can benefit from a friendly interface to write and manage content, while developers can build amazing blog frontend and improve experiences using whatever tools and design practices they love. This is perfect for companies or enterprises that want their content to appear in lots of different places without having to copy-paste everything manually. Not only companies, but developers who love hassle-free content creation while building beautiful frontends can try this too.

Here is an infographic that explains the inner workings of a headless CMS in more detail than a traditional headless CMS.

Headless CMS vs Traditional CMS:  Build your nextjs headless blog with Hyvor Blogs headless CMS
Headless CMS vs Traditional CMS

Why Hyvor Blogs as your Headless CMS?

Hyvor Blogs is an all-in-one blogging platform that mainly focuses on blogging: no e-commerce, no portfolios, just blogging. As such, we offer bloggers the following features.

  • 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 be writing, editing, collaborating with your team managing all your blog content.

Hyvor Blogs Console

Now let’s move to the Next.js headless blog tutorial!

Step 1: Setup Next.js Blog Project

  1. Create a new Next.js project
    For this step, we are using the Automatic installation recommended by Next.js. Run the following command in your terminal.

1npx create-next-app@latest

Once you’ve completed this, you'll see the following prompts in your terminal.

1What is your project named? my-app
2Would you like to use TypeScript? No / Yes
3Would you like to use ESLint? No / Yes
4Would you like to use Tailwind CSS? No / Yes
5Would you like your code inside a `src/` directory? No / Yes
6Would you like to use App Router? (recommended) No / Yes
7Would you like to use Turbopack for `next dev`? No / Yes
8Would you like to customize the import alias (`@/*` by default)? No / Yes
9What import alias would you like configured? @/*

Answer them as necessary to set up your Next.js project with the required dependencies. Here are my answers to the prompts.

Installation Prompts Next.js - Next.js headless Blog
Installation Prompts
  1. Navigate to the project directory
    Move into the newly created project folder:

1cd my-app
  1. Start the development server

1npm run dev

This will open the project in the browser at http://localhost:3000 .

Next.js blog after running in the developement environment: build nextjs headless blog with Hyvor Blogs headless CMS
Next.js blog after running in the development environment

This is the project hierarchy for now.

1my-app/
2├── public/ # Public assets (images, icons, etc.)
3├── src/
4│ ├── app/ # App Router directory for routing
5│ │ ├── favicon.ico # Favicon for the app
6│ │ ├── globals.css # Global CSS for the app
7│ │ ├── layout.tsx # Root layout for all pages
8│ │ ├── page.tsx # Homepage - lists all posts
9├── README.md
10├── eslint.config.mjs
11├── next-env.d.ts
12├── next.config.ts
13├── node_modules
14├── package-lock.json
15├── package.json
16├── postcss.config.mjs
17├── tailwind.config.ts
18└── tsconfig.json

Step 2: Create your Blog at Hyvor Blogs

Go to the Hyvor Blogs console and create your blog. Once you have created your blog in Hyvor Blogs, we need the subdomain of the blog. To get your blog subdomain, go to Console -> Settings -> Hosting -> Subdomain.

Get your blog’s subdomain - nextjs headless blog tutorial
Get your blog’s subdomain

For this tutorial, I’m using headless-cms as my subdomain.

To work on building this Next.js blog with Hyvor Blogs headless CMS, we use Hyvor Blogs Data API. 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}, for example, https://blogs.hyvor.com/api/data/v0/headless-cms.

Now what is left there to do for us is fetching data from the Hyvor Blogs data API.

Let’s continue.

Step 3: Index page - Post loop page

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

  1. Goto your Next.js project.

    Get started by editing the src/app/page.tsx .

To fetch data from HB data API, we are using the built-in fetch function of Next.js which is the modern streamlined approach currently.

Initially, there is some dummy code in it, just remove it. After removing those in page.tsx file, we are using it to call Hyvor Blogs data API and fetch the posts data. To do that, use the base path and call Data API’s posts endpoint to fetch post data as shown below.

1export default async function HomePage() {
2 const subdomain = "headless-cms"; // Replace with your blog's subdomain
3 let posts = [];
4
5 try {
6 const res = await fetch(
7 `https://blogs.hyvor.com/api/data/v0/${subdomain}/posts`
8 );
9 if (!res.ok) throw new Error(`API Error: ${res.statusText}`);
10 const response = await res.json();
11 posts = response.data;
12 } catch (error) {
13 console.error("Failed to fetch posts:", error);
14 }
15 return (
16 <main>
17 <h1>Blog Posts</h1>
18 {posts.length > 0 ? (
19 <ul>
20 {posts.map((post: any) => (
21 <li key={post.id}>
22 <a href={`/${post.slug}`}>
23 <h2>{post.title}</h2>
24 </a>
25 <p>{post.description}</p>
26 <p>
27 Published on: {new Date(post.published_at).toLocaleDateString()}
28 </p>
29 </li>
30 ))}
31 </ul>
32 ) : (
33 <p>No posts found or an error occurred.</p>
34 )}
35 </main>
36 );
37}

Now this is what it looks like when you successfully fetch post data into your index page of this Next.js blog.

Next.js blog with post loop data
Index page with post-loop data

I have added some CSS to style this blog using Tailwind to make it more visually appealing. I mean, who loves to stare at an ugly blog, right?

next.js headless blog

Step 4: Post page

In this step, we are going to show post content when clicking on a particular post. Therefore when a post loads its content with a URL that looks like /[slug] .

  1. Create a new route folder named [slug] in the app folder: /app/[slug] .

  2. Then create a page.tsx in it: /app/[slug]/page.tsx .

  3. Fetch post content data in that page.tsx as follows.

1export default async function PostPage({
2 params,
3}: {
4 params: Promise<{ slug: string }>;
5}) {
6 const { slug } = await params;
7 const subdomain = "headless-cms"; // Replace with your subdomain
8
9 const res = await fetch(
10 `https://blogs.hyvor.com/api/data/v0/${subdomain}/post?slug=${slug}`
11 );
12
13 if (!res.ok) {
14 throw new Error(`API Error: ${res.statusText}`);
15 }
16
17 const post = await res.json();
18
19 return (
20 <main>
21 <h1>{post.title}</h1>
22 <p>{post.description}</p>
23 <p>Published on: {new Date(post.published_at).toLocaleDateString()}</p>
24 <div dangerouslySetInnerHTML={{ __html: post.content }} />
25 </main>
26 );
27}

Once completed, it will look like this.

Post page - Next.js blog with headless cms
Post Content

You can style the content of posts using CSS. Refer to our documentation. Refer to our content-style post which has all the content blocks.

Step 5: Pagination

Now we are going to set up pagination on the homepage of our Next.js blog.

To add pagination, we use the Pagination Object of Hyvor Blogs data API. To limit the number of posts we show on each page, we will be using limit and page params. The URLS of the pagination pages will be like /page/1 .

  1. Create a reusable Pagination component.

    You can add pagination buttons without using a component but that will bring lots of repetition of code in your project. Therefore to create a Pagination component, create a folder named components in your src folder: /src/components

  2. In the components folder create a file named Pagination.tsx . This is where we going to design our pagination component.

1interface PaginationProps {
2 pagePrev: number | null;
3 pageNext: number | null;
4 basePath?: string; // Optional base path for URLs
5}
6
7export default function Pagination({
8 pagePrev,
9 pageNext,
10 basePath = "/page",
11}: PaginationProps) {
12 return (
13 <nav>
14 {/* Previous Button */}
15 {pagePrev && (
16 <a
17 href={pagePrev === 1 ? "/" : `${basePath}/${pagePrev}`}
18 >
19 Previous
20 </a>
21 )}
22
23 {/* Next Button */}
24 {pageNext && (
25 <a
26 href={`${basePath}/${pageNext}`}
27 >
28 Next
29 </a>
30 )}
31 </nav>
32 );
33}
  1. Go back to /app/page.tsx file. We have to make some changes to limit the number of posts shown on each page. Changes are to make the root page / should correspond to /page/1 and limit the number of posts on a page.

1import Pagination from "@/components/Pagination";
2
3export default async function HomePage() {
4 const subdomain = "headless-cms"; // Replace with your blog's subdomain
5 const currentPage = 1; // Root URL corresponds to the first page
6 const postsPerPage = 3; // Number of posts per page
7
8 let posts = [];
9 let pagination = { page_prev: null, page_next: null };
10
11 try {
12 const res = await fetch(
13 `https://blogs.hyvor.com/api/data/v0/${subdomain}/posts?page=${currentPage}&limit=${postsPerPage}`
14 );
15 if (!res.ok) throw new Error(`API Error: ${res.statusText}`);
16 const response = await res.json();
17 posts = response.data;
18 pagination = response.pagination;
19 } catch (error) {
20 console.error("Failed to fetch posts:", error);
21 }
22
23 return (
24 <div>
25 <main>
26 <div>
27 <ul>
28 {posts.map((post: any) => (
29 <li key={post.id}>
30 <a
31 href={`/${post.slug}`}
32 >
33 {post.title}
34 </a>
35 <p>{post.description}</p>
36 <p>
37 Published on:{" "}
38 {new Date(post.published_at).toLocaleDateString()}
39 </p>
40 </li>
41 ))}
42 </ul>
43 </div>
44 <Pagination
45 pagePrev={pagination.page_prev}
46 pageNext={pagination.page_next}
47 />
48 </main>
49 </div>
50 );
51}
52
  1. Then create a folder in your app folder named page. Inside the page folder, create another folder named [pageNumber]. Inside the [pageNumber] folder, create a page.tsx file: /app/page/[pageNumber]/page.tsx .

1import Pagination from "../../../components/Pagination";
2
3export default async function PaginatedPage({
4 params,
5}: {
6 params: Promise<{ pageNumber: string }>;
7}) {
8 const subdomain = "headless-cms"; // Replace with your subdomain
9 const postsPerPage = 3;
10
11 // Await the params to access `pageNumber`
12 const { pageNumber } = await params;
13 const currentPage = parseInt(pageNumber);
14
15 let posts = [];
16 let pagination = { page_prev: null, page_next: null };
17
18 try {
19 const res = await fetch(
20 `https://blogs.hyvor.com/api/data/v0/${subdomain}/posts?page=${currentPage}&limit=${postsPerPage}`
21 );
22
23 if (!res.ok) throw new Error(`API Error: ${res.statusText}`);
24 const response = await res.json();
25 posts = response.data;
26 pagination = response.pagination;
27 } catch (error) {
28 console.error("Failed to fetch posts:", error);
29 }
30
31 return (
32 <div>
33 <main>
34 <div>
35 <ul>
36 {posts.map((post: any) => (
37 <li key={post.id}>
38 <a
39 href={`/${post.slug}`}
40 >
41 {post.title}
42 </a>
43 <p>{post.description}</p>
44 <p>
45 Published on:{" "}
46 {new Date(post.published_at).toLocaleDateString()}
47 </p>
48 </li>
49 ))}
50 </ul>
51 </div>
52 <Pagination
53 pagePrev={pagination.page_prev}
54 pageNext={pagination.page_next}
55 />
56 </main>
57 </div>
58 );
59}

Et Viola! We have just completed the pagination for our Next.js blog.

Next.js blog with pagination
Pagination

Step 6: Deploying

You can deploy your blog with Vercel or self-host it on a Node.js server, Docker image, or static HTML files. Here are the guides provided by Next.js for self-hosting. Follow the steps in those guides to deploy your Next.js blog.

Here is the link to our Next.js blog which we built in this tutorial deployed using Vercel.

And, here is the project’s GitHub repository for your reference.

GitHub - IshiniAvindya/nextjs-blog: A simple NextJs blog created with Hyvor Blogs Headless CMS
A simple NextJs blog created with Hyvor Blogs Headless CMS - IshiniAvindya/nextjs-blog
github.com
GitHub - IshiniAvindya/nextjs-blog: A simple NextJs blog created with Hyvor Blogs Headless CMS
GitHub Repo - Next.js blog with Headless CMS

Tips

  • To add an author’s page, you can use the authors endpoint which returns the authors object.

  • To add a tags page, you can use the tags endpoint which returns the tags object.

  • To do multi-language blogging, you can use language param and language object.

next.js headless blog

Yay! You've built a Next.js blog using a Headless CMS Hyvor Blogs.

Feel free to expand this project using our Data API endpoints and objects.

Let me know by commenting down below if you hit a dead-end and need assistance.

You can also check our headless CMS tutorials written for other front-end frameworks and libraries.

Comments

Published with Hyvor Blogs