by Maxime LaBoissonniere
How to handle SEO with a React SPA
Not long ago, a developer friend of mine was telling me about a new e-commerce project for a client.
I would have loved to use a React.js app if only it wasn’t for all these SEO issues.
I thought I’d already convinced him with my earlier post on Vue.js that SEO with JS frameworks was manageable.
I guess not! So I started from scratch, and explained to my friend how to handle SEO with a React SPA.
Today, I’m putting my answer to him in words, using Next.js to craft a crawler-friendly e-commerce SPA.
In this tutorial, I’ll:
- create a Next.js project
- use the React/Redux bindings
- write and render products
- generate static Files for SEO.
- deploy my static assets with Netlify.
Before getting practical, let’s review a bit of theory.
What is Next.js?
In a nutshell, Next.js is a lightweight framework for static and server-rendered React applications.
Don’t confuse it with Nuxt, which is a framework for Universal Vue.js apps — actually inspired by Next. They share very similar purposes.
Next also has a cool set of features including automatic code splitting, simple client-side routing, webpack-based dev environment, and any Node.js server implementation.
No wonder big companies such as Netflix, Ticketmaster, and GitHub are already using it.
Handling React.js SEO
Search engine optimization is now a very competitive field, and small mistakes can cost your online business a whole lot of traffic.
Let’s see how we can fix this!
How can I verify if my SPA content is correctly crawled?
I suggest you run Fetch as Google from Google’s Search Console on every key page of your website.
The name is pretty self-explanatory, and you can use this tool to ensure that bots are finding your content. It’ll tell you:
- if it can, indeed, access the page
- how it renders it
- whether any of the page resources (images or scripts) are blocked to Googlebot
If you find out that JS dynamic rendering is causing any obstruction to search engine crawls, you can quickly act on it.
How do I make sure my content is crawled?
As I’ve already found out with Vue.js, there are a few solutions to this problem. In this case, the answer will come from Next.js.
You just have to determine the right approach for your specific needs:
- Server-side rendering. In an SSR setup, you’re offloading the rendering process to the back-end. Returned to the client are fully-rendered HTML views, easing the logic on the front-end. For this reason, this approach is great for time-sensitive apps.
- Third-party tools like Prerender SPA Plugin and Prerender.io also do kind of the same process as the latter, with great results.
For this demo, I decided to go with static files generation because it doesn’t require a server, which directly fits with the JAMstack logic.
To learn more about these rendering approaches, watch this thorough video tutorial. It was done for Vue.js, but the concepts are applicable to React.js.
Next.js Tutorial: Handling SEO on a React.js SPA
1. Creating the project’s structure
Let’s start from scratch here. Create a new folder where it pleases you and run the following commands:
Now that we have the required dependencies, let’s write the actual file structure.
root ├───components ├───lib └───pages
2. Mocking a real project’s architecture with Redux
I want to make this demo as real as possible. So even though it will feel a bit contrived for our use case, I decided to use Redux with the React/Redux bindings.
Later I’ll declare the store with products as the initial state, but not any actions and reducers. This is only done so you get as close as possible to a real-life architecture.
Hop into the pages folder and create a “_app.js” file, which is a brand new addition to Next—it'll only work if you are using version 6 and higher.
It enables page transitions, error boundaries, and more. In my case, I’ll use it to write a new app format that uses the React/Redux provider, so that it injects the Redux store in my components.
Please note: this architecture is highly inspired by this Next with Redux demo.
Here’s the content of my file:
As you can see, I provide the store to my
Provider and relay the current page props to the current component.
I don’t export the component directly, but I call
MyApp as a parameter instead. You'll have to craft this function, as it doesn't exist at the moment.
This is definitely the most complicated function. For the purposes of this post, I won’t explain it thoroughly as it’s a little bit more advanced, and the complex section only serves if you were to use server-side rendering. As we’ll generate static assets, it should all be okay.
So, hop in your “/lib” folder and create a “with-redux-store.js” file with the following content:
Basically, this checks if the app is running on a server or in the browser, then decides whether to serve a new Redux store instance or the current one. Once it’s determined, we give it to the
App component as a
This will give you access to the store in each top-level component.
initializeStore, which is the last piece of data control needed before jumping into products. Make a “store.js” file directly in the root folder.
As mentioned earlier, the store is bare bones. It really just instantiates with an initial state, but doesn’t provide anything else. You’ll still be placing products there, as it gives a realistic feel of the way to access data in the components we’ll define in the next step.
3. Writing and rendering products
For this demo, we want two different components, a
products.js that will render a link to each product, and a
product.js that will show the details of each product.
Write each of these in the “components/” folder.
products one will be a bit more complex, as it needs to access the Redux store. But they both remain simple as they are functional components. They only render what they are given as
props, thus they can be solely represented as a function without extending anything.
Here’s the “products.js” component:
And now here’s “product.js”:
Now that you have these two components, you need to use them in routes so they can render data.
To do so, generate two new files in the “pages” folder, “index.js” and “product.js”.
The first one goes as follows:
What React/Redux does is, instead of letting you access the Redux store directly, it gives you a
connect function. This gives you a way to map a
state part to a
prop. Although not done here, it can also give you a way of mapping a dispatching function to a
This isolates entirely the logic from the presentation layer. This really is an interesting way of doing things. This approach is heavily influenced by functional programming, as you can easily split all the dependencies of a component directly in its props.
If you want to read more about this, there’s a neat article here that introduces these concepts.
With this principle in mind, define the second file as:
This way, components stay “pure”, meaning that they don’t handle the filtering logic or the fetching logic. They simply get data and show it.
I also added Snipcart’s necessary scripts. The head component of “Next.js” is a simple wrapper. Everything put in there will be bundled inside the head tag of the rendered page.
I also included some meta tags here. In this case, I used “title” and “description”, which you should fill with researched keywords. Even if they won’t appear on-page, they’ll help crawlers understand the content of your page.
For SEO, it’s also important to know about meta name= “robots”. You’ll use it in bigger projects where you have internal pages accessible after login, or any other page you don’t want to get indexed. Read this to learn all its attributes.
4. Generating static files for React SEO
Pages are created dynamically by using the Redux store data. You’ll need to provide some paths to Next so it knows what routes to generate when creating the static files.
To do so, generate a “next.config.js” file directly in your route folder:
I really like how simply it’s handled. No need to hook up to the build process, a simple JS file lets you do it.
Before deploying this to any third party, let’s try it locally first.
Add the following
scripts section to your “package.json” file:
npm start in your project's root folder. This will start Next as a server, and you should be able to access everything at “http://localhost:3000”.
If you want to generate your static files instead, you can do so using
npm run export. This will generate an “out” folder where you will find these. If you want to host these locally to test the output, you can do it quickly with an npm package such as
5. Deploying the static assets
You’re now ready to deploy the website. Let’s use Netlify.
First, you’ll need to push your code to a Git repo, then hop in Netlify’s dashboard.
There, choose to create a new site with the following configuration:
It’s worth noting that the creators of Next also have a deploy product called Now. It lets you deploy static assets after build-up directly from your terminal, with a single command. It’s really neat, however since I’m already using a Git repo to show my code, I decided to stick with Netlify.
Live demo and GitHub repo
See the live demo here.
See the GitHub repo here.
Other important general SEO considerations
- Mobile-first indexing is now one of the primary ranking factors. So much so that you should take care of your mobile experience as much the desktop one — if not more!
- If you’re not aware of the importance of an HTTPS connection, I recommend you look into it ASAP. I’m hosting this demo on Netlify, which provides free SSL certificates with all plans.
- To up your SEO game, you’ll want to craft great content. You also want to be able to easily edit and optimize it. For content editing purposes, consider throwing one of these headless CMS into the mix!
- Don’t forget to add the appropriate meta tags, as we’ve seen earlier. A sitemap of your app pages is also very relevant here. You can find a great example on how to build a sitemap for Next.js projects here.
Playing with both Next.js and React was really fun. I didn’t face any major challenges, as everything was answered pretty straightforwardly in their docs, which is really thorough. I like the ‘’quiz” approach it has.
It took me around two hours to build the whole thing. It took a bit more time using the
react-redux binding: I wandered for a while in their examples to clearly understand what was happening.
To push all of this further, it would be fun to put together a store with more products to generate the
pathMap dynamically. I also wonder to which point the build process becomes bloated if you have too many routes to export. If you have the answer to this let me know in the comments!
With creativity, great coding, and a thoughtful care for SEO, nothing should stand in the way of your next projects!
If you’ve enjoyed this post, please take a second to share it on Twitter. Got comments, questions? Hit the section below!