DEV Community

AMIR TADRISI
AMIR TADRISI

Posted on

How to Optimize Your React App for SEO with Sanity

Introduction to SEO

What is SEO?

Search Engine Optimization (SEO) is the practice of increasing the quantity and quality of traffic to your website through organic search engine results. This is done by improving the ranking of your website on search engines. The higher your website ranks, the more likely it will be on the first page of search results and more people will visit it.

Why SEO is important?

68% of online experiences begin with a search engine and 75% of people never scroll past the first page of the search engine results. This means without SEO, your web application could be missing out on the potential growth, success and will be hidden from sight.

How does SEO work?

SEO process can be divided into 3 steps:

  1. Crawling: Crawlers can be thought of as "residents" of the search engine that go out and visit web pages. They then feed information back to the Search Engine about these web pages which are indexed into a database called the index for later retrieval.
  2. Indexing: When people does a Google search, results are not only based on what words are most frequent in your content, but also on which content is most relevant to the user. This means that search engines like Google care about metadata such as titles and meta descriptions when they're ranking your web page in results for a certain keyword.
  3. Ranking: Once the search engine has created it's an index of your website, then it tries to rank all indexed pages that are relevant to a user's search query. The goal of search engines is to provide the user with the best results that match their query.

SEO best practices

Now that we know how SEO works, let’s see what are the best practices for it.

  1. Be a good citizen and make a useful website

Before we go any further, this is probably the most important advice: write useful content! It doesn't matter how much effort you put into optimizing your React app if no one is there to see it. If you're publishing content that people care about, then other internet users are likely to link to your site and share it with their friends, which increases the likelihood of being visible in search engine results even more.

  1. Get descriptive with your titles & meta descriptions

This means that instead of writing titles like "Home", you should write something more descriptive. This is because search engine bots are not intelligent enough to figure out what your page is about based on different words being close to each other..

  1. Create unique content for every page of your website

Having duplicate content on your site is bad for SEO. For example, if you have multiple pages with the same content - search engines will not be able to properly rank each page.

  1. Optimize your page speed

Search engines like Google care about page speed.

An important thing to keep in mind here is that GoogleBot, which crawls the web looking for content, has a very low bandwidth. That means that it shouldn't have trouble crawling any of your website's pages if they don’t take long time to load.

The time it takes for your page to load can have a major impact on how many visitors stick around. Pages that take less than 2 seconds have an average bounce rate 9%, while pages loaded within 5 show 38%.

  1. Optimize Images

We all know that images are a great way to add more diversity and creativity on our pages, but there is something you should do when using them. Optimizing an image will lead not only increase how fast Google Bot crawls through your content; it'll also improve loading speeds for users!

  1. Include a variety of internal links

Internal links are a great way to make your website more accessible for both humans and search engines. They give Google additional signals that the content on these pages is relevant, which will help rank them higher in results; while also making it easier than ever before with all of their extra information!

Create the blog application

In this section, we create a new react.js application called sanity-blog that connects to Sanity.

The completed project can be found on this Github repo! You can also check the live version of this application

Setup a new React application

⚠️ Before you start, make sure Node.js is installed in your environment. To learn more visit the Node website.

Open your Shell and run the following command.

npx create-react-app sanity-blog
Enter fullscreen mode Exit fullscreen mode

Next, run this command to change the directory to the project you created

cd sanity-blog
Enter fullscreen mode Exit fullscreen mode

To ensure that everything is operating fine, execute the following command in your terminal. Your React application should open in http://localhost:3000 on your browser.

npm start
Enter fullscreen mode Exit fullscreen mode

Now let's install a few of the dependencies we'll need. Run the following command in the project's root:

npm install @portabletext/reactimport @portabletext/react @sanity/image-url react-router-dom
npm install -D tailwindcss postcss autoprefixer @tailwindcss/typography
npx tailwindcss init
Enter fullscreen mode Exit fullscreen mode

Next, let’s set up TailwindCSS for styling our front-end. Go to the src/index.css and replace the file content with

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Now go to the ./tailwind.config.js file and replace it with the following

module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [require("@tailwindcss/typography")],
}
Enter fullscreen mode Exit fullscreen mode

