It's been a long time since I've posted an article and I can say that I missed it! Today, I am very happy to announce the release of the first Strapi SEO plugin!
It's true, why make an SEO plugin? I could have improved our FoodAdvisor demo application or updated our outdated tutorials on v4!
More seriously, many of you ask how to properly manage your content so that it is SEO friendly. Indeed, Strapi does not generate views as a traditional CMS would. It is therefore difficult to have the feeling of having a direct impact on SEO. On Strapi, which is a Headless CMS, you only have access to the data. What concerns the front-end, i.e, the formatting which must please the search engine, is another distinct part that the content manager/editor does not have direct access to.
However, ensuring that your content is SEO friendly can and should be done upstream, during creation. It would therefore be interesting for you to be able to ensure that your content meets a few SEO criteria in order to rank on the SERP. Well, this is what this article is all about!
Disclaimer: This plugin has been developed internally but many other SEO plugins will emerge on our marketplace. It's up to you to use the one that best suits your needs. In any case, know that the ExFabrica dev team is currently working on another SEO plugin that works differently but can be combined with the one I'm about to present to you, which is just awesome!
The SEO plugin is divided into 2 parts:
Settings page
This page will detect if you have a seo
component in a shared
category (shared.seo
). For the v1 of this plugin, it was easier to define a classic and simple architecture in order to be able to reiterate more easily for a v2. The shared
category means that it is a category of components containing components that can be shared among all the content-types of your application, which is the case of an SEO component.
If such a component is not detected, then it will automatically import it inside your Strapi project.
Otherwise, the plugin will list the Content-Types of your application with or without the SEO component. You will then be able to add your component for each Content-Type, being careful to name it: seo
and include it at the first level (root) of your CT.
IMAGE
This menu contains 4 things:
This will open a modal containing a SERP preview of your content based on the metaTitle
and metaDescription
fields. You can see the web and mobile preview :)
This will open a modal containing a Facebook or Twitter preview card based on your metaSocial
components in your content.
This allows you to quickly take a look at the results of SEO checks on your content. Note that you have to click on "SEE DETAILS" to update it.
The SEE DETAILS link button will open a modal containing all the SEO checks on your content. You'll be able to see what you can improve or fix for your content to be SEO friendly as it should be!
This part is for curious developers who wish to know how the plugin was developed. Before you start, know that you can find the source code on the official GitHub repository as well as its npm package page. I won't detail each file of the plugin but I want to introduce the basics of creating a simple plugin so that you can more easily create your own!
I redirect you to the documentation to learn more about the basics of plugin development.
It all starts with the following command:
1strapi generate
It will run a fully interactive CLI to generate APIs, controllers, content-types, plugins, policies, middlewares and services.
What interests us here is the creation of a plugin! Simply choose the name, and activate the plugin in the ./config/plugins.js
file of your Strapi application:
1module.exports = {
2 // ...
3 seo: {
4 enabled: true,
5 resolve: "./src/plugins/seo", // Folder of your plugin
6 },
7 // ...
8};
I needed for this plugin to have in the front some information from the back-end of Strapi. To do this, you just need to create routes in the back-end part of your plugin that will use controllers and services to fetch this information.
So I defined the following routes for the SEO plugin:
1// ./server/routes/index.js
2
3module.exports = [
4 {
5 method: "GET",
6 path: "/component",
7 handler: "seo.findSeoComponent",
8 config: {
9 auth: false,
10 policies: [],
11 },
12 },
13 {
14 method: "POST",
15 path: "/component",
16 handler: "seo.createSeoComponent",
17 config: {
18 auth: false,
19 policies: [],
20 },
21 },
22 {
23 method: "GET",
24 path: "/content-types",
25 handler: "seo.findContentTypes",
26 config: {
27 auth: false,
28 policies: [],
29 },
30 },
31];
Let's look in detail at the first route. The handler is the findComponent
action of the seo
controller:
1// ./server/controllers/index.js
2const seo = require("./seo");
3
4module.exports = {
5 seo,
6};
1// ./server/controllers/seo.js
2module.exports = {
3 // ...
4 findSeoComponent(ctx) {
5 ctx.body = strapi.plugin('seo').service('seo').getSeoComponent();
6 },
7 // ...
This action directly uses a function present in the seo
service:
1// ./server/services/index.js
2const seo = require("./seo");
3
4module.exports = {
5 seo,
6};
1// ./server/services/seo.js
2module.exports = ({ strapi }) => ({
3 // ...
4 getSeoComponent() {
5 const seoComponent = strapi.components['shared.seo'];
6 return seoComponent
7 ? { attributes: seoComponent.attributes, category: seoComponent.category }
8 : null;
9 },
10 // ...
11}
This service allows me to have access to the strapi
object containing a lot of information about my project, such as whether the shared.seo
component exists in my whole project.
Once the back-end is ready, all I have to do in the front-end (./admin/src/...
) of the plugin is to call this route to get the desired information.
1// ./admin/src/utils/api.js
2// ...
3const fetchSeoComponent = async () => {
4 try {
5 const data = await request(`/seo/component`, { method: "GET" });
6 return data;
7 } catch (error) {
8 return null;
9 }
10};
11// ...
Voila, this is how I can get information about my Strapi application in the front-end of my plugin! Simple isn't it?
Learn more about plugin development on our v4 documentation
The admin panel is a React application that can embed other React applications. These other React applications are the admin parts of each Strapi plugin. As for the front-end, you must first start with the entry point: ./admin/src/index.js
.
This file will allow you to define more or less the behavior of your plugin. We can see several things:
1register(app) {
2 app.addMenuLink({
3 to: `/plugins/${pluginId}`,
4 icon: PluginIcon,
5 intlLabel: {
6 id: `${pluginId}.plugin.name`,
7 defaultMessage: 'SEO',
8 },
9 Component: async () => {
10 const component = await import('./pages/App');
11
12 return component;
13 },
14 });
15 app.registerPlugin({
16 id: pluginId,
17 initializer: Initializer,
18 isReady: false,
19 name,
20 });
21 },
First of all, there is a register function. This function is called to load the plugin, even before the app is actually bootstrapped. It takes the running Strapi application as an argument (app
).
Here it tells the admin to display a link in the Strapi menu for the plugin with a certain Icon, name, etc...
Then we find the bootstrap function:
1bootstrap(app) {
2 app.injectContentManagerComponent('editView', 'right-links', {
3 name: 'SeoChecker',
4 Component: SeoChecker,
5 });
6 },
This will expose the bootstrap function, executed after all the plugins are registered. Here we inject into the content manager a component that I created: SeoChecker
. This component contains the button opening the modal containing all the SEO checks in the content manager. I let you look at the code for more details.
Also, I redirect you to our documentation concerning the injection zones API.
Note: Know that it is possible to customize the admin using the injection zones API without having to generate a plugin. To do this, simply use the bootstrap function in your ./src/admin/app.js
file of your Strapi project to inject the components you want.
This is what was done on our demo FoodAdvisor, I redirect you to this file.
Back to our plugin!
The last part reffers to the translation management of your plugin:
1async registerTrads({ locales }) {
2 const importedTrads = await Promise.all(
3 locales.map((locale) => {
4 return import(`./translations/${locale}.json`)
5 .then(({ default: data }) => {
6 return {
7 data: prefixPluginTranslations(data, pluginId),
8 locale,
9 };
10 })
11 .catch(() => {
12 return {
13 data: {},
14 locale,
15 };
16 });
17 })
18 );
19
20 return Promise.resolve(importedTrads);
21 },
You will be able in the ./admin/src/translations
folder to add the translations you want. This plugin has been translated into French only for now. Feel free to add any other translations :)
For the rest, it is very simple React code, the beginning is in the file ./admin/src/pages/HomePage
. This file will contain the front-end of the plugin's Settings page. Then the part located in the content manager is managed by the SeoChecker
component.
I don't think it's useful in this article to go deeper into the code. If you are more curious then I leave you the freedom to look at the source code of the plugin and do not hesitate to give any feedback or to directly contribute to the plugin, you are more than welcome!
See you later!
Maxime started to code in 2015 and quickly joined the Growth team of Strapi. He particularly likes to create useful content for the awesome Strapi community. Send him a meme on Twitter to make his day: @MaxCastres