How to build a headless website with ButterCMS and NextJS
How to build a headless website with ButterCMS and NextJS

What is a headless website?

There is a trend in ecommerce and marketing websites to go "headless". The first natural question is: “What is Headless?”, especially in relation to e-commerce. Headless in the context of e-commerce means to separate the backend (content) from the frontend (code). By separating the backend and frontend, the two can now stand as separate entities, allowing content to be cached and statically served. Now that content is static, meaning it's not going to change, the code does not have to repeatedly look for it, directly resulting in an increase in site performance and page speed. 

In a monolithic or traditional website build, if the content is mistakenly changed, that issue will update on the live site as well. This is a security vulnerability and quality assurance problem that decoupling the frontend from the backend prevents. Once it's seen why headless website builds are better not only for performance but security as well, the next question is asked: how to build a headless site?

ButterCMS + NextJS

There are a few options to pick from when it comes to a Headless CMS. A headless site is achievable using CraftQL with CraftCMS, and other web software that support this, but for ease of use above all, we recommend ButterCMS. In a previous post we have explained why ButterCMS is king, and we still stand behind that. A ButterCMS and static site generator website can completely revitalize page performance and add another layer of security. This combined tech stack is a great way to benefit from a headless CMS, with a fast-loading frontend design. With custom components that can handle any data structure for copy and content, their easy-to-use API can be quickly connected with a React frontend. In this demo we’ll choose one of the most popular static site generators, Nextjs to pair with ButterCMS. One of the reasons we choose Nextjs aside from its popularity is its great documentation, and ease of use.

The first phase of development after creating an account on ButterCMS (which pre-fills your account with starter data for testing) is to create a codebase to render the data. The first step for this since we are using Next.js is to run npx create-next-app@latest . This creates a Next.js static site for you on https://localhost:3000 after running npm run dev, and gives a file structure for pages that we will be using.

Connect To ButterCMS

Now that we have a page rendering on a localhost, the next step is to connect to the Butter API. The most secure way to do this is to use environment variables, so to do this we create a .env file to hold the butter API key: 

NEXT_PUBLIC_BUTTER_CMS_API_KEY=paste_key_here

Then update the next.config.js file so that it can process environment variables (and allow images from the butter CDN as a valid source) like this: 

/** @type {import('next').NextConfig} */
const nextConfig = {  reactStrictMode: true,
 env: {    BASE_URL: process.env.BASE_URL,
 },  images: {
   domains: ['cdn.buttercms.com'],  },
}module.exports = nextConfig

Fetch ButterCMS Data

       Now that we have the API key safely secured at the code level, we can start to fetch content from the butterCMS.  Create a file lib/api.js, which will be the file that holds the functions that fetch data. 

import Butter from 'buttercms'
let butterconst postsPageSize = 1
try { butter = Butter(process.env.NEXT_PUBLIC_BUTTER_CMS_API_KEY)
} catch (e) { console.log(e)
} export async function getCategories() {
 try {    const response = await butter.category.list()
   return response?.data?.data  } catch (e) {
   throw e.response.data.detail  }
}  export async function getPostsData({ page, pageSize, tag, category } = { page: 1, pageSize: defaultPostCount },) {
 try {    const params = {
     page_size: pageSize || defaultPostCount,      page: page || 1,
   }    if (tag) {
     params.tag_slug = tag    }
   if (category) {      params.category_slug = category
   }    const response = await butter.post.list(params)
   return {      posts: response?.data?.data,
     prevPage: response?.data?.meta.previous_page,      nextPage: response?.data?.meta.next_page,
   }  } catch (e) {
   throw e.response.data.detail  }
}

With these functions to retrieve blog data, which is populated by our starter account, we can start pulling it into our code so we can display it on a page. Since we want this data statically, meaning that the code only fetches it at build time, we want to use Next.js’s getStaticProps() function. Inside it we can reference and call the functions we defined above in our lib/api.js like so:

export async function getStaticProps() {
   try {      const blogPosts = (await getPostsData()).posts
     const categories = await getCategories()      return { props: { posts: blogPosts, categories } }
   } catch (e) {      console.log('Could not get posts', e)
     return {        props: { posts: [], categories: [] },
     }    }
 return { props: { posts: [], categories: [] } }}