Setup Sanity

Our goal here is:

  • Create a new Sanity project for our blog
  • Customize the default blog Schema to add SEO fields
  • Add some content

Let's get started by installing Sanity CLI on our local environment.

⚠️ Before running these commands make sure you are in the sanity-blog directory.

npm i -g @sanity/cli
Enter fullscreen mode Exit fullscreen mode

When the Sanity CLI is installed successfully we can activate initialize Sanity in our React project.

sanity init
Enter fullscreen mode Exit fullscreen mode

Now you see a couple of questions in the command line. Answer them as the following:

? Select project to use **Create new project**
? Your project name: **Sanity Blog**
? Use the default dataset configuration? **Yes**
? Project output path: **[Choose default path]**
? Select project template **Blog (schema)**
Enter fullscreen mode Exit fullscreen mode

Once finished you should see a new folder called sanityblog under the sanity-blog head over there and run the following command to start the Sanity Studio.

cd sanityblog
sanity start
Enter fullscreen mode Exit fullscreen mode

After running these commands you should see this

sanity start
✔ Checking configuration files...
⠧ Compiling...webpack built 2f98f0cdc718c8744e79 in 11987ms
✔ Compiling...
Content Studio successfully compiled! Go to http://localhost:3333
Enter fullscreen mode Exit fullscreen mode

Head over to http://localhost:3333 to open Sanity Studio.

Now, let’s add some new fields to our post model so that it can be optimized for search engines.

In your code editor go to the sanityblogschemas and open the post.js file.

post.js location in the project

post.js location in the project

Replace the content of this file with the following:

