What is prerendering? Pre-rendering a JavaScript heavy website refers to the process of using a proxy that loads the requested page and runs all JavaScript on the page, and then returning the content of the rendered page as the response to the page's request. The advantages of doing so are twofold:
Perfomance of the site is greatly improved as the browser is able to render the page without waiting for external scripts to load and external data api requests to finish. In Brickwork client site that means requesting a store page returns HTML that is already rendered with all the data about the store.
After the initial page HTML is rendered from the response, the browser asycronously request JavaScript scripts that load in the background, query the API for data and re-render the page in browser, adding interactivity to the page. Ideally the page pre-rendered on the server/proxy and the page rendered in the user's browser will be exactly the same (given the api data hasn't changed). But even if the api data is different, the user will initially see slightly older data, which will be replaced once page's JavaScript and data load.
Prerendering is also beneficial for all kinds of bots. The bots will see plain HTML, with JSON-LD and other meta-data present, making the page compatible with a wide range of bots (Slack, Facebook, Twitter, Yandex, Google etc).
While Google bot has the ability to render JavaScript while crawling, one must presume there is some kind of timeout that limits the time Google allows the web-page to render.
Essentially all tech in this space uses either phantom.js engine (webkit based headless browser) or Chrome engine (headless Chrome engine).
Prerendered (https://prerender.io) runs a hosted pre-rendering service and open source projects for the server (pre-rendering proxy) https://github.com/prerender/prerender and various middleware libraries that allow piping web-requests through prerenderer proxy. Prerenderer server internally manages phantom.js workers that load and render urls from requests. Using the server is pretty straight-forward:
- Request to http://stores.client.com/en/slug hits the server
- Server (through prerenderer middleware) proxies the request to prerenderer server, to http://prerenderer.brickworksoftware.com/?url=http://stores.client.com/en/slug
- prerenderer request http://stores.client.com/en/slug (with different headers, not to get to infinite loop)
- Server returns HTML that contains lots of JS scripts
- prerenderer drives phantom.js to fully render that html and execute all JS scripts that dynamically modify the page.
- prerenderer returns the resulting html to the server, which returns it to the browser (or bot)
In Brickworks case this can be architected two ways:
- Configure AWS CloudFront distribution to use prerenderer proxy as the origin server. Browser <-> CloudFront <-> prerenderer <-> Rails app. No modification to Rails app required.
- Configure Rails app to use pre-renderer through middleware. Browser <-> CloudFront <-> Rails App <-> prerenderer
Prerenderer can be deployed either as AWS Beanstack application, or Heroku appliation (its a simple node.js express app).
- (Done) Does this architecture with pre-rendering actualy work? What are the unknown unknown? Need to implement a very simple prototype with prerenderer on heroku
- How exactly to plugin prerenderer in the CloudFront <-> app interaction? Can we configure CloudFront to proxy through renderer passing right parameters? Can we use Lambda@Edge to hook into CloudFront request life-cycle?
- How to host prerenderer to make it scaleable? Beanstock/Heroku/Serverless Lambda?
- Can pre-renderer be used by Brickwork clients should they decide to host Asiago JS widgets on their site?
- Distribution needs to be configured to pass Host header to the origin, so the rails app knows which client to render.
- Phantom.js does not support WebGL. WebGL is used by SearchMap component which used react-mapbox-gl module. Need to test if WebGL is awailable and conditionally render react-mapbox-gl map.
- Running prerenderer server locally on OSX fails, ended up running it in docker container (https://nodejs.org/en/docs/guides/nodejs-docker-webapp/)
docker build -t kmamyk/prerender .
- Add an unused address (e.g. 10.200.10.1) to lo0 interface to be accessed from the container (see https://docs.docker.com/docker-for-mac/networking/)
sudo ifconfig lo0 alias 10.200.10.1/24
docker run --rm --name prerender -p 49160:3000 --add-host="host.local:10.200.10.1" -it kmamyk/prerender
curl "http://localhost:49160/http://host.local:3000/"
- use renderer to pre-render http://host.local:3000/docker stop prerender
- Debugging notes:
echo "Hello World" | nc -l 10.200.10.1 4321
makes nc listen on IP and port, and return Hello World to any requests.
- http://asiago2.demoretailer.com/en/sitemap.xml has rails structure of urls. Need to either tweak asiago to support this structure or render different sitemap for asiago...
- https://github.com/justengland/phantom-lambda-template
- http://docs.aws.amazon.com/lambda/latest/dg/automating-deployment.html
- https://github.com/awslabs/serverless-application-model
- http://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html
- https://www.npmjs.com/package/phridge
- https://aws.amazon.com/blogs/aws/coming-soon-lambda-at-the-edge/