dynamic server-side rendering using headless Chrome to effortlessly solve the SEO problem for modern javascript websites

Rendora

Rendora

Go Report Card CircleCI GoDoc License Join the chat at https://discord.gg/6yyErk8

Rendora is a dynamic renderer to provide zero-configuration server-side rendering mainly to web crawlers in order to effortlessly improve SEO for websites developed in modern Javascript frameworks such as React.js, Vue.js, Angular.js, etc... Rendora works totally independently of your frontend and backend stacks

Rendora's Diagram

Main Features

  • Zero change needed in frontend and backend code
  • Filters based on user agents and paths
  • Single fast binary written in Golang
  • Multiple Caching strategies
  • Support for asynchronous pages
  • Prometheus metrics
  • Choose your configuration system (YAML, TOML or JSON)
  • Container ready

What is Rendora?

Rendora can be seen as a reverse HTTP proxy server sitting between your backend server (e.g. Node.js/Express.js, Python/Django, etc...) and potentially your frontend proxy server (e.g. nginx, traefik, apache, etc...) or even directly to the outside world that does actually nothing but transporting requests and responses as they are except when it detects whitelisted requests according to the config. In that case, Rendora instructs a headless Chrome instance to request and render the corresponding page and then return the server-side rendered page back to the client (i.e. the frontend proxy server or the outside world). This simple functionality makes Rendora a powerful dynamic renderer without actually changing anything in both frontend and backend code.

What is Dynamic Rendering?

Dynamic rendering means that the server provides server-side rendered HTML to web crawlers such as GoogleBot and BingBot and at the same time provides the typical initial HTML to normal users in order to be rendered at the client side. Dynamic rendering is meant to improve SEO for websites written in modern javascript frameworks like React, Vue, Angular, etc...

Read more about dynamic rendering from these articles by Google and Bing. Also you might want to watch this interesting talk at Google I/O 2018

How does Rendora work?

Rendora is listening by default to the port 3001 but can be changed using the config file; for every request coming from the frontend server or the outside world, there are some checks or filters that are tested against the headers and/or paths according to Rendora's configuration file to determine whether Rendora should just pass the initial HTML returned from the backend server or use headless Chrome to provide a server-side rendered HTML. To be more specific, for every request there are 2 paths:

  1. If the request is whitelisted as a candidate for SSR (i.e. a GET request that passes all user agent and path filters), Rendora instructs the headless Chrome instance to request the corresponding page, render it and return the response which contains the final server-side rendered HTML. You usually want to whitelist only web crawlers like GoogleBot, BingBot, etc...

  2. If the request isn't whitelisted (i.e. the request is not a GET request or doesn't pass any of the filters), Rendora will simply act as a transparent reverse HTTP proxy and just conveys requests and responses as they are. You usually want to blacklist real users in order to return the usual client-side rendered HTML coming from the backend server back to them.

Install and run Rendora

First, run a headless Chrome instance

If Chrome/Chromium is installed in your system, you can run it using

google-chrome --headless --remote-debugging-port=9222

note: Mac users may have a google-chrome: command not found error. If that's the case, run the following command and repeat the previous step:

alias google-chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"

or simply using docker

docker run --tmpfs /tmp --net=host rendora/chrome-headless

note: the tmpfs flag is optional but it's recommended for performance reasons since rendora/chrome-headless runs with flag --user-data-dir=/tmp

Then, run Rendora

you can build and run Rendora from source code, (NOTE: please read the configuration manual before running Rendora)

git clone https://github.com/rendora/rendora
cd rendora
# MAKE SURE YOU HAVE GO V1.11+ INSTALLED
make build
sudo make install
rendora --config CONFIG_FILE.yaml

or simply using docker

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

Documentation

You can read the docs here or here

Configuration

Configuration is discussed in detail in docs here or here

A minimal config file example

target:
    url: "http://127.0.0.1" # this is the base url addressed by the headless Chrome instance, it can be simply your website url
backend:
    url: "http://127.0.0.1:8000" # your backend server url

filters:
    userAgent: # .i.e. only whitelist useragents containing the keywords "bot", "slurp", "bing" or "crawler"
        defaultPolicy: blacklist
        exceptions:
            keywords:
                - bot
                - slurp
                - bing
                - crawler

A more customized config file

listen:
    address: 0.0.0.0
    port: 3001
cache:
    type: redis
    timeout: 6000
    redis:
        address: localhost:6379
target:
    url: "http://127.0.0.1" 