export default {
  name: 'post',
  title: 'Post',
  type: 'document',
  groups: [
    {
      name: 'seo',
      title: 'SEO',
    },
  ],
  fields: [
    {
      name: 'title',
      title: 'Title',
      type: 'string',
    },
    {
      name: 'seoTitle',
      title: 'SEO Title',
      group: 'seo',
      validation: Rule => [
        Rule.required().min(40).max(50).error('SEO titles between 40 and 50 characters with commonly searched words have the best click-through-rates'),
      ],
      type: 'string',
    },
    {
      name: 'seoDescription',
      title: 'SEO Description',
      group: 'seo',
      validation: Rule => [
        Rule.required().min(50).max(156).error('Good SEO descriptions utilize keywords, summarize the story and are between 140-156 characters long.'),
      ],
      type: 'text',
    },
    {
      name: "ogTitle",
      title: "Open Graph Title",
      group: 'seo',
      validation: Rule => [
        Rule.required().min(40).max(50).error('SEO titles between 40 and 50 characters with commonly searched words have the best click-through-rates'),
      ],
      type: "string",
    },
    {
      name: "ogDescription",
      title: "Open Graph Description",
      group: 'seo',
      validation: Rule => [
        Rule.required().min(50).max(156).error('Good SEO descriptions utilize keywords, summarize the story and are between 140-156 characters long.'),
      ],
      type: "text",
    },
    {
      name: "ogImage",
      title: "Open Graph Image",
      group: 'seo',
      type: "image",
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
    },
    {
      name: 'author',
      title: 'Author',
      type: 'reference',
      to: {type: 'author'},
    },
    {
      name: 'mainImage',
      title: 'Main image',
      type: 'image',
      options: {
        hotspot: true,
      },
    },
    {
      name: 'categories',
      title: 'Categories',
      type: 'array',
      of: [{type: 'reference', to: {type: 'category'}}],
    },
    {
      name: 'publishedAt',
      title: 'Published at',
      type: 'datetime',
    },
    {
      name: 'body',
      title: 'Body',
      type: 'blockContent',
    },
  ],

  preview: {
    select: {
      title: 'title',
      author: 'author.name',
      media: 'mainImage',
    },
    prepare(selection) {
      const {author} = selection
      return Object.assign({}, selection, {
        subtitle: author && `by ${author}`,
      })
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

We added 2 important changes to the default schema:

  1. We've added a new group called SEO that will be appearing as a tab on the Post page. This group contains all the necessary fields for SEO.

Screen Shot 2022-02-07 at 23.02.43.png

  1. We have a whole new set of fields for meta title, description, open graph title, description, and image. These are all validated so that they contain the proper length to produce the best result in SEO.

Screen Shot 2022-02-07 at 23.11.11.png

Finally, let’s create a sample blog post in Sanity Studio

Screen Shot 2022-02-07 at 23.24.09.png

Connecting Sanity to our React app

To connect Sanity to the React app, let’s install Sanity Client first. Go to the root of the project and run the following command

npm install @sanity/client
Enter fullscreen mode Exit fullscreen mode

Next, we need to create an instance of Sanity Client and setup in our project. To do so under the src folder create a new file called client.js Then, inside that file, add the following code:

import sanityClient from "@sanity/client";

export default sanityClient({
    apiVersion: "2022-04-06",
  projectId: "Your Project ID Here",
  dataset: "production",
  useCdn: true,
});
Enter fullscreen mode Exit fullscreen mode

💡 To find the projectId you can go to the sanityblog/sanity.json and search for the projectId

Finally, let’s add react app URL to the Sanity Project CORS origins. In the command line, go to the sanityblog folder and run the following and answer yes to the question.

sanity cors add http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

Create React components

Under the src folder create a new folder called components let’s add AllPosts.js and OnePost.js there

The AllPosts.js file should contain

import React, { useState, useEffect } from 'react';
import sanityClient from "../client"
import imageUrlBuilder from "@sanity/image-url";

const builder = imageUrlBuilder(sanityClient);
function urlFor(source) {
  return builder.image(source);
}

export default function AllPosts() {
    const [posts, setPosts] = useState([]);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
            // This is GROQ syntax for our query, to learn more about it, check out the docs at https://www.sanity.io/docs/groq
      sanityClient.fetch(
        `*[_type == "post"] | order(publishedAt desc) {
          title,
          publishedAt,
          slug,
          body,
          "authorName": author->name,
          "authorImage": author->image,
          mainImage{
            asset->{
              _id,
              url
             }
           },
          categories {
            title,
            slug,
          },
        }`
        )
        .then(posts => {
          setPosts(posts);
          setLoading(false);
        })
    }, []);

    return loading ? (
        <div>Loading...</div>
      ) : ( 
        <div className="relative bg-gray-50 pt-16 pb-20 px-4 sm:px-6 lg:pt-24 lg:pb-28 lg:px-8">
        <div className="absolute inset-0">
          <div className="bg-white h-1/3 sm:h-2/3" />
        </div>
        <div className="relative max-w-7xl mx-auto">
          <div className="text-center">
            <h2 className="text-3xl tracking-tight font-extrabold text-gray-900 sm:text-4xl">From the blog</h2>
            <p className="mt-3 max-w-2xl mx-auto text-xl text-gray-500 sm:mt-4">
              Lorem ipsum dolor sit amet consectetur, adipisicing elit. Ipsa libero labore natus atque, ducimus sed.
            </p>
          </div>
          <div className="mt-12 max-w-lg mx-auto grid gap-5 lg:grid-cols-3 lg:max-w-none">
            {posts.map((post) => (
              <div key={post.slug.current} className="flex flex-col rounded-lg shadow-lg overflow-hidden">
                <a href={`/${post.slug.current}` } className="block mt-2">
                    <div className="flex-shrink-0">
                    <img className="w-full object-cover" src={urlFor(post.mainImage).width(100).url()} alt={post.title} />
                    </div>
                    <div className="flex-1 bg-white p-6 flex flex-col justify-between">
                    <div className="flex-1">
                        <p className="text-xl font-semibold text-gray-900">{post.title}</p>
                    </div>
                    <div className="mt-6 flex items-center">
                        <div className="flex-shrink-0">
                            <span className="sr-only">{post.authorName}</span>
                            <img className="h-10 w-10 rounded-full" src={urlFor(post.authorImage).width(100).url()} alt={post.authorName} />
                        </div>
                        <div className="ml-3">
                        <p className="text-sm font-medium text-gray-900">
                            {post.authorName}
                        </p>
                        <div className="flex space-x-1 text-sm text-gray-500">
                            <time dateTime={post.publishedAt}>{post.publishedAt}</time>
                            <span aria-hidden="true">&middot;</span>
                        </div>
                        </div>
                    </div>
                    </div>
                </a>
              </div>
            ))}
          </div>
        </div>
      </div>
      );
}
Enter fullscreen mode Exit fullscreen mode

The OnePost.js file should contain

import React, {useState, useEffect} from 'react';
import sanityClient from '../client';
import imageUrlBuilder from '@sanity/image-url';
import { PortableText } from '@portabletext/react'

const builder = imageUrlBuilder(sanityClient);
function urlFor(source) {
  return builder.image(source);
}

export default function OnePost(){
    const [post, setPost] = useState([]);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
      sanityClient.fetch(
        `*[_type == "post" && slug.current == $slug][0]{
          title,
          publishedAt,
          slug,
          body,
          "authorName": author->name,
          "authorImage": author->image,
          mainImage{
            asset->{
              _id,
              url
            }
          },
          categories {
            title,
            slug,
          },
        }`,
        {slug: window.location.pathname.split('/')[1]}
      )
      .then(post => {
        setPost(post);
        setLoading(false);
      })
    }, []);

    return loading ? (
        <div>Loading...</div>
    ) : (
      <div className="relative py-16 bg-white overflow-hidden">
          <div className="hidden lg:block lg:absolute lg:inset-y-0 lg:h-full lg:w-full">
            <div className="relative h-full text-lg max-w-prose mx-auto" aria-hidden="true">
              <svg
                className="absolute top-12 left-full transform translate-x-32"
                width={404}
                height={384}
                fill="none"
                viewBox="0 0 404 384"
              >
                <defs>
                  <pattern
                    id="74b3fd99-0a6f-4271-bef2-e80eeafdf357"
                    x={0}
                    y={0}
                    width={20}
                    height={20}
                    patternUnits="userSpaceOnUse"
                  >
                    <rect x={0} y={0} width={4} height={4} className="text-gray-200" fill="currentColor" />
                  </pattern>
                </defs>
                <rect width={404} height={384} fill="url(#74b3fd99-0a6f-4271-bef2-e80eeafdf357)" />
              </svg>
              <svg
                className="absolute top-1/2 right-full transform -translate-y-1/2 -translate-x-32"
                width={404}
                height={384}
                fill="none"
                viewBox="0 0 404 384"
              >
                <defs>
                  <pattern
                    id="f210dbf6-a58d-4871-961e-36d5016a0f49"
                    x={0}
                    y={0}
                    width={20}
                    height={20}
                    patternUnits="userSpaceOnUse"
                  >
                    <rect x={0} y={0} width={4} height={4} className="text-gray-200" fill="currentColor" />
                  </pattern>
                </defs>
                <rect width={404} height={384} fill="url(#f210dbf6-a58d-4871-961e-36d5016a0f49)" />
              </svg>
              <svg
                className="absolute bottom-12 left-full transform translate-x-32"
                width={404}
                height={384}
                fill="none"
                viewBox="0 0 404 384"
              >
                <defs>
                  <pattern
                    id="d3eb07ae-5182-43e6-857d-35c643af9034"
                    x={0}
                    y={0}
                    width={20}
                    height={20}
                    patternUnits="userSpaceOnUse"
                  >
                    <rect x={0} y={0} width={4} height={4} className="text-gray-200" fill="currentColor" />
                  </pattern>
                </defs>
                <rect width={404} height={384} fill="url(#d3eb07ae-5182-43e6-857d-35c643af9034)" />
              </svg>
            </div>
          </div>
          <div className="relative px-4 sm:px-6 lg:px-8">
            <div className="text-lg max-w-prose mx-auto">
              <h1>
                <span className="mt-2 block text-3xl text-center leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl">
                  {post.title}
                </span>
              </h1>
              <hr className="mt-8 border-b-2 w-24 mx-auto border-gray-200" />
              <figure>
                <img
                  className="w-full h-72 rounded-lg mt-12"
                  src={urlFor(post.mainImage).width(100).url()} 
                  alt={post.title}
                />
                <figcaption className='text-gray-700 text-center pt-2'>Sagittis scelerisque nulla cursus in enim consectetur quam.</figcaption>
              </figure>
              <div className="mt-8 text-xl text-gray-500 leading-8 prose prose-indigo">
                <PortableText value={post.body} />
              </div>
            </div>
          </div>
        </div>
      )
    }
