Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
My hobby projectââânpmcharts, is a single-page app that shows the download trends of various npm packages. If you went looking into which headless chrome library to use, youâll see this graph in your browser -
However, when that page is shared to Facebook, Twitter or Slack, the preview image that shows up would all be the sameâââthe screenshot I took comparing frontend frameworks and uploaded as the siteâs sole Open Graph image two years ago. Tsk tsk.
The problem
Everything was hosted on my little $5 Digital Ocean droplet. One of the reasons Iâd put off this feature for so long was because I was worried the droplet wouldnât be able to handle the load of running a snapshotting service. Iâll have to try to be efficient.
Starting out simple
Letâs start out simple and first get something that works:
Fairly straightforward, but launching a browser instance for each request and closing it afterward seems wasteful.
The average time to return a screenshots is currently ~3.5 seconds on my MacBook, itâll only take longer on the D.O. droplet. Letâs reduce and reuse.
Note: All timings given are measured against local servers running on my MacBook. It only accounts for execution speed of the function to return an image on a 2015 MBP. Network transfer speeds are not included!Pooling browsers
Pooling would allow us to keep a handful of browser instances open and reuse them for each screenshot request. Puppeteer doesnât come with built-in pooling solutions, but there are a few generic libraries available. Weâll be using generic-pool.
The first pool:
But wait, we shouldnât need to create pages and set viewport for each screenshot either. Letâs pool pages instead of browsers:
Letâs update our getChartImage function to ask for pages from the pool:
The average time of subsequent screenshots is now down to ~1.58 seconds! (For those curious, when just pooling browsers without pages, the average time was ~1.87Â seconds)
Take advantage of âsingle-paged-nessâ
One of the advantages of SPAs is that browsers donât have to reload all resources and re-parse all the scripts upon navigation. However, by calling page.goto each time, we were unnecessarily triggering full page reloads when navigating within the same app.
The solution for this varies depending on the framework and routing library the app uses, but the basic idea is fairly simple and translatable â
- On the frontend, expose the routing function that would allow route navigation to be triggered by puppeteer from the global context (i.e. window)
- Also on the frontend, make a flag available to let puppeteer know when the route transition is complete.
- Puppeteer would flip the flag to false, call the routing function, and poll the flagâs value until the front-end flips it to true (signifying that the route change is complete).
If your app uses React and React-Router v4, you could use withRouter somewhere in the app to ask for the history object, then stick that into the window. e.g.
In my case with Vue 1.0 and vue-router 0.7, I added this line to the root componentâs ready hook:
After the route transition has completed (data loading and rendering is done), the frontend would flip the flag to signal back that itâs ready to have its screenshot taken:
Letâs update getChartImage to use those hooks
A screenshot now only takes ~860ms! Weâve managed to shave the time down to less than a quarter of the initial implementation.
Thereâs one more thing we can do â
Memoize
Weâd save more resources if we didnât have to generate these images every time theyâre accessed.
When a request for a screenshot comes in, we want to â
- Check if a screenshot for that resource already exists.
- If it does, check if itâs stale and needs to be updated.
- If it exists and is not stale, directly return that file.
- If it doesnât exist or needs to be updated, create a new snapshot.
And thatâs it! Subsequent requests within a certain time period only takes 0.3ms. The next step would be to save and serve it up from a CDN instead of the local filesystem, but I think this is good enough for now :) Digital Oceanâs droplet comes with 25 gigs of SSD and 1TB transfer, will save that for when I need it.
Hope this was helpful! Thanks so much to Ben Hare and Jeffrey Burt for their feedback on drafts!
And please come checkout my site npmcharts.com for all your npm package comparison needs. Hereâs one of webpack, browserify, rollup, and parcel â
đŹSubscribe to my newsletter to receive upcoming articles in your inbox
đ§ Digital Ocean $10 referral link
Efficiently snapshotting your single-page-apps with Puppeteer was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.