backend:
    url: "http://127.0.0.1:8000"
headless:
    waitAfterDOMLoad: 0
    internal:
      url: http://localhost:9222
output:
    minify: true
filters:
    userAgent:
        defaultPolicy: blacklist
        exceptions:
            keywords:
                - bot
                - slurp
                - bing
                - crawler
    paths:
        defaultPolicy: whitelist
        exceptions:
            prefix:
             - /users/

FAQs

What is the difference between Rendora and Puppeteer?

Puppeteer is a great Node.js library which provides a generic high-level API to control headless Chrome. On the other hand, Rendora is a dynamic renderer that acts as a reverse HTTP proxy placed in front of your backend server to provide server-side rendering mainly to web crawlers in order to effortlessly improve SEO.

What is the difference between Rendora and Rendertron?

Rendertron is comparable to Rendora in the sense that they both aim to provide SSR using headless Chrome; however there are various differences that can make Rendora a much better choice:

  1. Architecture: Rendertron is a HTTP server that returns SSR'ed HTML back to the client. That means that your server must contain the necessary code to filter requests and asks rendertron to provide the SSR'ed HTML and then return it back to the original client. Rendora does all that automatically by acting as a reverse HTTP proxy in front of your backend.

  2. Caching: Rendora can be configured to use internal local store or Redis to cache SSR'ed HTML.

  3. Performance: In addition to caching, Rendora is able to skip fetching and rendering unnecessary content CSS, fonts, images, etc... which can substantially reduce the intial DOM load latency.

  4. Development: Rendertron is developed in Node.js while Rendora is a single binary written in Golang.

  5. API and Metrics: Rendora provides Prometheus metrics about SSR latencies and number of SSR'ed and total requests. Furthermore, Rendora provides a JSON rendering endpoint that contains body, status and headers of the SSR response by the headless Chrome instance.

Acknowledgements

Many thanks to @mafredri for his effort to create cdp, a great Chrome DevTools Protocols client in Golang.

Follow rendora news and releases on Twitter

George Badawi - 2018

