Building a Blog with Next.js and ButterCMS

What is ButterCMS?

ButterCMS is a cloud-based, API-first, headless CMS that enables you to rapidly build CMS-powered apps and websites. It has an easy-to-use dashboard for creating and managing content. It comes with a prebuilt, API-first blog engine that is SEO-optimized out-of-the-box, which enables you to quickly build and integrate a functional blog system for websites or apps. It seamlessly integrates into new and existing applications and is one of the best headless CMSs available out there.

Tutorial Prerequisites

Below are the main requirements to follow along with this tutorial:

  • Basic knowledge of Next.js
  • A code editor such as Sublime or Visual Studio Code
  • Font Awesome Icons
  • A ButterCMS trial account

Setting Up the Development Environment

Before you get started, open a terminal and navigate to where you want to create/store and run this command below to execute create-next-app to bootstrap the project.

$ npx create-next-app my-blog

Then, a prompt will display, requesting you to confirm some additional dependencies. After that, the command above will automatically scaffold a folder structure for our project.

The next step is to configure ButterCMS in our Next.js project with the ButterCMS SDK using the following command:

$ npm install butter --save
# OR
$ yarn add buttercms

After that, create a Butter account. Click this link to create your account. Then, to connect ButterCMS to your application, you will need to save the READ API Token key as an environment variable. Create a .env file at the root of your project directory and add your ButterCMS API token as an environment variable:

NEXT_PUBLIC_API_KEY=YOUR_API_KEY

You can get your READ API Token from your settings dashboard.

Setting Up ButterCMS

After creating a ButterCMS account, log in to your dashboard on the ButterCMS website. One of the most impressive features of ButterCMS is that it comes with a pre-built blog engine that easily integrates with your websites or applications using a well-structured API. The Blog Engine has an intuitive content setup designed for creating blog posts, so no setup is required from your end; it's a plug-and-play approach. To set up a blog, go into your dashboard and click on the Blog Posts from the left sidebar:

Once the new page opens, click on the New Post button at the top-right corner of the dashboard and add your blog post information on the page that opens up.

The ButterCMS blog engine comes with a WYSIWYG editor that allows you to easily create content for your blog site. It boasts several features that enable you to insert media files like videos and images, provides an easy-to-use text editor, and includes a section to set up all your blog metadata.

On the Blog Editing page, enter the title of your blog post, add content using the text editor, and include a cover image, author information, relevant tags, and important SEO data.

In the image above, the blog engine includes a section that allows you to add categories and tags, the blog summary, and a section for the blog's SEO. You can follow the steps above to add more blog posts. After inserting all the details of your blog, click the Publish button at the top-right corner of your dashboard to publish your post.

Integrating and Rendering Data From ButterCMS API

Don't forget that to connect your ButterCMS to your application, you must save your Read API key as an environment variable. After you're done preparing your content in ButterCMS, open your code editor and create a components folder in the src folder of your Next project, then create a Footer component and Layout component.

Open the Footer.js component and add the following code:

// components/Footer.js

const Footer = () => {
   return (
     <>
       <hr/>
       <div className="footer-container">
         <p>
           (c) {new Date().getFullYear()} David Adeneye
         </p>
         <div className="social_icons">
           <a
             href="https://twitter.com/davidadeneye"
             aria-label="Twitter"
             target="_blank"
           >
             <i className="fa-brands fa-twitter"></i>
           </a>
           <a
             href="https://github.com/daacode"
             aria-label="GitHub"
             target="_blank"
           >
             <i className="fa-brands fa-github"></i>
           </a>
           <a
             href="https://www.linkedin.com/in/david-adeneye-490027188/"
             aria-label="LinkedIn"
             target="_blank"
           >
             <i className="fa-brands fa-linkedin"></i>
           </a>
         </div>
       </div>
     </>
   )
 }
  export default Footer;
Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

The code above will add the footer section of the page, which will be displayed on all blog pages.

In this article, we will be making use of Font Awesome icons. To use Font Awesome, you can either install it into your project or use its CDN. You can add the CDN link to your _document.js file like this:

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <meta charSet="utf-8" />
        <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css"
          integrity="sha512-MV7K8+y+gLIBoVD59lQIYicR65iaqukzvf/nwasF0nqhPay5w/9lJmVM2hMDcnK1OnMGCdVK+iQrJ7lzPJQd1w=="
          crossOrigin="anonymous"
          referrerPolicy="no-referrer"
        />
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}

Then create the Layout component and add the Footer component to your page by wrapping your page content within it. As a result, the Layout component will accept children as props, allowing you to access the contents of your Next.js pages.

Open the Layout.js component and add the following code below:

// components/Layout.js
import Footer from './Footer';

const Layout = ({ children }) => {
 return (
   <>
     <main>{children}</main>
     <Footer />
   </>
 )
}