Render Data

With this function, it is now time to start rendering the data! We want to pass { posts, categories } as our props to the Home component in this same file, so we can map through them. ButterCMS utilizes data collections to allow for any type of information. In this example, we are demonstrating the use of their built-in blog content, but this same code structure can be used for products, custom pages, promotions, and more.

Altogether your code should look like this:

import { getPostsData, getCategories,} from '../lib/api'
export default function Home({ posts, categories }) {  console.log('posts, categories', posts, categories)
 return (    <section>
<div>{posts?.map((post, idx)=> {
return  (<p key={idx}>{post.title}</p>)})}
</div><div>
{categories?.map((category, idx)=> { return  (<p key={idx}>{category.name}</p>)
})}</div>
</section>  )
}export async function getStaticProps() {
   try {      const blogPosts = (await getPostsData()).posts
     const categories = await getCategories()      return { props: { posts: blogPosts, categories } }
   } catch (e) {      console.log('Could not get posts', e)
     return {        props: { posts: [], categories: [] },
     }    }
 return { props: { posts: [], categories: [] } }}

Conclusion

That’s it! That's all that's needed to statically fetch and render blog data from ButterCMS! The setup is not too complicated and only requires a few steps in order to connect. After this, the possibilities are endless for what can be built, while also building lightning-fast pages! Reference these ButterCMS and Nextjs Headless builds here for more examples on how to connect and deploy to Vercel: 

https://github.com/ButterCMS/react-cms-blog-with-next-js 

https://github.com/vercel/next.js/tree/canary/examples/cms-buttercms 

If you need React development, modern designs, or a ButterCMS partner you can rely on, look no further than Electric Enjin. We build custom solutions to help businesses integrate their existing content management systems, product data, customer base, and more. ButterCMS and Next.js is a great way to get started building a platform that provides a strong foundation for all of your current and future business needs. A headless website can offer extreme speed and performance improvements, which frequently results in better conversion rates. Contact us today for all design, development, and CRO services.

Looking for help with an RFP Website redesign? Contact us today to get started.

selected projects
selected projects
selected projects
Big ideas. Bigger results. Let’s make it happen. Get an instant quote today.

sf-required

Manage recurring validation states.
sf-form_input
sf-required
Field Input (Required)
sf-form_checkbox-field
sf-required
Checkbox (Required)
sf-form_radio-field
sf-required
Radio (Required)
sf-form_input-select
sf-required
Select (Required)
sf-form_input-date
is-icon-left-right
sf-required
sf-form-icon-left
sf-required
Date Input (Required)
This is an error tag
sf-form_input-error-wrapper
sf-required
Error Tag (Required)
sf-form-icon-right
sf-required
Icon on Input Right (Required)
sf-form-icon-left
sf-required
Icon on Input Left (Required)
sf-form-icon-right
is-text-area
sf-required
Icon on Input Right Text Area (Required)

sf-checked

Manage recurring checked radio & checkboxes states.
sf-form_checkbox-field
sf-checked
Checkbox (Checked)
sf-form_radio-field
sf-checked
Radio (Checked)

sf-focus

Manage recurring focusing for button, radio and checkbox states.
sf-form_radio-field
sf-focus
Radio (Focused)
sf-form_checkbox-field
sf-focus
Checkbox (Focused)

sf-hide

Manage hidden states.
sf-skeleton
sf-hide
Loader Box (Currently Hidden)

sf-await

Manage awaiting states.
sf-button-child
sf-await
sf-button-await-child
sf-await
Awaiting Status of Buttons
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Step {Current Slide}/{Max Slides}

Project Details

Start by telling us about your project.
This field is empty
Enter a valid email
Select one option
Next Slide

Key Project Requirements

Tell us about the essential features and requirements for your project.
This field is empty
Select one option
Next Slide

Timeline & Budget

Tell us about your project's timeline and budget.
This field is empty
Select one option
Next Slide

Additional Information

Anything else you'd like to add?
This field is empty
Select one option
End
Thank you! Your submission has been received! A representative will be in touch within 24 hours.
Oops! Something went wrong while submitting the form.