Enter fullscreen mode Exit fullscreen mode

Finally, replace the src/App.js content with the following

import React from 'react';
import AllPosts from './components/AllPosts';
import OnePost from './components/OnePost';
import { BrowserRouter, Route, Routes } from "react-router-dom";
import './App.css';

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<AllPosts />} />
        <Route path=":slug" element={<OnePost />} />
      </Routes>
    </BrowserRouter>
  )
}
Enter fullscreen mode Exit fullscreen mode

Optimize your React app for SEO with Sanity

In this section, we learn how to build a seo-friendly React application through practical techniques using Sanity’s features.

Optimizing images

As we said earlier our site performance is crucial for SEO. Images are assets that can harm site performance if they are not in the proper format or size. The process of optimizing your site can be tedious and time-consuming. It is crucial to have an automatic way, so you don't end up spending hours optimizing images.

Sanity has a global content delivery network (CDN) for serving assets. When we upload our blog images, they can be accessed from cdn.sanity.io

When a user requests an asset, it is processed by Sanity’s backend systems and then cached on servers located near end-users. Subsequent requests are served from the cache so that they can quickly respond with high-quality content without having to slow down or load extra resources for each individual request.

Let’s see an example. For sample blog content I downloaded https://unsplash.com/photos/qWwpHwip31M
 The file size is 985 KB in JPEG format with 5184 × 3456 dimensions. I went to the Sanity Studio and uploaded it as a blog image. In the OnePost.js component, we have the following