export default Layout;

Now you can integrate the Layout component into your pages by wrapping the page content in it.

Open _app.js file to add the following code:

// pages/_app.js

import '@/styles/globals.css';
import Layout from '../components/Layout';

export default function App({ Component, pageProps }) {
 return (
   <Layout>
     <Component {...pageProps} />
   </Layout>
 );
}

Then open your index.js file and add the following code:

import React, { useEffect, useState } from "react";
import Link from "next/link";

const read_token = process.env.NEXT_PUBLIC_API_KEY;

export default function Blog({ posts }) {
console.log(posts);

   return (
       <>
       <div className="nav-container">
           <div className="logo">
               <Link href="/">
               David Adeneye
               </Link>
           </div>

           <Link href="#" className="blog-menu">
               My Blog
           </Link>
           <a href="" className="cta-btn">Subscribe</a>
       </div>

       <div className="blogpost-container">
           {posts.data.map((post, index) => (
           <div className="blogpost-box" key={index}>
              
               <div className="blogpost-img">
                   <img src={post.featured_image} alt="Blog" width={300} height={300}/>
               </div>

              
               <div className="blogpost-text">
                   <span className="blogpost-tag">{post.tags[0].name}</span>
                   <a href={`/posts/${post.slug}`} className="blogpost-title">{post.title}</a>
                   <p>{post.summary}</p>
               </div>

               <div className="blogpost-footer">
                   <div>
                       <img src={post.author.profile_image} alt="avatar" />
                       <p className="blogpost-name">{
                         post.author.first_name + " " + post.author.last_name
                       }</p>
                   </div>

                   <Link href={`/posts/${post.slug}`} className="blogpost-link">
                       Read More
                   </Link>
               </div>

           </div>   
         
       ))}
       </div>
       </>
   );
}

// This function gets called at build time
export async function getStaticProps() {
   // Call an external API endpoint to get posts
   const res = await fetch(`https://api.buttercms.com/v2/posts?auth_token=${read_token}`)
   const posts = await res.json()

   // By returning { props: { posts } }, the Blog component
   // will receive `posts` as a prop at build time
   return {
     props: {
       posts,
     },
   }
 }

The code above fetches the post data from the ButterCMS API and displays them on the page.

Next, we need to create an individual blog page to which each blog post links using dynamic routes. Next.js enables you to generate pages with paths that depend on external data. To create dynamic routes for the blog posts, create a folder named posts inside the src/pages folder. Within the current folder, create a new file named [id].js and add the following code:

import React, { useEffect, useState } from "react";

const read_token = process.env.NEXT_PUBLIC_API_KEY;

export default function Post({ post }) {
  console.log(post.data.title)

 return(
     <>
       <div className="blog-container">
         <span className="blog-goBack"><a href="/blog">Go back</a></span>
     <div className="blog-wrap">
       <header>
         <p className="blog-date">Published {post.data.created}</p>
         <h1>{post.data.title}</h1>
         <div className="blog-tag">
           <div></div>
         </div>
       </header>
       <img src={post.data.featured_image} alt="cover" />
       <div className="blog-content" dangerouslySetInnerHTML={{__html: post.data.body }}></div>
     </div>
     </div>
     </>
 )
}

// This function gets called at build time
export async function getStaticPaths() {
   // Call an external API endpoint to get posts
   const res = await fetch(`https://api.buttercms.com/v2/posts?auth_token=${read_token}`)
   const posts = await res.json()
    // Get the paths we want to pre-render based on posts
   const paths = posts.data.map((post) => ({
     params: { id: post.slug },
   }))

    // We'll pre-render only these paths at build time.
   // { fallback: false } means other routes should 404.
   return { paths, fallback: false }
 }

 // This also gets called at build time
export async function getStaticProps({ params }) {
   // params contains the post `id`.
   // If the route is like /posts/1, then params.id is 1
   const res = await fetch(`https://api.buttercms.com/v2/posts/${params.id}?auth_token=${read_token}`)
   const post = await res.json()

 
    // Pass post data to the page via props
   return { props: { post } }
 }

In the code above, each page will be generated based on the params id: post.slug.

Note: Don't forget to copy the style/global.css file from the Github repo and add it into your own project file if you're following along with the tutorial.

Then run the command below to launch your app in dev mode:

Your blog page will look like this:

And each blog post page will look like this:

You can get the codebase for this tutorial here on Github.

Conclusion

In this article, we have walked through how to easily scaffold a blog website or integrate a blog engine into your website using Next.js and the ButterCMS blog engine. ButterCMS is such a great headless CMS for building blogs; it will save you the headache and time from creating a blog manually. You can easily set up a blog with any programming language or framework stack of your choice with ButterCMS.

To learn more about ButterCMS, you can check the ButterCMS documentation.

Last Updated: July 11th, 2024
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms