Developing a Jekyll Strapi blog powered with plugins for SEO, internationalization and commenting
Author: Mark Munyaka
Plugins are a powerful tool to customize and extend the functionality of your Strapi app. The Strapi Market is the official marketplace to find plugins comprising more than sixty (60) plugins to choose from. These plugins were developed by the Strapi team, technology and solution partners, and individual community members. You can find plugins that integrate Strapi with other tools, such as Sentry, Mux, and Moesif, and plugins that extend Strapi features, such as SEO, content versioning, comments moderation, internationalization, database configuration, sitemap, and more.
This tutorial aims to show how you can enhance functionality in your Strapi blog by making use of 3 plugins from the Strapi Market. You will use the official Strapi SEO plugin to make your content more SEO friendly, the Internationalization (i18n) plugin to help distribute the content in different languages, and the Comments plugin to moderate comments on your blog. You will start by creating a simple static blog with Strapi as the backend and Jekyll for the frontend. Then, you will install the plugins and show their use cases. At the end of this tutorial, you should know how to install and use plugins from the Strapi Market to power up your app.
Why Strapi?
Strapi is the leading open-source headless Content Management System (CMS) with over 45000 Github stars. It saves its users API development time through an advanced and feature rich API CMS. It makes it simpler to access content through provision of fast and secure APIs built in an efficient and scalable manner.
Strapi is open source; the entire codebase is available and maintained on Github by thousands of contributors. It’s highly customizable with a beautiful admin panel that anyone can use and customize to their taste. The API as well can be customized to one’s needs. Content management functionality can be extended through the use of plugins available through the Strapi Market.
The APIs you generate using Strapi can be consumed using any frontend client from vanilla HTML, CSS and Javascript sites to frontend frameworks like React, Vue, Angular etc. to mobile apps and even IoT devices using REST or GraphQL.
Another reason why Strapi is so compelling is that you can self-host it. That’s right, you can host on your own servers or any other hosting providers. These include cloud platforms such as 21YunBox, Render, Heroku, AWS, Azure and others or locally using Docker
What is Strapi Market?
Strapi Market is the official marketplace that lists all the plugins created by the Strapi team, technology and solution partners, as well as individual community members. It is the trusted place where people can access all the submitted plugins. Anyone is free to develop and submit a plugin to the marketplace. All submitted plugins go through a Review Process. The verified plugins are highlighted and listed on the marketplace so that every plugin creator’s work is showcased and acknowledged.
What is Jekyll?
Jekyll is a static site generator (SSG). It takes text written in your favorite markup language and uses layouts to create a static website. You can tweak the site’s look and feel, URLs, the data displayed on the page, and more. Some of the features include permalinks, categories, pages, templates and custom layouts.
Jekyll was developed using the Ruby language and uses Liquid as its templating language. It is also the SSG powering Github Pages.
Goals
In this tutorial, we will build a simple static blog using Jekyll and Strapi
and install and showcase the functionality of the following plugins:
Prerequisites
To complete the tutorial, the following prerequisites need to be installed:
Backend
Node.js: Node.js is the server-side runtime environment used to run our Strapi application. Only LTS versions are supported (minimum v12). You can download from the Downloads | Node.js page and install it using the provided instructions. To check if Node.js has been installed correctly, open up your command line and type
node -v
.npm (minimum v6) or yarn: This is the package manager for Node.js to run the CLI scripts. We will use this to install Strapi. npm comes bundled with Node.js. To check your version, enter
npm -v
in your shell. If you prefer yarn, follow the instructions on the Installation | Yarn - Package Manager page to install it. To check if yarn is installed, typeyarn -v
in your shell after installation.
Frontend
- Ruby v2.5.0 or higher, including all development headers (check ruby installation using
ruby -v
) - RubyGems
- GCC and Make
- Jekyll (minimum v4)
NOTE:
Knowledge of the Ruby language and the Liquid templating language is not necessary but can be helpful to understand and complete the tutorial.
You can find the instructions on how to install Jekyll on the installation page for the Jekyll website.
Backend Setup
The initial setup for the project involves installing and setting up Strapi as the backend for your Jekyll blog. It will host the content as well as the plugins to power up your blog.
- First, make sure node and npm/yarn are installed by running
node -v
. - Create a new Strapi project. I’ll name mine jekyll-blog-backend.
yarn create strapi-app jekyll-blog-backend --quickstart
The strapi server should be running automatically on port 1337. Go to http://localhost:1337/admin in your browser to set up your admin for Strapi.
Create an Admin User
Add an admin user to your Strapi app from the registration page.
Create Content Types
Select Content-Type Builder
from the Strapi Dashboard sidebar to open up the Content-Type Builder page.
Posts
In this stage,
- Create a
Collection Type
for posts. - Select
Create new Collection Type
and enter a post name. You can name itPost
. - Click on
Advanced Settings
and disable theDrafts System
then clickContinue
. - Add the following fields to your Post collection type:
- A
Text
field with nametitle
and typeshort text
. - A
Text
field with the namedescription
and type long text
. - A
Rich Text
field namedcontent
.
Click Save
and wait for Strapi to restart. Once Strapi has restarted, "Post" is listed under Content Manager > Collection types in the navigation.
If you get stuck, refer to the Strapi Quick Start Guide, for detailed instructions.
Add Relation Field
Let's add a relation between Post
and User
from the users-permissions
plugin. This makes it easier to link a post to a user to display key data like the Author's name and profile picture, posts etc.
- Add a new
Relation
field to thePost
content type. The column on the left should bePost
, and the column on the right should beUser (from: users-permissions)
. - Select the fourth relation, User has many Posts, and click
Finish
.
Click Save
and wait for Strapi to restart for the changes to appear.
Add Slug UID to Posts
Next, we will add a dynamic auto-generated slug to the Post
collection.
- Go to Content-Types Builder and click Post.
- Select Add another field and choose UID.
- Set the field
Slug
and set the attached field totitle
. - Click
Save
and wait for Strapi to restart for the changes to take effect.
- Click on Configure the view, and select the
Slug
field. - In the
Edit Slug
menu set Editable Field toTRUE
. - Click Finish and then Save.
Now, the slug will be the same as the title for a blog article.
Add Posts
Let’s create new entries for our blog posts.
- Create an entry for the “Post” collection type.
- Go to Content Manager > Collection types - Post.
- Click on Create new entry.
- Add the title, description and content to your blog entry.
- Click Save.
Repeat these steps to add multiple blog entries. Feel free to enter any content of your choice. Go to Content Manager > Collection types - Post to see a list of your blog entries.
Allow Access
For security reasons, the API access is restricted by default. We need to make the content available publicly through the API.
- Click on Settings under GENERAL at the bottom of the main navigation.
- Under Users & Permissions Plugin, choose Roles.
- Click the Public role.
- In the Permissions tab, find Post, click on it and select the checkboxes next to find and findone.
- Save.
Now, test the link to the API by visiting http://localhost:1337/api/posts. If it’s working you should see an output showing the raw posts data as seen below.
Great job, your API is now working. With that, the backend is all set.
Frontend Setup
The next step is to set up your frontend for the blog. You will install Jekyll CLI and all its prerequisites using the instructions from the Jekyll installation page.
Next, install the jekyll bundler and gems using the following command:
$ gem install jekyll bundler
Generate Jekyll project
In this stage, we will generate our Jekyll project.
- In the root of your application folder, create a new Jekyll site at
./jekyll-blog
or any other name you prefer to use.
$ jekyll new jekyll-blog
Start Development Server
To start the development server,
- Change directory into your Jekyll project directory.
$ cd jekyll-blog
- Build the site and start the server to make it available on your local machine.
$ bundle exec jekyll serve
- Browse to http://localhost:4000 to see your new Jekyll blog.
⚠️ WARNING:
For Ruby version 3.0.0 or higher,bundle exec jekyll serve
may not work. You can fix it by addingwebrick
to your dependencies:bundle add webrick
. See image below.
NOTE:
Add the--livereload
option toserve
to automatically refresh the page with each change you make to the source files:bundle exec jekyll serve --livereload
.
Fetch Blogposts
Data for statically generated website can come from varying sources. This can be markdown files, XML files, JSON, CSV, etc. The default data source for Jekyll is markdown. Fortunately, the data source which is the Strapi REST API is already in markdown format. We need to construct a simple Ruby script to consume the API.
In your Jekyll project directory, create a new ruby file and give it any name you want, I'll name mine jekapi.rb
. Copy and paste the following source code into jekapi.rb
.
# Ruby script to consume the Strapi API endpoint
require 'net/http'
require 'json'
require 'ostruct'
url = 'http://localhost:1337/api/post?populate=*'
uri = URI(url)
response = Net::HTTP.get(uri)
result = JSON.parse(response, object_class: OpenStruct)
result.data.each do |document|
url = 'http://localhost:1337/api/posts?populate=*'
uri = URI(url)
response = Net::HTTP.get(uri)
result = JSON.parse(response, object_class: OpenStruct)
result.data.each do |document|
post_file_name_suffix = document.attributes.Slug
post_file_name_prefix = document.attributes.createdAt
post_file_name_prefix.slice!(10..24)
post_file_name = "#{post_file_name_prefix}-#{post_file_name_suffix}"
post_file_title = document.attributes.title
post_file_date = document.attributes.createdAt
post_file_description = document.attributes.description
post_file_author = document.attributes.author.data.attributes.username
post_file_content = document.attributes.content
post_file = File.new("_posts/#{post_file_name}.md", "w")
post_file.puts("---")
post_file.puts("layout: post")
post_file.puts("title: "\"#{post_file_title}\"\")"
post_file.puts("date: #{post_file_date}")
post_file.puts("description: "#{post_file_description}\")"
post_file.puts("author: #{post_file_author}")
post_file.puts("---")
post_file.puts("#{post_file_content}")
end
Run the script below in your terminal:
$ ruby jekapi.rb
Your _posts folder should have your Strapi posts with extension *.md.
This Ruby script will fetch the blog posts from the api endpoint http://localhost:1337/api/post?populate=*. Requesting using this URL with a * wildcard query will populate all the fields defined in our Strapi API. Refer to the Strapi Documentation - REST API: Population & Field Selection for further details.
The script loops through all the JSON data and creates posts which are added to the _posts folder using the Jekyll post naming convention.
Display Posts List
The next step is to display the list of articles for our blog.
$ bundle exec jekyll serve
The Jekyll site at http://localhost:4000 should now be displaying a list of our Strapi posts on the homepage:
Now, we have a fully functional blog. You can read any post by clicking the link in the home page. Each post will merge with the theme settings.
All the data from the Strapi API has been successfully retrieved and displayed. This includes the title
, author
, date
, description
and content
.
Now that we have set up our blog, let's power it up with some plugins
Plugin Setup
The next stage involves setting up our various plugins. We'll take them one after the other. Let's begin!
i18n Plugin
Internationalization is the process of developing your software so it can be localized to a particular audience that may vary based on culture, language, or region. It could mean removing all the hard-coded strings in your application and putting them in a JSON file. It is a cool feature you can add to your blog to cover a much wider audience.
The Strapi Internationalization (i18n) Plugin allows Strapi users to create, manage and distribute translated content in multiple languages.
Features
This plugin was developed in house by the Strapi Team. Its features include the following:
- Admin panel users can create several localized versions of their content.
- Developers can build localized projects by fetching and consuming the right content depending on the country/language of the audience.
Installation
Strapi v4 comes with i18n plugin by default. The other way is through NPM
. To install this plugin, you need to add an NPM dependency to your Strapi application:
# Using Yarn
yarn strapi install i18n
# Or using NPM
npm run strapi install i18n
Configuration
A STRAPI_PLUGIN_I18N_INIT_LOCALE_CODE
environment variable can be configured to set the default locale for your environment. The plugin uses an ISO country code as the value for the environment variable.
This is useful when a Strapi application is deployed in a production environment, with the i18n plugin enabled
How to Use i18n With the REST API
Our blog is using Strapi as a REST API endpoint. The i18n plugin adds new features to the REST API:
- A new
locale
parameter to fetch content for a specified locale - Creating and updating localized entries
Our blog will fetch localized entries with the locale parameter. The response to requests will include a string field locale
for the locale code for the content entry and an object localizations
which contains a data array of the id
and attributes
of the localization.
Go back to your Strapi Admin Dashboard and make sure that internationalization is enabled. Navigate to General > Plugins and make sure that internationalization is seen on the list of installed plugins:
If you face any issues with installation, refer to the Strapi i18n plugin documentation.
Let's set up a new locale for our blog. Proceed to Settings > Internationalization and click on "Add new locale". For this tutorial, choose French (fr):
We need to enable localization for the Post collection. Navigate to Content-Type Builder > Post, click on Edit. Inside the pop-up, select Advanced Settings and check the box labeled Enable localization for this Content-Type. Wait for your server to restart for the changes to take effect:
Next, add translations to your blog posts. Go to Content Manager > COLLECTION TYPES > Post. Select one of your blog post entries. You should see the INTERNATIONALIZATION tab in the side panel with a Locales drop-down:
Click on the Locales drop-down and create content for your locale. After selecting your second locale, you’ll be taken to a new page to enter your translated content:
Once you’ve created your content, click Save. Repeat the same procedure for the other posts and you should have a few published entries with content available in multiple locales:
At the frontend, we need a way to consume the new locale data for our blog. Update jekapi.rb
with the locale data from the Strapi API endpoint:
#jekapi.rb
require 'net/http'
require 'json'
require 'ostruct'
url = 'http://localhost:1337/api/posts?populate=*'
uri = URI(url)
response = Net::HTTP.get(uri)
result = JSON.parse(response, object_class: OpenStruct)
result.data.each do |document|
post_file_locale_def = document.attributes.locale
post_file_name_suffix = document.attributes.Slug
post_file_date = document.attributes.createdAt
post_file_name_prefix = document.attributes.createdAt
post_file_name_prefix.slice!(10..24)
post_file_name = "#{post_file_name_prefix}-#{post_file_name_suffix}"
post_file_title = document.attributes.title
post_file_description = document.attributes.description
post_file_author = document.attributes.author.data.attributes.username
post_file_content = document.attributes.content
post_file = File.new("_posts/#{post_file_name}.md", "w")
post_file.puts("---")
post_file.puts("layout: post")
post_file.puts("title: \"#{post_file_title}\"")
post_file.puts("date: #{post_file_date}")
post_file.puts("description: #{post_file_description}")
post_file.puts("author: #{post_file_author}")
post_file.puts("locale: #{post_file_locale_def}")
post_file.puts("---")
post_file.puts("#{post_file_content}")
post_file_locale = document.attributes.localizations.data[0].attributes.locale
post_file_title_locale = document.attributes.localizations.data[0].attributes.title
post_file_description_locale = document.attributes.localizations.data[0].attributes.description
post_file_content_locale = document.attributes.localizations.data[0].attributes.content
post_file_alt = File.new("fr/_posts/#{post_file_name}.md", "w")
post_file_alt.puts("---")
post_file_alt.puts("layout: post")
post_file_alt.puts("title: \"#{post_file_title_locale}\"")
post_file_alt.puts("date: #{post_file_date}")
post_file_alt.puts("description: #{post_file_description_locale}")
post_file_alt.puts("author: #{post_file_author}")
post_file_alt.puts("locale: #{post_file_locale}")
post_file_alt.puts("---")
post_file_alt.puts("#{post_file_content_locale}")
end
Create a folder for your French content in your Jekyll working directory. Name the folder fr to match with the locale: fr
configured in the Strapi dashboard. Within fr, create a directory named _posts.
Run the jekapi.rb
script, then launch your Jekyll site. Open one of the posts and add a /fr/
prefix to the site link to view the French version of your post. There you have it: a simple implementation of internationalization in your website:
SEO Plugin
What's a good blog without some SEO?. The Strapi Team developed the Strapi plugin SEO to help with that.
Features
- Easily see which Content-Types have the SEO Component or not.
- Preview content in the Search Engine Results Page (SERP).
- Manage important meta tags like
metatitle
andmetadescription
. - Manage meta social tags (Facebook and Twitter) for your content.
- Get strong SEO analysis for your content.
Installation
Install the SEO plugin by adding it as an NPM dependency to your Strapi backend application.
# Using Yarn
yarn add @strapi/plugin-seo
# Or using NPM
npm install @strapi/plugin-seo
Configuration
After installing, navigate to ./config/plugins.js
in your Strapi backend and add:
module.exports = ({ env }) => ({
// ...
seo: {
enabled: true,
},
// ...
});
Build your admin panel, then start your Strapi server:
$ yarn build
$ yarn develop
Your Strapi dashboard should now have SEO listed under Plugins:
How to Use It
Now that we have learnt how to install and configure it, here's how to use it:
- Go to localhost:1337/admin/plugins/seo.
- Under the Collection Types tab, select +Add component for the Post collection type. You will be directed to the Content-Type Builder for the Post collection. The SEO plugin enables you to add shared components to your collection type. Listed under the Shared components is MetaSocial and SEO:
- Next, select Add another field in Post under Collection Types.
- Choose Component from the pop-up.
- In the Add new component menu, select Use an existing component and click on Select a component. The Select a component drop-down will show the available existing components from the SEO plugin, shared - metaSocial and shared - seo.
- Choose shared - seo and name it seo. Click Finish then Save and wait for your server to restart:
The seo
component should now be listed under the Post collection type in the Content-Type Builder and if you check the SEO plugin menu you will find Post collection type has been added:
Now for the fun stuff. Select one of your posts in the Content Manager. The SEO Plugin menu should be available in the right side section of your post. It contains Browser Preview, Social Preview and SEO Summary for your blog post. Below the Slug text box is the seo component for you to add the necessary SEO entries for your blog post:
Click on+
in the seo box. In the pop-up you will see numerous seo entries to fill for your post. Add metaTitle, metaDescription, metaImage and keywords entries for your post, select + Add an entry then click Save:
The SEO Summary will give you hints together with the SEO Analyzer to improve your SEO entry performances.
The SEO Analyzer provides more context for your SEO to boost your performance so that you can have an SEO friendly metatitle
and metadescription
in your blog.
For more in-depth uses for the Strapi SEO Plugin check out The first Strapi SEO plugin article.
Cloudinary Provider Plugin
Now that your blog has i18n and improved SEO, an external media management solution can enhance your blog. Visit Cloudinary.
Features
Cloudinary is an image and video management solution for websites and mobile apps. It covers everything from image and video uploads, storage, manipulations, optimizations to delivery. Cloudinary can deliver a large amount of data through a fast Content Delivery Network (CDN). This frees your backend from performance bottlenecks and frees up resources on your server.
Installation
To install the cloudinary provider plugin:
- Create a free Cloudinary account and after verifying your email, you will be redirected to the management dashboard of your account:
- Copy and save the following Account Details:
- Cloud Name
- API Key
- API Secret
SECURITY REMINDER
Save your account credentials somewhere safe and keep them secret.
- Go back to your Strapi blog backend and stop the server using
Ctrl + C
. 4. Install the Strapi Cloudinary Provider Plugin:
# using yarn
yarn add @strapi/provider-upload-cloudinary
# using npm
npm install @strapi/provider-upload-cloudinary --save
Configuration
a. Provider Configuration
Add the following to ./config/plugins.js
:
module.exports = ({ env }) => ({
// ...
upload: {
config: {
provider: 'cloudinary',
providerOptions: {
cloud_name: env('CLOUDINARY_NAME'),
api_key: env('CLOUDINARY_KEY'),
api_secret: env('CLOUDINARY_SECRET'),
},
actionOptions: {
upload: {},
uploadStream: {},
delete: {},
},
},
},
// ...
})
In the root directory of your Strapi blog backend, create a .env file and add the following variables and with their respective values. These can be found in your Cloudinary dashboard under Account Details:
CLOUDINARY_NAME = cloudinary-name
CLOUDINARY_KEY = cloudinary-key
CLOUDINARY_SECRET = cloudinary-secret
Security Middleware Configuration
Due to the default settings in the Strapi Security Middleware, you will need to modify the contentSecurityPolicy
settings to properly see thumbnail previews in the Media Library i.e. you will be able to preview images uploaded to Cloudinary in your Strapi Admin dashboard. You should replace strapi::security
string with the object below in ./config/middlewares.js
as explained in the middleware configuration documentation.
module.exports = [
// ...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': ["'self'", 'data:', 'blob:', 'dl.airtable.com', 'res.cloudinary.com'],
'media-src': ["'self'", 'data:', 'blob:', 'dl.airtable.com', 'res.cloudinary.com'],
upgradeInsecureRequests: null,
},
},
},
},
// ...
];
Restart your Strapi server to complete the configuration. After uploading an image, it will upload to Cloudinary.
How to Use It
To use the newly-installed plugin,
- Upload an image to one of your posts in Post collection in the Content Manager.
- Select Add new assets and then upload an image.
- Tick the one you want and click on Finish.
Your blog post now has a link to your image hosted on the Cloudinary cloud provider.
Navigate to your Cloudinary Management Console - Media Library and you should see the images you uploaded appear there.
Next let's rebuild our Jekyll site to see the changes take effect. In your Jekyll frontend working directory stop the Jekyll server using Ctrl+C
if you haven't already. Next, in the root folder of your Jekyll blog, clean your site:
$ bundle exec jekyll clean
Run the jekapi.rb
script to update your posts, then start the Jekyll server
$ ruby jekapi.rb
$ bundle exec jekyll serve
Navigate to the post you have updated with an image. If you have configured your Cloudinary Upload Provider plugin correctly, an image should appear in your post and if you inspect the html
for the image, it should show the cloudinary url to the image.
Now your blog can benefit from faster performance loading images hosted on a CDN at the same time off loading some work for your Strapi backend server.
Conclusion
That's it. You have seen the power and ease of creating a Jekyll blog powered by Strapi as a headless CMS to store your content and provide the content securely through a REST API. You powered up your blog by adding Internationalization, SEO and Cloudinary as an external upload provider to help manage your media and improve loading speed of your media assets.
Next, you could look at a deployment strategy for your blog on a production server. See this Strapi deployment guide. You can also tinker with adding some more plugins to your blog by checking out the numerous options on the Strapi Market
I hope you were able to follow along. Here's the repo with the complete source code to test out the full working version of the blog.