In a galaxy far, far away, there lived an individual who was known as the grill and breakfast guy. So he set out on an epic quest to become not just a Padawan Chef but a Master Chef, like those who came before him.
This is a guide to building a blog with Contentful while using the popular JavaScript framework Next.js. If you haven’t caught on already, it’s a Star Wars-themed cookbook filled with recipes handed down from multiple family members and friends. I’m hopeful that you’ll enjoy this journey and the build out of this app. May the FOOD be with you.
Episode I: The Phantom Starter
To get your project started you need to follow along with this amazing Next.js and Contentful starter guide crafted by Developer Advocate Brittany Walker. This walkthrough will help create your project, connect your Contentful account and deploy with Vercel. Following this starter you’ll be able to implement any of the additional features detailed below.
Episode II: Attack of the Webhooks
Now that you have your project created, running it locally, and connected to Contentful, let’s add a webhook. By connecting it to your Vercel project, it will notify Vercel every time you make change to your content and trigger Contentful to rebuild.
To add a webhook, go to settings in the Contentful web app and simply select “Add Webhook,” or you can use the Vercel template which we can do because this project is already deployed there. If you are taking the manual approach, here is the Webhooks documentation to help you along.
Episode III: Revenge of the Disqus
With any good blog, having the ability for readers to interact is a must. On multiple occasions I’ve used Disqus when adding a comments section. However, being new to Next.js I wasn’t certain where to add it and Disqus doesn’t have integration with Next.js within its tool. So here are the steps to adding Disqus.
First, sign up for an account at Disqus. They have a free plan which will get the job done. After signing up, you’ll need to click “I want to install Disqus on my site.” Follow the instructions by adding your website name and category. Again, you won’t see Next.js listed in the platforms section, so you will need to choose a manual universal code install. I will be providing an example of my code below.
Now you are in luck because there is Disqus React, an npm package for Disqus. In your terminal run:
npm install disqus-react
Next we are going to add a comment.js
file to the components folder and add the following code:
// /components/comment.js
import {DiscussionEmbed} from "disqus-react"
const DisqusComments = ({ post }) => {
const disqusShortname = "stahlwalkercookbook" // use your shortName from Disqus project
const disqusConfig = {
url: `https://cookblog.vercel.app/posts/${post.slug}`, // use your URL
identifier: post.slug, // Single post slug
title: post.title // Single post title
}
return (
<div>
<DiscussionEmbed
shortname={disqusShortname}
config={disqusConfig}
/>
</div>
)
}
export default DisqusComments;
Next we need to import the Disqus component into the [slug.js]
file located in the posts folder in your pages directory. Then it’s just a matter of finding where you’d like the comments to populate, I placed it at the end of my blog posts.
Episode IV: A New Social Share
I’m a fan of the ability to share your blog across whatever social networks you prefer. We’re going to use another npm package to add social sharing. In the terminal of your project run:
npm i next-share
From there you can add the following code to your [slug.js]
file. I added this component after my article, before the comments. You’ll need to update the URL to your site’s URL to grab all the individual posts. From here, people will be able to share not just your site but the blog post you’ve written. Below is an example:
// /pages/posts/[slug.js]
<div className="social">
<h2>Looks tasty, share with friends</h2>
<FacebookShareButton
url={`https://cookblog.vercel.app/posts/${post.slug}`} >
<FacebookIcon size={32} round />
</FacebookShareButton>
<TwitterShareButton
url={`https://cookblog.vercel.app/posts/${post.slug}`} >
<TwitterIcon size={32} round />
</TwitterShareButton>
</div>
Episode V: The Open Graphs Strike Back
Now to make sure everything looks great when links from your blog are shared, we’re going to work on updating the open graph tags. Open graph tags hold metadata that is used by search engines and social media platforms. This section focuses on updating your open graph tags and including Twitter and Facebook.
The file we are working with is located in the components directory and named meta.js
.
Make sure to customize yours to fit your project. For example, you’ll have to create a project in Facebook to obtain your fb:app_id
. With Twitter, I created a “summary_large_image” Twitter card, alternatively you can just put “summary” if you prefer the smaller content display.
One problem I ran into was that images were not properly displaying. This was because the image meta properties require a full image URL, so keep that in mind while updating.
Here is an example of what my meta tags look like:
// /components/meta.js
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@LucasStahl11" />
<meta name="twitter:creator" content="@LucasStahl11" />
<meta name="twitter:title" content="Stahlwalker Cookbook" />
<meta name="twitter:description" content="A blog dedicated to cooking up recipes for all those far far and away." />
<meta property="twitter:image" content="https://cookblog.vercel.app/images/starwarssocialfinal.jpg"/>
<meta property="og:title" content="Stahlwalker Cookbook"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="https://cookblog.vercel.app/"/>
<meta property="og:image" content="https://cookblog.vercel.app/images/starwarssocialfinal.jpg"/>
<meta property="fb:app_id" content="add Facebook id"/>
<meta property="og:description" content="A blog dedicated to cooking up recipes for all those far far and away."/>
To validate these are working correctly you can check Twitter’s validator and Facebook’s debugger.
Regarding your Favicon images, those can be located in the public directory. Here is a link to a Favicon generator to create the correct image sizes you need to replace the current Next.js image.
Episode VI: Return of the Categories
Having your blog posts is one thing but wouldn’t it be cool if you could sort them by a category tag? To do this, we need to add another field to the content model, which we can do in the “Content Model” section in the Contentful web app. In the “Post” content type, I added a new text field and selected the “short” and “list” options. This new field means you can start tagging your blog posts that later can be searched. In my cookbook, I categorized the recipes by cuisine type.
Once you have your content model updated, you need to update your GraphQL query in your code. In the api.js
file located in your “lib” directory, add the name of the new field to the query variable. I called my field foodCategory. If you are unsure how your query should look, this is where you can use the GraphQL playground app to make sure your queries are correct.
To install the GraphQL playground app. You’ll find this by navigating to “Apps” and clicking on “Manage Apps” in the top menu of your Contentful app. Simply click install and then this feature will be available to querying data within your project. Note, you’ll need to configure this app, so have your Contentful Preview API token available.
I wanted the category to be listed under the header on the blog post pages. I added foodCategory as a prop for my PostHeader component so that I could access the category within the PostHeader component.
// /pages/posts/[slug.js]
<PostHeader
title={post.title}
coverImage={post.coverImage}
foodCategory={post.foodCategory}
date={post.date}
author={post.author}
/>
If you would like users to be allowed to search by category, you could add an API search tool such as Algolia or Elasticsearch. You may also want to filter all blog posts by category and build that out. This is the first step in the process.
Episode VII: The CSS Awakens
I have zero experience with Tailwind and it comes pre-installed with this project. If you are like me and want to get back to the basics you can style with regular CSS. The styles folder is located in the public directory, you’ll just have to apply a class (and remember since we are using Next.js you will need to use “className”) to the containers you are looking to style.
Since this is a Star Wars-themed cookbook, I used Google Fonts to get a font that had the look and feel similar to what has been used in the films. I also used Font Awesome for social icons. I added both libraries to my app.js
Head tag.
Episode VIII: The Last Load More Button
At this point, my homepage was displaying all of my blog posts. I wanted to have more control over how many posts were listed. You have the option to add pagination, and if that interests you, I suggest taking a look at the following blog on Next.js pagination with Contentful. If you want something simple and sweet you can add a load more button. In this instance I went with the latter.
I modified the existing more-stories
component which allowed me to control how many posts to display and how many should appear when a user clicks to load more. I also used a prop to determine if the component should display a load more button since it is used in multiple places. Here is an example of what that looks like:
// /components/more-stories.js
import React, { useState } from 'react';
import PostPreview from '../components/post-preview'
export default function MoreStories({ posts, showMore }) {
const [ postNum, setPostNum] = useState(4); // Default number of posts displayed
function handleClick() {
setPostNum(prevPostNum => prevPostNum + 4)
}
posts.length + 1 === postNum ? showMore = false : showMore = true;
return (
<section>
<h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight">
More Recipes
</h2>
<div className="grid grid-cols-0 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32">
{posts.slice(0, postNum).map((post) => (
<PostPreview
key={post.slug}
title={post.title}
coverImage={post.coverImage}
date={post.date}
author={post.author}
slug={post.slug}
excerpt={post.excerpt}
/>
))}
</div>
{showMore && (
<button className="load-more" onClick={handleClick}>Load More</button>
)}
</section>
)
}
Once you have the code updated in your component, make sure to add the new prop, showMore
, to the component in the index.js
file:
// /pages/index.js
{morePosts.length > 0 && <MoreStories posts={morePosts} showMore={true} />}
And there you have it, your load more should be in effect!
Episode IX: Rise of the Stahlwalker Cookbook
And that is how I created my first Next.js project. We took a starter and built it out by adding apps, a commenting section, the ability to share, styled it with CSS rather than Tailwind and added a load more options for viewers to dive further into your blog posts. To see all of the final code, you can access it here.
The Next.js and Contentful starter guide really inspired me, so I’d love to see what you’ve built with it as well! I’m planning on continuing to add features to my new blog but if you’d like to check it out, here is the live version of the Stahlwalker Cookbook project.