Owner
Rendora
dynamic server-side rendering using headless Chrome to effortlessly solve the SEO problem for modern javascript websites
Rendora
Comments
  • Rendora cannot even render a

    Rendora cannot even render a "Hello-World" React Project

    After following this tutorial: https://hashnode.com/post/instantly-solving-seo-and-providing-ssr-for-modern-javascript-websites-independently-of-frontend-and-backend-stacks-cjpuy274b01br3zs1zr46awfz

    I realised that Rendora seemed broken. No matter what I do, I cannot seem to connect to the port listened-to by Rendora, and therefore cannot get any SSR-ed content.

    My Config:

    listen: port: 3001

    backend: url: http://127.0.0.1:3000

    target: url: http://127.0.0.1

    filters: userAgent: defaultPolicy: whitelist

    Example Project: https://github.com/po-trottier/rendora-test To run the project, start 2 terminal, in the first one write "npm start" in the second one write "docker-compose up". You will need docker-desktop and node installed.

    Important Note: I also tried to run it with a VueJS Project running on NGINX and got the same result.

  • go build fail

    go build fail

    ➜ rendora git:(master) go build go: golang.org/x/[email protected]: unrecognized import path "golang.org/x/text" (https fetch: Get https://golang.org/x/text?go-get=1: dial tcp 216.239.37.1:443: i/o timeout) go: golang.org/x/[email protected]: unrecognized import path "golang.org/x/sync" (https fetch: Get https://golang.org/x/sync?go-get=1: dial tcp 216.239.37.1:443: i/o timeout) go: golang.org/x/[email protected]: unrecognized import path "golang.org/x/sys" (https fetch: Get https://golang.org/x/sys?go-get=1: dial tcp 216.239.37.1:443: i/o timeout) go: golang.org/x/[email protected]: unrecognized import path "golang.org/x/sys" (https fetch: Get https://golang.org/x/sys?go-get=1: dial tcp 216.239.37.1:443: i/o timeout) go: golang.org/x/[email protected]: unrecognized import path "golang.org/x/sys" (https fetch: Get https://golang.org/x/sys?go-get=1: dial tcp 216.239.37.1:443: i/o timeout) go: error loading module requirements

    These resource addresses are 404

  • Question: running rendora docker together with a docker nginx container

    Question: running rendora docker together with a docker nginx container

    First of all kudos for this awesome project. It solves an actual paint point while working with SPAs.

    I have a custom setup I am not able to make work and I am wondering if it is even possible.

    The VPS looks like this:

    A docker container built from a custom nginx image that serves the react files.

    The custom nginx image has a custom ngnix.conf copied to /etc/nginx/nginx.conf and the react dist files copied to /var/www/html/myapp at build time.

    docker run --name my-nginx-app -d --network my-network -p 80:80 my-custom-nginx-image

    Everything works but when I try to force SSR.

    Just running rendora and the chrome headless images do nothing although I am able to render the SSR'ed content through the API, when enabled, posting to /render

    What am I doing wrong?

  • How to use Rendora as a library (inside a web framework like Beego)?

    How to use Rendora as a library (inside a web framework like Beego)?

    We built a forum software based on React + Beego: https://github.com/casbin/casnode and we want to add SSR to it.

    We don't want to use Rendora as a reverse proxy because we already have Nginx as the reverse proxy. So two proxies seem too many for us. We want to use Rendora as a library to directly integrate it into our source code (given both are Golang). Is there a way to do it? Thanks!

    /cc @Kininaru

  • Add a Gitter chat badge to README.md

    Add a Gitter chat badge to README.md

    rendora/rendora now has a Chat Room on Gitter

    @geokb has just created a chat room. You can visit it here: https://gitter.im/rendora/rendora.

    This pull-request adds this badge to your README.md:

    Gitter

    If my aim is a little off, please let me know.

    Happy chatting.

    PS: Click here if you would prefer not to receive automatic pull-requests from Gitter in future.

  • Bump github.com/gin-gonic/gin from 1.3.0 to 1.7.0

    Bump github.com/gin-gonic/gin from 1.3.0 to 1.7.0

    Bumps github.com/gin-gonic/gin from 1.3.0 to 1.7.0.

    Release notes

    Sourced from github.com/gin-gonic/gin's releases.

    Release v1.7.0

    BUGFIXES

    • fix compile error from #2572 (#2600)
    • fix: print headers without Authorization header on broken pipe (#2528)
    • fix(tree): reassign fullpath when register new node (#2366)

    ENHANCEMENTS

    • Support params and exact routes without creating conflicts (#2663)
    • chore: improve render string performance (#2365)
    • Sync route tree to httprouter latest code (#2368)
    • chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa (#2375)
    • chore(performance): improve countParams (#2378)
    • Remove some functions that have the same effect as the bytes package (#2387)
    • update:SetMode function (#2321)
    • remove a unused type SecureJSONPrefix (#2391)
    • Add a redirect sample for POST method (#2389)
    • Add CustomRecovery builtin middleware (#2322)
    • binding: avoid 2038 problem on 32-bit architectures (#2450)
    • Prevent panic in Context.GetQuery() when there is no Request (#2412)
    • Add GetUint and GetUint64 method on gin.context (#2487)
    • update content-disposition header to MIME-style (#2512)
    • reduce allocs and improve the render WriteString (#2508)
    • implement ".Unwrap() error" on Error type (#2525) (#2526)
    • Allow bind with a map[string]string (#2484)
    • chore: update tree (#2371)
    • Support binding for slice/array obj [Rewrite] (#2302)
    • basic auth: fix timing oracle (#2609)
    • Add mixed param and non-param paths (port of httprouter#329) (#2663)
    • feat(engine): add trustedproxies and remoteIP (#2632)

    Improve performance

    ENHANCEMENTS

    • Improve performance: Change *sync.RWMutex to sync.RWMutex in context. #2351

    release v1.6.2

    Release Notes

    • BUGFIXES
      • fix missing initial sync.RWMutex (#2305)
    • ENHANCEMENTS
      • Add set samesite in cookie. (#2306)

    Contributors

    release v1.6.1

    ... (truncated)

    Changelog

    Sourced from github.com/gin-gonic/gin's changelog.

    Gin v1.7.0

    BUGFIXES

    • fix compile error from #2572 (#2600)
    • fix: print headers without Authorization header on broken pipe (#2528)
    • fix(tree): reassign fullpath when register new node (#2366)

    ENHANCEMENTS

    • Support params and exact routes without creating conflicts (#2663)
    • chore: improve render string performance (#2365)
    • Sync route tree to httprouter latest code (#2368)
    • chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa (#2375)
    • chore(performance): improve countParams (#2378)
    • Remove some functions that have the same effect as the bytes package (#2387)
    • update:SetMode function (#2321)
    • remove an unused type SecureJSONPrefix (#2391)
    • Add a redirect sample for POST method (#2389)
    • Add CustomRecovery builtin middleware (#2322)
    • binding: avoid 2038 problem on 32-bit architectures (#2450)
    • Prevent panic in Context.GetQuery() when there is no Request (#2412)
    • Add GetUint and GetUint64 method on gin.context (#2487)
    • update content-disposition header to MIME-style (#2512)
    • reduce allocs and improve the render WriteString (#2508)
    • implement ".Unwrap() error" on Error type (#2525) (#2526)
    • Allow bind with a map[string]string (#2484)
    • chore: update tree (#2371)
    • Support binding for slice/array obj [Rewrite] (#2302)
    • basic auth: fix timing oracle (#2609)
    • Add mixed param and non-param paths (port of httprouter#329) (#2663)
    • feat(engine): add trustedproxies and remoteIP (#2632)

    Gin v1.6.3

    ENHANCEMENTS

    • Improve performance: Change *sync.RWMutex to sync.RWMutex in context. #2351

    Gin v1.6.2

    BUGFIXES

    • fix missing initial sync.RWMutex #2305

    ENHANCEMENTS

    • Add set samesite in cookie. #2306

    Gin v1.6.1

    BUGFIXES

    • Revert "fix accept incoming network connections" #2294

    ... (truncated)

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

  • Support for forcing HTTP status code through HTML (for SPA)

    Support for forcing HTTP status code through HTML (for SPA)

    Since, SPA handles 404 pages in the client side, it needs a mechanism to handle HTTP status code through HTML

    For example, https://github.com/GoogleChrome/rendertron#status-codes provides below syntax to pass HTTP status code from HTML:

    <meta name="render:status_code" content="??" />
    

    FWIW, tagging active fork authors in case if they have better solution implemented in their fork. @PureLandFlying @plan-my-binge @muhammadmuhlas @mehdipourfar

  • Failed to adjust OOM score of renderer with pid 30: Permission denied (13)

    Failed to adjust OOM score of renderer with pid 30: Permission denied (13)

    I am getting the following permission denied on the chrome-headless service, when running on a k8s cluster on Digital Ocean and GCP.


    Image: rendora/chrome-headless:latest

    Complete log:

    Fontconfig warning: "/etc/fonts/fonts.conf", line 100: unknown element "blank" [0811/114937.698064:ERROR:gpu_process_transport_factory.cc(967)] Lost UI shared context.

    DevTools listening on ws://0.0.0.0:9222/devtools/browser/a0a6536d-82d6-42d2-986f-acf4267dd9bc [0811/114937.706665:ERROR:zygote_host_impl_linux.cc(259)] Failed to adjust OOM score of renderer with pid 30: Permission denied (13)

    A couple of months ago I was able to deploy and use it without any issues. Now I am getting this.

  • ERR_CONNECTION_REFUSED

    ERR_CONNECTION_REFUSED

    with the configuration down below, everything looks just fine,but when I visit http://localhost:3001/, I only got **ERR_CONNECTION_REFUSED**, can anyone told me how to debug with it, a lot of thanks.

    chrome-headless image

    rendora image

    local react project image

    configuration

    listen:
        address: 0.0.0.0
        port: 3001
    cache:
        type: redis
        timeout: 6000
        redis:
            address: localhost:6379
    target:
        url: "http://127.0.0.1:3000" 
    backend:
        url: "http://127.0.0.1:3000"
    headless:
        waitAfterDOMLoad: 0
        internal:
          url: http://localhost:9222
    output:
        minify: true
    filters:
        userAgent:
            defaultPolicy: blacklist
            exceptions:
                keywords:
                    - bot
                    - slurp
                    - bing
                    - crawler
        paths:
            defaultPolicy: whitelist
            exceptions:
                prefix:
                 - /users/
    
Multiplexer: HTTP-Server & URL Crawler

Multiplexer: HTTP-Server & URL Crawler Приложение представляет собой http-сервер с одним хендлером. Хендлер на вход получает POST-запрос со списком ur

Nov 3, 2021
Simple price scraper with HTTP server/exporter for use with Prometheus

priceserver v0.3 Simple price scraper with HTTP server/exporter for use with Prometheus Currently working with Bitrue.com exchange but easily adaptabl

Nov 16, 2021
Youtube tutorial about web scraping using golang and Gocolly

This is an example project I wrote for a youtube tutorial about webscraping using golang and gocolly It extracts data from a tracking differences webs

Mar 26, 2022
A simple scraper to export data from buildkite to honeycomb using opentelemetry SDK
A simple scraper to export data from buildkite to honeycomb using opentelemetry SDK

A quick scraper program that let you export builds on BuildKite as OpenTelemetry data and then send them to honeycomb.io for slice-n-dice high cardinality analysis.

Jul 7, 2022
🔍 gowitness - a golang, web screenshot utility using Chrome Headless

?? gowitness A golang, web screenshot utility using Chrome Headless. introduction gowitness is a website screenshot utility written in Golang, that us

Jan 9, 2023
Prototype pollution scanner using headless chrome
Prototype pollution scanner using headless chrome

plution Prototype pollution scanner using headless chrome What this is Plution is a convenient way to scan at scale for pages that are vulnerable to c

Jan 1, 2023
crawlergo is a browser crawler that uses chrome headless mode for URL collection.
crawlergo is a browser crawler that uses chrome headless mode for URL collection.

A powerful browser crawler for web vulnerability scanners

Dec 29, 2022
sign Apple’s mobileconfig file to solve the ‘unsigned’ problem
sign Apple’s mobileconfig file to solve the ‘unsigned’ problem

amcs(apple mobile config signature) sign Apple’s mobileconfig file to solve the ‘unsigned’ problem the project rely openssl https://github.com/openssl

Nov 25, 2022
Exercise for solve problem data processing, performance and something wrong in passing data

Citcall Exercise Exercise for solve problem data processing, performance and something wrong in passing data Pengolahan data data processing - Readme

Nov 25, 2021
Implementation of the Feynman algorithm to solve any problem!

Feynman Algorithm Allegedly coined in jest by Murray Gell-Mann to describe Richard Feynman's incredible problem solving ability, this simple algorithm

Mar 16, 2022
Chrome-Password-Dumper - Chrome password dumper written in Go for Linux and Windows

Chrome-Password-Dumper Chrome password dumper written in Go for Linux and Window

Dec 19, 2022
🎨 Terminal color rendering library, support 8/16 colors, 256 colors, RGB color rendering output, support Print/Sprintf methods, compatible with Windows.
🎨 Terminal color rendering library, support 8/16 colors, 256 colors, RGB color rendering output, support Print/Sprintf methods, compatible with Windows.

?? Terminal color rendering library, support 8/16 colors, 256 colors, RGB color rendering output, support Print/Sprintf methods, compatible with Windows. GO CLI 控制台颜色渲染工具库,支持16色,256色,RGB色彩渲染输出,使用类似于 Print/Sprintf,兼容并支持 Windows 环境的色彩渲染

Dec 30, 2022
Embedded javascript server-side renderer for Golang

v8ssr Embedded javascript server-side renderer for Golang. Useful for static server-side rendering. This does not attempt to polyfill node or browser

Aug 27, 2022
HLive is a server-side WebSocket based dynamic template-less view layer for Go.
HLive is a server-side WebSocket based dynamic template-less view layer for Go.

HLive HLive is a server-side WebSocket based dynamic template-less view layer for Go. HLive is a fantastic tool for creating complex and dynamic brows

Jan 8, 2023
Dec 28, 2022
Headless CMS with automatic JSON API. Featuring auto-HTTPS from Let's Encrypt, HTTP/2 Server Push, and flexible server framework written in Go.
Headless CMS with automatic JSON API. Featuring auto-HTTPS from Let's Encrypt, HTTP/2 Server Push, and flexible server framework written in Go.

Ponzu Watch the video introduction Ponzu is a powerful and efficient open-source HTTP server framework and CMS. It provides automatic, free, and secur

Dec 28, 2022
Repo Tugas Problem Solving Paradigm (Greedy, D&C, Dynamic Programming) ALTA Immersive BE5
Repo Tugas Problem Solving Paradigm (Greedy, D&C, Dynamic Programming) ALTA Immersive BE5

Cara mengerjakan tugas clone project ini, melalui git clone https://github.com/ALTA-Immersive-BE5/Problem-Solving-Paradigm.git setelah clone selesai,

Dec 23, 2021
Deploy TiDB with Pulumi effortlessly.

TiDB ❤️ Pulumi Deploy TiDB with Pulumi effortlessly. It should be easy to spin up some virtual machines, and deploy a TiDB cluster there for developme

Jun 24, 2022
Effortlessly generate chmod commands

?? CHMOD-CLI Simple cli tool that brings the chmod command in tui format. Genera

Dec 20, 2022