Implementing serverside rendering
By: Maarten Blokker
Tags: V8, React, JavaScript, Universal Javascript, Server Side Rendering

Refactoring the frond-end

It is quite easy to break the whole server-side rendering process. Whenever a piece of JavaScript references anything browser related, an error is thrown, and you are left with only client-side rendering. We had to make considerable changes on our front-end code base, because the code was never written with the assumption that it would run without a browser. All of our DOM-logic had to be moved to different locations in the code, outside of the rendering path.

To make the code usable from an embedded JavaScript runtime, we bundled all of the code together using Webpack. Some of our third-party components were still manipulating the DOM after we finished refactoring, so we had to exclude these from our bundle using the normal module replacement plugin in combination with the noop-loader. Now we could finally render our components 😌.

Rough approximation of how our webpack setup looks like

And the entrypoint that has the render functions

Nashorn

Our initial implementation was with good old Nashorn. This JavaScript engine came bundled with Java since version 8 and has much better performance than it’s older brother “Rhino”. Nashorn compiles JS programs to Java bytecode classes, and after continued invocations, this bytecode is further optimised to native machine code.

The biggest issue, and the reason why we eventually switched over to a different implementation, is that Nashorn has a warmup time. Server-side rendering was implemented by starting up a few JavaScript processes, which evaluate our front-end code into memory. This evaluation phase took a long time, in the realm of 5–10 seconds before being ready to serve pages 🐢. After evaluation was done, Nashorn still needed to build up code cache to execute JavaScript at acceptable speed. This evaluation and optimisation needed to happen every time we did a new deployment.

V8

Our second round of server-side rendering was with Google’s open source high-performance JavaScript engine V8. We used the native bindings provided by the Eclipse foundation, found here. Using a native implementation sounds like the right way to go, if you’re looking for performance.

Implementing an embedded javascript engine with J2V8 is pretty straight forward, although there is one caveat. If the native library has any issues, you can get segmentation faults that the JVM can’t recover from, and the webserver crashes. Scary stuff 🙀.

This is how you could embed V8 with the J2V8 bindings

We did extensive testing with the V8 engine before we felt confident enough to run it in production. We started out with load testing, in the graph you can see the difference between Nashorn and V8. The warmup time Nashorn has is really visible in this graph (around 20:00–20:15), no warmup needed for V8.

Load testing our serverside rendering implementations

After load testing for longer periods, we rolled the V8 engine out to a single production server, and results looked promising. In total we saw a drop of over 30% in our response times 🔥🔥. Over the course of the next month we rolled V8 out to the other servers, and still use it to date.

Performance comparison of front-end servers (prod-publisher-1 using V8)

Graal

We might go for a round three with Graal, this framework aims to create a universal virtual machine that supports any language. The Javascript support in Graal is quite advanced, there’s debugging with Chrome Developer Tools, profiling using VisualVM and the performance is a lot better compared to Nashorn. It’s still not as fast as the V8 engine, but fast enough to replace the scary V8 native binding. If you’re interested, we wrote another article on Graal about using “ahead of time compilation” to speed up microservice launch times.