Search code examples
reactjsseosingle-page-applicationgoogle-searchgooglebot

How to achieve SEO for React SPA without SSR or prerendering. And preferably keep the code portable e.g. no vendor lock-in


The SEO topic has been debated to an extraordinary length and React SPAs are very common. Yet searching SO yields no clear coding guidance complimented by a simple to follow sequence of specific practical steps required to deploy a React SPA and achieve SEO.

The existing Q/A are either not very helpful or use links. This question is not related to React, the detailed answer considers deprecated AJAX technology. Although its upvote and viewing counts show the importance of this topic.

Searching beyond SO yielded the official Create React App (CRA) page. To create a minimal reproducible example I followed the steps:

  1. Executed commands:
yarn create react-app my-cra
cd my-cra
yarn add react-router-dom
  1. Replaced the content of the generated src/App.js file with the code:
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Page1 from './Page1';
import Page2 from './Page2';

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Page1 />} />
        <Route path="test" element={<Page2 />} />
      </Routes>
    </BrowserRouter>
  );
}
  1. Added a file src\Page1.js:
import { Link } from "react-router-dom";

const Page1 = () => {
  return (
    <>
      <h1>Page1</h1>
      <Link to="/test">To Page2</Link>
    </>
  );
};

export default Page1;
  1. Added a file src\Page2.js:
import { Link } from "react-router-dom";

const Page2 = () => {
  return (
    <>
      <h1>Page2</h1>
      <Link to="/">Back to Page1</Link>
    </>
  );
};

export default Page2;

The React SPA works as intended and naturally uses client side routing. GitHub Pages deployment was chosen since it doesn't require to introduce vendor specific code. However the problem is that according to the Notes it will be necessary to use either routing with hashes or this repo. Hashes are not acceptable because Google explicitly disallows URLs with hashes for SEO. The repo is not an option either since the author suggests another solution if SEO is important.

So the question is how to get indexed by Googlebot without replacing BrowserRouter used at the step 2 with HashRouter in a simple React SPA. Another solution, apart from CRA + GitHub Pages can be suggested.


Solution

  • The answer is meant to demonstrate an easily reproducible set of steps to get a React SPA indexed by Google without any need of SSR or prerendering. It's split into two parts with headings:

    • Website Deployment
    • Requesting Google to Index the Website

    The first part is all about building a deploying a sample React application. Only one bullet point (that deals with title and canonical) is specific to SEO.

    The second part is all about SEO, however it's not specific to SPA or React.

    Website Deployment

    The deployment is based on Crisp React boilerplate (I'm the author) and uses Cloudflare Pages.

    This particular boilerplate has been chosen due to its features, like the variety of deployments, both Jamstack and full stack. It makes it easy to alter the deployment described below and switch from Jamstack to full stack if need be. The ready-to-replace Structured Data placeholders provided by the boilerplate could help with further SEO improvements once the website is indexed by Google.

    The steps:

    • Clone Crisp React repository:

      git clone https://github.com/winwiz1/crisp-react.git
      cd crisp-react
      
    • Simplify the configuration by replacing the code fragment with the following code:

      /****************** Start SPA Configuration ******************/
        var SPAs = [
          new SPA({
            name: "index",
            entryPoint: "./src/entrypoints/first.tsx",
            ssr: false,
            redirect: true
          })];
      
        SPAs.appTitle = "Crisp React";
      /****************** End SPA Configuration ******************/
      
      

      Additionally replace "Crisp React" with your SPA title. It's important for SEO to have a unique and sensible title.

    • Review the client codebase to ensure each page sets the <title> HTML element and the canonical <meta> tag to the values that are meaningful for your website. This can be done by searching all the client/src/components/*.tsx files for the <Helmet> pattern and reviewing the relevant code:

      <Helmet>
        <title>{getTitle(pageName)}</title>
        <link rel="canonical" href={getCanonical()} />
      </Helmet>
      
      // Simplified code
      export const getCanonical = (pagePath?: string): string|undefined => {
        return !!pagePath? (window.location.origin + pagePath) : window.location.href;
      }
      // Simplified code
      export const getTitle = (pageTitle?: string): string => {
        return !!pageTitle? `${SPAs.appTitle} - ${pageTitle}` : SPAs.appTitle;
      }
      
    • Commit the changes:

      git add client
      git commit -m "Changed configuration"
      
    • Create a new GitHub repository by visiting repo.new.

    • Point the cloned repository to the newly created one and push it there:

      git remote set-url origin https://github.com/your-github-username/your-newly-created-repo
      git push
      
    • Deploy to Cloudflare Pages by logging into the Cloudflare dashboard and creating a Cloudflare Pages project.

      This step will take several minutes spent mostly on waiting. It should take around a minute to copy the data provided below and paste it into the single configuration screen presented by Pages.

      Use Menu > Pages > Create a project. You will be asked to authorise read-only access to your GitHub repositories with an option to narrow the access to specific repositories. Select the repository which you pushed to GitHub at the previous step and on the "Set up builds and deployments" screen, provide the following information:

      Configuration option Value
      Production branch master
      Build command yarn build:jamstack
      Build output directory client/dist

      Add the following environment variable:

      Environment variable Value
      NODE_VERSION 16.14.0

      Optionally, you can customise the "Project name" field. It defaults to the GitHub repository name and is used to create a subdomain e.g. <project-name>.pages.dev.

      After completing the configuration, click on the "Save and Deploy" button. When the deployment pipeline finishes, point a browser to https://<project-name>.pages.dev to check the website is online.

      Finally use the 'Custom domains' tab to map the deployed website to a domain you own.

    Requesting Google to Index the Website

    Add some original content to the webpages to avoid duplicate content.

    You can choose either passive approach and simply wait until Googlebot discovers your website or proactively ask Google to index it. If you choose the latter, use Google Search Console (GSC):

    • Add your custom domain to GSC using the "+ Add Property" menu.
    • Click on the "URL Inspection" menu to activate the URL Inspection Tool and type the path to the page you would like to index. The response will state that "URL is not on Google" telling you the page hasn't been indexed yet.
    • Click on the "TEST LIVE URL" button to get confirmation the page can be indexed. Optionally review the screenshot of the page rendered by GSC.
    • Request indexing for the page by clicking on the "REQUEST INDEXING" link. The response should say your request has been added to the priority crawl queue.

    The last 3 steps will have to be repeated for each SPA page.


    The deployed website lacks commonly used, though not strictly necessary for SEO files, such as sitemap.xml and robots.txt. Sitemap is more important for the passive approach. The robots.txt file is not needed unless you want to put some crawling restrictions in place.

    The website doesn't use all Crisp React features e.g. the ability to split a React app into multiple SPAs and selectively prerender the landing/index page of each SPA for better performance. If you need this functionality along with SEO, consider switching to full stack build or using a Cloudflare Worker as described in this article.