Optimizing Wildfly + Angularjs Single Page Applications For Faster Load Time
When we created gozoomo, we wanted to give the best end-user experience for our customers. For gozoomo.com, our production servers were
most a set of JSON endpoints and a place to serve static files. But using this stack also has a cost. We had a simple angular stack. Consequently, our servers
Stage1: Serve HTML with partially filled body for SPA using widlfly
Rendr was kind of close to what we wanted. But we did not wish to change our application from Java and Clojure to nodejs. So what we wanted was a mechanism for me for to render a part of my HTML on the server side and make the rest on the client side. My first instinct was to use the managed version of Prerender, which I was already using to serve search engines and other bots. I first tried using the java-prerender filter to route my requests to prerender.io. But I faced an issue, the filter was too particular for SEO purposes. There was no way for me to serve all HTTP requests, but those originating from prerender, using prerender. Consequently, I used AsyncHttpClient with a timeout to route requests to prerender depending on the user agent.I did not want my users to have to wait too long. In case, prerender did not serve the response within a reasonable time limit, I needed a fallback mechanism.
Stage2: Fork Prerender and host locally to serve required HTML
It worked like a charm, but there was a problem. Prerender used to strip the script tags, from generated HTMLs. SEO haves have no need of the script tags but my customers sure needed them.
Prerender open sourced their library for all these use cases. They have a very nice modular structure to add and remove plugins.
I ran an instance of Prerender on my machine after disabling the removeHtmlScript plugin. Voila! I had unlocked my first achievement. Now, generating HTML using phantomjs is pretty time consuming. Hence, the next logical
step is using a form of caching. One good developer had already written a plugin for this. All I needed to do was change the
package.JSON and enable the plugin. By this time, I was using
Foreman to run prerender. Hence, you also need to add
REDIS_URL="redis://seller.carcredible.com:6379" to my .env file. The average time required
to serve HTML reduced to under 8 ms and also made my pre-cached website functional.
Stage3: Optimizing initial HTML and handling things asynchronously
Stage4: Handle js exception and ignore parts of angular generated HTML
Optimization Step2: Eliminate render-blocking css in above-the-fold content
Stage1: Find the CSS actually used in the HTML
CSS was arguably more tricky for me. As a rule, we only write SCSS and not CSS. As a rule, we do not like to duplicate code. As a rule, we do not write inlineCSS. But Google recommendations
were asking us to write part of the CSS inline. Coming back to the surfing analogy, this was a problem faced by many people, and some great libraries had come up with takes an HTML string and CSS
file as input and emits the CSS part required. I used UNCSS to find out the CSS required by my cached HTML and then used
cleanCSS to minify the CSS. Since I had already tweaked prerender plugins, packaging the logic in a prerender plugin was straightforward.The code snippet used is below.
Stage2: making CSS async
Optimization Step3: Enable Compression
Most modern browsers supports “gzip” and it is better to make use of this compression. Since we were using a standard undertow and wildfly, this was simple. I had to add the below entry in standalone.xml. The filter also took care of minifying HTML.
Optimization Step4: Leverage browser caching
Browser caching was also pretty straightforward, and all I had to do was add a cookie. This cookie ensures that before sending a request to the URL, the browser checks for it in its local cache and avoids unnecessary trips to server.
Optimization Step5: Viewport Issues
Viewport issue more of a bug. One carousel we were using had “overflow:visible” as CSS. The overflow was causing part of HTML to go out of the viewport.I just had to take care of the CSS.
I could not hit 100% because of segment libraries. But the results were damn good. To say thanks to all open-source contributors who have made this project possible. The code is open sourced @prerender-fork