Introduction

Rendora is a Go-based SSR solution that works like a proxy server, enabling server-side rendering for SPA projects such as Vue, React, and Angular without modifying existing project code.

Architecture

Main features:

  • Simple configuration
  • No need to modify the original project
  • Redis cache support
  • RESTful API provided
  • Prometheus monitoring support
  • Support for rendering any route page
  • Not limited to frontend frameworks and technologies used
  • Does not render invalid resources (such as images, fonts, and CSS) to speed up DOM rendering
  • Search engine crawlers and regular users get the same data (thanks to Chrome API)

In fact, Rendora’s position in the architecture can be defined by yourself. It can be placed at the front end or behind Nginx, which means making decisions at the Nginx layer to determine whether the request is from a user or a crawler.

Deployment

Running Headless Browser

# Can be run on Docker
$ docker run --tmpfs /tmp --net=host rendora/chrome-headless

# If Google Chrome browser is installed on Mac, you can run it as follows
$ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --headless --remote-debugging-port=9222

Required Configuration

target: # The address requested by the headless browser
  url: "http://10.0.8.100:8080" 
backend: # The host address of the original SPA page
  url: "https://spa.example.it"

Note: target.url is the address to which the request is forwarded when it is allowed to be rendered through Chrome headless, and backend.url defines the address to which the request is forwarded when it is not allowed to be rendered through Chrome headless.

Configuration Description

listen: # Set the binding address and listening port of the Rendora service (optional)
  address: 0.0.0.0 # Default 0.0.0.0
  port: 3001 # Default 3001
cache: # Set the driver for caching (optional)
  type: redis # Supports two caching modes: local and redis, default is local
  timeout: 6000 # Cache lifetime in seconds
  redis:
    address: localhost:6379
    password: PASSWORD # Redis password
    db: 0 # Default uses 0
    keyPrefix: Key prefix # Default value is __:::rendora:
target: # The SPA address for the headless browser, requests will be forwarded to this address when SSR rendering is required
  url: "http://10.0.8.100:8080" 
backend: # The host address of the original SPA page
  url: "https://spa.example.it"
headless: # Headless browser configuration
  waitAfterDOMLoad: 0 # Timeout to wait after the initial DOM load event (in milliseconds), default value is 0
  timeout: 15 # Specifies the timeout for Rendora to wait for the headless browser response, if no response is received within this time, a 500 status code will be returned to the client, default value is 15
  internal: 
    url: http://localhost:9222 # Specifies the address of the headless browser, default is http://localhost:9222
  blockedURLs: # Define resources that do not need to be loaded, can use wildcards or absolute URLs
    - "*.png"
    - "*.jpg"
    - "*.jpeg"
    - "*.webp"
    - "*.gif"
    - "*.css"
    - "*.woff2"
    - "*.svg"
    - "*.woff"
    - "*.ttf"
    - "https://www.youtube.com/*"
    - "https://www.google-analytics.com/*"
    - "https://fonts.googleapis.com/*"
output:
  minify: true # Whether to compress HTML
filters: # Define filters
  userAgent:
    defaultPolicy: blacklist # Define default policy: whitelist or blacklist, default value is blacklist
    exceptions: # Used to define exceptions, if defaultPolicy is whitelist, then what is defined here will be blacklist
      keywords:
        - bot
        - slurp
        - bing
        - yandex
        - crawler
    exact: # Can filter specific browsers
      - Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36
  paths: # Only check paths after checking the request user agent and passing its filter
    defaultPolicy: whitelist
    exceptions:
      prefix:
        - /users/
      exact:
        - /api/
debug: false # Whether to enable Debug mode
server: # Configuration for Rendora API
  enable: false
  listen: 
    address: 0.0.0.0
    port: 9242
  auth:
    enable: false
    name: X-Auth-Rendora # HTTP authentication header name
    value: '' # Define the token used for authentication

Installation

Docker

$ docker run --net=host -v ./CONFIG_FILE.yaml:/etc/rendora/config.yaml rendora/rendora

Note: Currently, Rendora services built through Docker cannot connect, and someone has already raised an Issue on Github

Source Code

$ git clone https://github.com/rendora/rendora
$ cd rendora
$ make build
$ sudo make install
$ rendora --config CONFIG_FILE.yaml

Scenario Analysis

Rendora as the front-end reverse proxy

For many people, because they haven’t formed a concept, understanding the difference between target and backend is a bit difficult. I was also very confused at first, but through multiple tests, I understood the role of the two settings. Let’s simulate a scenario to explain the configuration’s function.

target: # The address requested by the headless browser
  url: "http://10.0.8.100:8000" 
backend: # The host address of the original SPA page
  url: "https://spa.example.it"
headless:
  waitAfterDOMLoad: 10
  timeout: 5
  internal:
    url: "http://localhost:9222"
output:
  minify: true
filters:
  userAgent:
    defaultPolicy: blacklist
    exceptions:
      keywords:
        - bot
        - slurp
        - bing
        - crawler
debug: true

For example, in the configuration above, target.url points to the internal SPA project address, which is the SPA module in the diagram above, and backend.url points to what is accessed through Nginx.

Thoughts

From the architecture diagram, we can see that the principle is actually very simple. It’s just through a middleware-like service that calls Chrome’s API, letting Chrome render the page and then return it to the crawler. So we can also implement the same functionality through Nginx with Lua.

I hope this is helpful, Happy hacking…