When we built RapidAPI, we wanted to reap the functional benefits of a single page application (SPA). However, we ran into some challenges with how search engines processed and indexed our website. We found a solution by using isomorphic JavaScript. Here’s how we did it.
Origins and Benefits of the SPA
First, we want to fully explain the benefits of an SPA. To do that, we’ll have to go back in time a little bit. Since the beginning of the Web, browsing the internet has worked like this:
- The web browser requests a specific page
- The server generates a HTML page
- The server sends the HTML page back to the client
In the past, this process has worked well because the client side was mostly static. Rich applications that used JavaScript runtime and HTML5 standards were only possible on native platforms. Here’s what that classic approach looked like:
Classic approach
With time however, the browsers became more powerful. JavaScript enabled developers to create more dynamic pages, slideshows, widgets, etc. This new functionality gave birth to a whole new family of applications: the Single Page Applications (SPA).
The SPA allowed developers to build apps entirely in the browser. No more making a round trip to the server just to render a new page! Now, the apps could run a lot faster without loading HTML pages on every request.
SPA approach
Pretty great, right? We’re not the only ones who think so. SPA functionality allowed developers to build apps like Gmail, Twitter and Facebook. No big deal.
The SEO Problem with SPAs
While the potential of SPAs is promising, actually implementing them can be tricky due to search engine optimization (SEO). Search engines are still the most popular way to discover new products and find information.
Because of the way SPA and Google Bots work, Google will probably never be able to properly index a SPA page. Why? The reason is simple.
This is a SPA:
This is how Google sees a SPA:
Why the discrepancy? Once the page loads, Google sends a request to get the initial data. The request takes only a few milliseconds. This tiny amount of time is enough for Google to get what it needs to index most websites. In most cases, Google won’t wait for a response from the server.
For an SPA, the page will load empty at first, then request data from the server. Unfortunately, that takes more time than the few milliseconds Google spends on the page. Therefore, Google gets an incomplete picture of the full SPA. Damn.
The Solution: An Isomorphic Hybrid Approach
The SEO issue above is a very common, but there isn’t a standard approach to solving it. Yet. One solution developers came up with over time was a hybrid approach – combine the classic approach with the new SPA approach. These hybrid solutions are called Isomorphic (or Universal) apps.
The idea is simple. When the app loads for the first time – the response should be a fully rendered page with its data. Any other request will return a JSON which will be rendered on the client side.
At RapidAPI, we’re working with React + Redux for the frontend and Node.js for the backend. There are pretty much a thousand different ways to accomplish this task, the whole thing is a one huge jungle.
If you’re trying to build a similar solution, here are a few libraries that helped us out a lot:
- React-dom (https://www.npmjs.com/package/react-dom): A few months ago, React and React-DOM were separated into different projects. React-DOM allows us to work with the DOM, render React components, and more. React-DOM/Server allows the same, but on the server-side. It helps return a rendered HTML from within your components.
- React-Router (https://www.npmjs.com/package/react-router): This is a must-have library for working with routes. The most interesting part of this library for isomorphic apps is the match function. The match function matches a set of routes to a location without rendering, and calls a callback when it’s done. That means that you don’t need to write the routing twice (once for backend, once for frontend).
- Redux-Async-Connect (https://www.npmjs.com/package/redux-async-connect): Redux-Async-Connect is a real-life server that allows you to write a function that will run before the component actually loads. This way, your components will be able to fire an action and get the state before the actual rendering.
I would definitely recommend these libraries as you start your work.
Caution! The Ugly Part!
This solution isn’t necessarily a clean one. In Redux, the state should reflect your app status. If your initial state already has data (Rendered HTML Pages), the data should also be in the state. Currently, the accepted way to do it is by rendering of a global variable inside a <script> tag and reading it to the actual state once the SPA loads. For example:
Server Side:
<script type="text/javascript" charset="utf-8"> window.__REDUX_STATE__ = '<%= reduxState %>'; </script>
Client Side:
let reduxState = {} if(window.__REDUX_STATE__) { try { let plain = JSON.parse(unescape(__REDUX_STATE__)); _.each(plain, (val, key) => { reduxState[key] = val; } } catch(e) { } } const store = configureStore(reduxState);
Even though this solution is a little messy, it’s worth it because it’s the only way we have so far. Hopefully, in the near future, a cleaner solution will come from Google itself.
Conclusion
The above way is just one of many ways in which you can build an isomorphic app. I found this one to be the most clean and simple for our application. You can find a lot of materials, boilerplates and libraries on this repo: https://github.com/enaqx/awesome-react.
If you’ve gotten around this issue or found other resources useful, let me know in the comments below. If you want to check out our SPA, head over to RapidAPI.com to find, test and connect to APIs (with code snippets you can export right into your code). Feedback always welcome :).
Frisco Web Solutions says
That’s really nice post. I appreciate your skills, Thanks for sharing.