Rendering Contentful in Rails and Next.js
A couple of code snippets to help you render your Contentful content in Rails or Next.js.
Recently, I moved the Bento marketing site from ye ol' reliable Rails to Next.js.
One of the things that made that process super smooth was I have been using Contentful to manage my blog posts in Rails. I did this because I couldn't be bothered building a CMS and I love the flexibility that Contentful provides.
For the migration, it ended up being super easy.
I just fetched the content in Next.js similarly to how I did it in Rails and I was off to the races. No need to mess with redirects or manually port content. It #justworked.
Below is two very simplified versions of my implementation that you can use yourself.
Rails Guide
Start by installing the gem.
gem install contentful
Then, create a controller for your blog posts. For Bento, I just wanted to have everything under /posts so created a PostController and added the route.
class PostsController < ApplicationController
before_action :init_contentful
def index
@posts = @client.entries(content_type: "blogPost", include: 2)
end
def show
@post = @client.entries(content_type: 'blogPost', include: 10, 'fields.slug[match]' => params[:id]).first
end
private
def init_contentful
@client ||= Contentful::Client.new(
access_token: ENV['CONTENTFUL_ACCESS_TOKEN'],
space: ENV['CONTENTFUL_SPACE_ID'],
dynamic_entries: :auto,
raise_errors: false
)
end
end
Once you add your keys and content you should be done! Nice!
You can now render your blog posts in your Rails views however you like.
Next.js
In Next.js it's basically the same process but a little more involved.
First, install contentful
.
yarn add contentful
Then create a new utility function to help you fetch all posts.
// utils/contentfulPosts.tsx
const space = process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID
const accessToken = process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN
const client = require('contentful').createClient({
space: space,
accessToken: accessToken,
})
export async function fetchEntries() {
const entries = await client.getEntries({ content_type: 'blogPost', fields: {}, include: 3 })
if (entries.items) return entries.items
console.log(`Error getting Entries for ${contentType.name}.`)
}
export default { fetchEntries }
Now we can spin up a new page to render all the posts.
// pages/posts.tsx
import { fetchEntries } from '@utils/contentfulPosts'
export default function Home({ items }) {
return (
<>
{items.map(item => (
<div key={item.sys.id}>
<h1>{item.fields.title}</h1>
<p>{item.fields.body}</p>
</div>
))}
</>
)
}
export async function getStaticProps() {
const res = await fetchEntries()
const posts = await res.map((p) => {
return p.fields
})
return {
props: {
items: posts,
},
}
}
For rendering individual content, I took a slightly different approach to the above.
First, I created a new utility function to initiate the client.
// utils/contentfulClient.tsx
import { createClient } from "contentful";
const client = createClient({
space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN,
});
export default client;
Then I create a new page to render a single post.
// pages/[slug].tsx
import client from '@utils/contentfulClient';
export async function getStaticPaths() {
const res = await client.getEntries({
content_type: "blogPost",
});
const paths = res.items.map((item) => {
return {
params: { slug: item.fields.slug },
};
});
return {
paths,
// if we go to path that does not exist, show 404 page
fallback: false,
};
}
export async function getStaticProps({ params }) {
const slug = params.slug;
const res = await client.getEntries({
content_type: "blogPost",
"fields.slug": slug,
});
const data = await res.items;
const post = data[0];
const content = post.fields.body
return {
props: {
post,
content,
},
};
}
export default function Post({ post, content }) {
return (
<>
<h1>{post.title}</h1>
</>
)
}
And you're done! Content should be loading from Contentful at the same paths as before.
Subscribe to my personal updates
Get emails from me about building software, marketing, and things I've learned building products on the web. Occasionally, a quiet announcement or two.