How to handle SEO with a React SPA

Photo by JOHN TOWNER on Unsplash

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.

Sigh.

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.

By now, you must at least have heard about React. But, for the sake of clarity, we’ll define it as a component-based JavaScript library for building interfaces.

And what do we mean by Universal JavaScript? Well, it refers to apps where JavaScript runs on both client and server. This is great, both for performance in first-page load, and for SEO purposes, as we’ll see in a moment.

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

What’s the matter with SEO in React SPAs? Like many front-end frameworks, rendering is done dynamically with JavaScript. Search engine bots then have a hard time crawling the asynchronous content of our pages, resulting in lower SEO performances.

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.
  • Generating static files. This lightweight process performs the action of loading all your assets into a static HTML for the crawlers to enjoy. It only executes for pages that are requested by bots so they aren’t blocked by all the JavaScript. Otherwise (for normal users) everything is loaded as usual.
  • 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

Pre-requisites

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 withReduxStore with 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 prop.

This will give you access to the store in each top-level component.

Import 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.

The 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 prop.

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.

Sure, that was only three products with easy IDs to hard code here. But it wouldn’t be difficult to create the returned object dynamically with some specified format, since this is a JavaScript file where you can use any logic.

Before deploying this to any third party, let’s try it locally first.

Add the following scripts section to your “package.json” file:

Now run 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 serve.

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

  1. 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!
  2. 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.
  3. 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!
  4. 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.

Closing thoughts

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!

I originally published this on the Snipcart blog and shared it in our newsletter.

--

--