<img
  className="w-full h-72 rounded-lg mt-12"
  src={urlFor(post.mainImage).width(100).url()} 
  alt={post.title}
/>
Enter fullscreen mode Exit fullscreen mode

As you see in for src we are defining width 100. That helps us to reduce the file size and dimensions.

Screen Shot 2022-02-08 at 17.10.32.png

You can check the file in the CDN here https://cdn.sanity.io/images/a4zwcx9l/production/ffa097961aeffbde70f97ab81ece18dc80705907-5184x3456.jpg?w=100

Metadata

Title

The title tag is an essential SEO element that users see when they click to enter your website from search results. It's also one of the main factors Google uses in determining what a page is about, so using relevant keywords here will help you rank higher on SERPs (search engine result pages).

To be able to add the title and the rest of the meta tags, let’s first install React Helmet

At the root of the project run the following command in the terminal

npm install --save react-helmet
Enter fullscreen mode Exit fullscreen mode

Next, let’s add Meta title in Sanity Studio. In the Studio, click on Post and choose the blog you want to add a title for. In the SEO tab, you can fill out the SEO Title

Finally, let’s import Helmet at the top of the file and add the Meta title to OnePost.js components

import React, {useState, useEffect} from 'react';
import sanityClient from '../client';
import imageUrlBuilder from '@sanity/image-url';
import { PortableText } from '@portabletext/react'
import { Helmet } from 'react-helmet';

export default function OnePost(){
            .
            .
            .
        return loading ? (
        <div>Loading...</div>
        ) : (
      <div>
        <Helmet>
          <title>{post.seoTitle}</title>
        </Helmet>
                .
                .
                .
      </div>
      )
}
Enter fullscreen mode Exit fullscreen mode

Description

The description meta tag is an often overlooked SEO element, but it can have a significant effect on your click-through rate. According to Google this part of the page isn't taken into account when ranking pages; however if you use keywords here they will show up bolded in any searches that include them - so make sure those words are relevant!

Next, let’s add Meta Description in Sanity Studio. In the Studio, click on Post and choose the blog you want to add a description for. In the SEO tab, you can fill out the SEO Description

Screen Shot 2022-02-08 at 19.50.51.png

Finally let’s add Meta description to OnePost.js components

            .
            .
            .
import { Helmet } from 'react-helmet';

export default function OnePost(){
            .
            .
            .
      <div>
        <Helmet>
                    <title>{post.seoTitle}</title>
          <meta name="description" content={post.seoDescription} />
        </Helmet>
            .
            .
            .
      </div>
      )
}
Enter fullscreen mode Exit fullscreen mode

