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:
yarn create react-app my-cra
cd my-cra
yarn add react-router-dom
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>
);
}
src\Page1.js
:import { Link } from "react-router-dom";
const Page1 = () => {
return (
<>
<h1>Page1</h1>
<Link to="/test">To Page2</Link>
</>
);
};
export default Page1;
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.
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:
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.
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.
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):
"TEST LIVE URL"
button to get confirmation the page can be indexed. Optionally review the screenshot of the page rendered by GSC."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.