Server-Side Rendering (SSR) with React and Flask

Olayinka Otuniyi
The Startup
Published in
4 min readJan 12, 2020

A well known downside of client-side rendered React applications is that, certain webpage content’s are not usually indexed or discoverable by search engines depending on, if the JavaScript file(s) is executed by the engine and how the application itself is designed. This usually results in the React components not being rendered into the HTML file, which means the page will not be returned when certain keywords are queried in search.

This is because React-based apps are designed as Single Page Applications (SPA) which makes use of Routes, whereby the pages are not separately anchored files, instead, the contents are replaced in the one single file dynamically with JavaScript.

This is when developers might consider implementing server-side rendering and or other Search Engine Optimisation (SEO) techniques as a way of guaranteeing that the search engine crawler detects every content of the application and thereby increasing the chances that their websites are returned as at least one of the search results.

There are a few SSR or Prerendering frameworks out there but some of these appear very bulky with many steps required to implement them and I have found that SSR itself, doesn't appears to be a widely discussed topic, which can be very discouraging if you are wanting to give it a go. But here is one of the many ways of approaching SSR but this time with python.

In this example, I use Flask as my preferred SSR framework, in which I create a simple server to render my application on the server-side with the renderToString() method and use the hydrate() method on the client-side (both available in React>=16.X.X), while using webpack to bundle the entire application.

The application structure ReactFlask/
├── __init__.py
├── config.py
├── server.py
├── templates/
│ ├── __init__.py
│ ├── public/
│ │ ├── __init__.py
│ │ ├── components/...
│ │ ├── index.html
│ │ ├── index.tsx
│ │ ├── routes_server.js
│ │ └── routes.tsx
│ ├── register_server.js
│ ├── src/
│ │ ├── __init__.py
│ │ └── views.py
│ ├── static/css/|fonts/|imgs/|js/
│ └── tsconfig.json
└── webpack.config.js

Like every other Flask application, I start by creating a basic server file.

#__init__.py
from flask import Flask
from .src.views import src_blueprint
app = Flask(__name__)app.register_blueprint(src_blueprint, url_prefix="/src")

In my src/view.py, I define the location for my static contents, paths/routes available in my application and define a list of command line arguments. These arguments triggers a subprocess which opens a shell and runs the command:

$ node [path][file] requested route #veiw.py 
import os
from flask import render_template, Blueprint, request
import subprocess
src_blueprint = Blueprint("src", __name__, static_folder="../static/", template_folder="../public")@src_blueprint.route('/', methods=['GET', 'POST'],defaults={'path': ''})
@src_blueprint.route('/<path>', methods=['GET', 'POST'])
def index(path):
command_line_args = ['node', os.path.join(os.getcwd(), "templates", "register_server.js"), request.path]
process = subprocess.Popen(command_line_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
context = process.communicate()[0].decode('utf-8').strip()
return render_template("contents.html", react_context=context)

The file register_server.js called in view.py has a babel-node loader type configuration required to load React type project s— @babel/preset-react. In the file routes_server.js I pass the command line arguments from view.py, which then renders the requested route by using nodes process.argv.

//routes_server.js
import React from "react";
import { renderToString } from "react-dom/server";
import {StaticRouter, Routes} from "./routes";
if (process.argv[2]) {
const url = `/${process.argv[2].split("/")[2]}`;
const content = renderToString(<StaticRouter location={url}><Routes /></StaticRouter>);
console.log(content);
}

The resulting string from the renderToString() method, is then rendered into an HTML file using the jinja {{rendered|safe}} template with Flask.

{% extends "index.html" %} {% block title %}- Flask{% endblock %} {% block head %} {{ super() }}{% endblock head %} {% block content %} {% include "header.html" %}<div id="root">{{react_context|safe}}</div>{% include "footer.html" %} {% endblock %}

The webpack bundle which uses index.tsx as its entry point, its output is also imported into this file, initially as a blank file, rather than directly into the HTML file, to ensure both hydrated client bundle and server request return the same content and avoid a mismatch error.

Resulting SSR Page

Depending on the complexity of your application, you should ask yourself if SSR is really the right way to go as it can be quite difficult to implement, for example when Redux is used. Google Search Console for instance, is a testing tool which, like the search engine, uses clever bots to crawl through the contents of your application. But if you do wish to give this approach a go, here is a repo of this sample app. I hope it helps!

--

--