Open Graph

known as og is a way to provide meta information to social media like Facebook and Twitter. It is used to describe a webpage and tell these platforms what sort of content the page contains, such as images.

These Open Graph tags are not Search Engine Optimization (SEO) related, but still, have benefits for sharing your content on social media or messaging apps such as WhatsApp and Telegram.

Next, let’s add Open graph information in Sanity Studio. In the Studio, click on Post and choose the blog you want to add og information for. In the SEO tab, you can fill the Open Graph Title, Description, and Image.

Screen Shot 2022-02-08 at 20.20.26.png

Finally let’s add Open Graph description to OnePost.js components

            .
            .
            .
import { Helmet } from 'react-helmet';

export default function OnePost(){
            .
            .
            .
      <div>
        <Helmet>
                    <title>{post.seoTitle}</title>
                    <meta name="description" content={post.seoDescription} />
          <meta property='og:title' content={post.ogTitle} />
          <meta property='og:description' content={post.ogDescription} />
          <meta property='og:image' content={urlFor(post.ogImage).width(300).url()} />
        </Helmet>
            .
            .
            .
      </div>
      )
}
Enter fullscreen mode Exit fullscreen mode

What else may we do to improve our SEO?

Headings

Headings are used to organize the page content and help users understand its structure. They also allow search engines like Google, Bing or Yahoo! (the ones who actually read this stuff) know which parts of your website they should rank higher in importance for visitors' convenience.

There are 6 different heading, H1 to H6. The H1 heading should represent what your webpage focuses on - it must be similar to a title tag.

Headings are used to organizing the content on a page and help users understand its structure. They also allow search engines like Google, Bing, or Yahoo! (the ones who read this stuff) know which parts of your website should rank higher in importance for visitors' convenience.
There are 6 different headings, H1 to H6. The H1 heading should represent what your webpage focuses on - it must be similar to a title tag.

In the src/components/OnePost.js component, we have the following

<h1>
     <span className="mt-2 block text-3xl text-center leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl">
    {post.title}
     </span>
</h1>
Enter fullscreen mode Exit fullscreen mode

*Adding a sitemap*

Although adding a sitemap to your website is no longer as essential as it once was, it's still a good idea. Google can effortlessly crawl your website, but adding a sitemap is still beneficial for ensuring that all of your content gets seen.

There are plugins like react-router-sitemap-generator that can do it for you.

Server-side rendering and Static site generation

By using React we can build single-page apps. A single page application is an application that loads a single HTML page and necessary assets like Javascript files and CSS are required for the application to run. Any interaction with the application only loads the necessary content and it doesn’t need to reload the page. SPA has better performance in comparison with the traditional web app but it can be challenging for SEO because when the search engine crawls the application it finds an empty page container that doesn’t have meaningful content for the bot and it should wait until Javascript loads to render the page content. This can cause a delay in indexing the page content or the crawler can miss important content to index.

One of the best options we have to solve this is to use pre-rendering technology to generate HTML files in advance, instead of having it all done by client-side Javascript. In the following we see 2 pre-render options react developers have to improve react SEO.

Server-side rendering (SSR)

As we said React websites are usually generated on the client-side browser. The idea behind SSR unlike client-side rendering is to render the initial HTML page on the server instead of waiting for the Javascript to be loaded on the browser. So the server handles the initial rendering of the application. The server imports the React application root component and renders it into an HTML file and sends that rendered HTML to the client.

Implementing SSR by yourself is a time-consuming and complex process it’s highly recommended to take a look at javascript frameworks like next.js that are tailored for SSR.

Static site generation (SSG)

SSG like SSR generates a static HTML page in advance instead of leaving it to the client browser. But the difference between SSG with SSR is in static site generation we generate HTML files during the build time, unlike server-side rendering that generates the HTML on each request. I also recommend taking a look at next.js to build SSG React application.

Conclusion

SEO is an essential part of ensuring your website/application appears on search result pages.

We have seen Sanity has a lot of features that help us to deliver high performance React application and provides a way for any user to add important SEO elements to content for better visibility and ranking in search engines.

Top comments (0)