Want to use headless Chrome? Think twice!

Niklas Klein
3 min readMay 28, 2020

For a side project of mine, I initially leveraged headless Chrome to take care of rendering huge images as print templates. My application generated an HTML file with included stylesheets and passed it on to Chrome to take care of the rest. This was a great workflow in the beginning, but unfortunately became more and more of a burden as the project progressed.

In this article I will share my experiences working with headless Chrome and why I ultimately had to ditch it in favor of a more involved canvas drawing solution (java.awt.* in my case).

Pros

  • Easy to set up
    All you need to get started is the chrome executable in your environment and you can use it with your programming language of choice by executing shell commands. The official documentation does a great job on getting you started.
  • Easy to use
    It’s straightforward: you feed it with an HTML file and can rely on a rock solid rendering engine taking care of generating an image or even a PDF export for you.
  • Chrome DevTools Protocol
    With libraries like Puppeteer, or chrome-devtools-java-client in my case, you gain access to a powerful API allowing you to improve resource consumption by keeping a Chrome instance, and even tabs, around.

Cons

  • Easy to set up, but hard to get right
    The amount of available command line switches is staggering. Trying to identify the ones you need and understanding what they actually do quickly becomes a time consuming trial and error activity.
  • Slow
    Understandably Chrome is a bad choice if you need your images to be generated quickly. The browser needs time to start up, parse your HTML, render your image and finally write it to disk. This takes easily an order of magnitude longer than a canvas drawing approach where you do not pay the start up and parsing penalty and receive the generated image as a stream.
  • Memory
    Chrome has a reputation of being memory hungry and that does, to some extend, also apply to headless mode. But it is predictable and you can adjust your server capabilities to its needs. However, I eventually ran into more severe issues where Chrome…
  • Can’t keep up with large images
    For my use case I had to generate images that were well above 10,000 pixels per dimension. But even with generous memory resources, Chrome started to trip over those. An unobtrusive log message (“WARNING: tile memory limits exceeded, some content may not draw”)indicated that the rendering ran into memory issues. The process doesn’t fail though, it yields an image file in the end but that image might contain weird artifacts or unexpected blank spots. I started to scan the headless Chrome logs for these log messages and promoted them to application errors. As an ad-hoc solution I split the image generation into multiple sections which I then had to stitch together. But since then at the latest, the rendering times became unacceptable.
  • Varying results in different environments
    The final dealbreaker for me was when I ran my rendering pipeline in a Docker container after developing it on a MacOS system. Especially in conjunction with the --force-device-scale-factor command line flag, the quality of generated images differed significantly from my development environment.
  • Base64 blobs
    You can either use the Chrome CLI to generate a screenshot directly to a file, or use the more advanced Chrome DevTools Protocol. Unfortunately, the protocol returns the image document as a Base64 encoded blob which is unsuitable for large image files.

Conclusion

Headless Chrome is incredibly powerful and the right tool for a wide variety of use cases. However, if you want to render very large images or if latency is a decisive parameter for your application, you unfortunately have to bite the bullet and reach for a more involved alternative such as drawing on canvas.

--

--