* The post was also featured @ tech.ftbpro.com
Single Page What?!
Not too long ago, FTBpro.com moved its website from classic web architecture (CWA) to a single-page application (SPA). For those who are less familiar with SPA, the major differences between the two are as follows:
- First client request:
- CWA: The server returns a full HTML page
- Subsequent requests
- CWA: The server returns a full HTML page
- SPA: The server returns only the data needed to display the request. Since all templates are already on the client side from the first request, no HTML/CSS/JS should be delivered.
To put it in simpler words – while in CWA the client gets a full HTML page on each request, in SPA the client gets all needed to be able to render the HTML by itself on the first page load so that subsequent requests might be carried in a very fluent and rich manner. It makes your website more of an application and less of a site.
Pros & Cons
- User experience is much richer and fluent.
- Lower server load: Because HTML rendering is done on the client and subsequent requests only require the specific data needed to render that page.
- Definitive separation between client and server – server acts more like an API.
- Caching: Since pages are rendered on the client side, you can’t cache full HTML pages on the server end. This also impacts CDN services as what they will cache will be only templates and not full pages.
- Performance: As said, SPA is heavily client-side dependent. It means that your site might load fast on some clients while being painfully slow on other. This becomes a large factor as old mobile devices might have a rough time rendering the pages and introduce large load times for your site.
We knew that we must build our web site as a SPA in order to stay relevant and provide our users with the best possible experience. We also knew we will have to overcome those disadvantages if we wanted to be able to keep our SEO rank high and our users happy. There are several solutions to these problems like PreRender but they are more focused around SEO solutions and less of the other two. We wanted a solution that would enable us:
- Be indexed by Google bots and other search engine crawlers.
- Cache full pages HTML on the CDN and on our server cache.
We figured out that what we really need is a hybrid between CWA and SPA: We want to return fully rendered HTML pages on the first request while subsequent requests behave just like normal SPA. If we could achieve that target, we will enjoy all the advantages of SPA with all the disadvantages eliminated since:
- Google bots and other crawlers will easily index our pages since the server returns fully rendered HTML pages.
- There is no problem to cache full HTML pages on the server and the CDN will also have a full HTML page cached.
- Since we render HTML pages on the server no additional work is needed on the client side (at least for the first request) for the page to be shown. This decreases the dependency on client hardware which mainly impacts old mobile devices.
How Does It Work?
- Client (user) requests a URL – assume it is /posts/3 from the web server
- The web server, via phantom_renderer asks phantom_server to render /posts/3
- phantom_server requests SPA version of /posts/3 from the web server
- The web server returns the SPA version to phantom_server
- phantom_server renderes the SPA to a full HTML page
- phantom_server returns a full HTML page to the web server
- The web server returns a full HTML page to the client
Let’s talk a little bit about each of the components and what are their roles in the system.
phantom_renderer is a ruby gem which integrates with rails controllers and communicates with the phantom_server in order to produce a full HTML page. It is responsible on two important things:
- Caching: when full HTML pages are returned by phantom_server they are being cached so that they won’t have to be produced again.
- Managing phantom_server responses: In case of bad (5xx) response from phantom_server a regular SPA page will be returned to the client. The same is applied if phantom_server does not respond in a given timeframe (configurable). This way, even if something bad happens to phantom_server we fallback to a regular SPA.
At the beginning we were having a lot of problems with PhantomJS processes over time: Some of their memory got bloated, some of them crashed and some of them just stopped responding. We had to come up with a solution to make those processes more stable or at least monitor them somehow. phantom_manager does just that: It manages a predefined amount of PhantomJS processes behind an NginX server. For example, when one PhantomJS process stops responding it will remove it from the Nginx Configuration and re-add it only after it raised another process instead. This way we keep a constant pool of PhantomJS processes which are always ready to render a page.
phantom_server is a collection of technologies making the whole rendering process happen. This is how it works:
- An NginX server listening to port 80
- phantom_manager manages PhantomJS processes on ports 80xx. It syncs every action it does on PhantomJS processes with the NginX Config.
- Each PhantomJS process runs a customized version of rndr.me
- Requests are delegated from NginX directly to PhantomJS processes on ports 80xx
The system has been up and running for about half a year now. It is all open-source and ready to deploy. If you have a SPA site, and experience the same issues as we did, give it a try. We will be more than happy to help with any setup issue you encounter.