I presented this at Austin ReactJS meetup (slide deck).
Most commonly a ReactJS application is powered by a NodeJS server on the backend. A ReactJS application can be deployed without needing NodeJS – by using Client Side Rendering (CSR). But even a ReactJS application that is fully rendered on the client-side is usually hosted on a full-blown web application server such as Tomcat. We could take this a step further to make a ReactJS application not necessarily need a full-blown web application server, as long as we have HTTP server endpoints on the backend to serve the required static and possibly also dynamic resources. The term HTTP server endpoint is used here as strictly referring to an implementation of the HTTP protocol, and it may or may not have additional capabilities such as infrastructure for session tracking that some web servers provide. In this post, we will look into some possibilities for motivation of such a design, some implementation techniques, and where it might start to fall behind.
Motivation for CSR
A ReactJS application can be rendered on the server-side or the client-side. Most often while choosing between the two, their performance characteristics are compared. The common notion is to use server-side rendering when Search Engine Optimization and the initial page-load times are critical for your site, although both of these are becoming less of a problem with CSR over time – search engine crawlers can now run JavaScript, and the initial load-time can be minimized with code splitting.
There is another reason why CSR could be more preferable – it allows you to build a ReactJS frontend for your backend server without you having to run NodeJS on it or even having to run a full-blown web application server. All the client-side rendered ReactJS application needs is an endpoint on the backend server that could talk HTTP. This could have several advantages such as: the backend server needs lesser memory, has lesser and hence easier to manage dependencies, and this in turn leads to faster deployments.
Implementation
We assume the project was created with Create React App as an example here, but the concepts for serving content should apply to other approaches as well.
- Build the project using
npm run build
, which generates static assets in thebuild
directory under your main project directory - Assuming you have set
homepage
in package.json to “/ui” as an example, implement routing on the HTTP server endpoint such that:HTTP GET /ui
returnsbuild/index.html
HTTP GET /ui/static/*
returnsbuild/static/*
- Additionally, if you are using client-side routing such with BrowserRouter, implement additional routing such that
GET /ui/*
returnsbuild/index.html
– this ensures that the routing correctly works, rather than resulting in a HTTP 404 error upon navigating
- Make sure the response has correct
Content-Type
andContent-Encoding
- Set
Content-Security-Policy
as appropriate, and additionally:- You may use
proxy
setting to proxy requests on your local development server. This is useful when you have the static assets served by node’s development server and the dynamic requests served on a different port
- Set
INLINE_RUNTIME_CHUNK
tofalse
to prevent embedded scripts in index.html
- You may use
Limitations of a stateless HTTP server endpoint
A client-side rendered application built without a full-blown web application server could hit its limits pretty quickly. One example could be “session management”. HTTP is stateless. Most web application servers provide session management capabilities, and some control over the cookies used for exchanging the session identifiers. This begs the question whether it is worth implementing a session management system on top of the HTTP server endpoint or if it time to re-evaluate on the decision not to use a full-blown web application server. Implementing session management may not be all that difficult, and it may not even be necessary if we consider other forms of authentication for our frontend application, such as JWTs.
Conclusion
Client-side rendered ReactJS applications present interesting use-cases. They could provide a nice frontend for a backend application that talks HTTP without having to run a full-blown web application server, e.g. provide monitoring capabilities. This could be especially useful in case of legacy applications that implement the HTTP protocol already and do not want to include a web application server alongside for reasons such as to prevent increased operational complexity and burden.