Optimizing web performance for static site

gulpcdn

As most web apps are I/O bound, I naively assume that the static site is immune of the web performance issue by design. I am totally wrong. The growing mobile web traffic has significantly changed the landscape: the sluggish cellular network just becomes the bottleneck in the new era.

More concretely, it still takes 5s for a mobile device to render this simple About page in regular 3G network(750 KB/s) with an empty cache after some best practice.

Distribute assets with CDN

The YSlow report shows the HTML document is 8.5K, a marginal 6% of 148.6K payload.

YSlow breakdown

So if we manage to deliver the assets more efficiently, we should boost the performance. After comparing several low end CDN services, I decide to give KeyCDN(refer link) a spin due to:

  • its flat price for $0.04/GB
  • its multiple Points of Presence (POPs), verified with VPN.
  • no minimum monthly or annual commitment, though the minimum top-off is $25.

After the heavy-lifting is outsourced to KeyCDN, the rendering time for above-the-fold is down to 2.5s, not bad.

Since this site is built with metalsmith and gulp, I manage to integrate the CDN with following tools:

  • gulp-rev: append the hash to the file name as asset revision, no worry about the cache staleness.
  • gulp-rev-replace: replace the original asset reference in all HTML pages with revisioned assets.
  • gulp-cdnizer: replace the local link to CDN links.

Here are some lessons learned:

  • If multiple tasks uses the gulp-rev and write to the same rev-manifest.json with merge option, you may find some assets mappings are missing due to the racing condition. I am currently working on a reader-writer lock patch to address this bug. For the time being, adding artificial dependency can work around this issue.

  • Be aware of the base option in the gulpfile.js, it also governs the path in rev-manifest.json.

FOIT vs FOUT

Chrome Dev Tools allows us to review the rendering snapshots in the timeline:

This is the typical “Flash of invisible text”, aka FOIT: the rendering pipeline is stalled by the web font dependency, so no content is rendered until 2.35s. The alternative is “Flash of unstyled text”, FOUT: the content is styled with system font so the rendering is not blocked, then we promote the style to web font when they are asynchronously loaded. Some front end engineers go extra miles to support so-called “above-the-fold” experience: we just load minimum styles and a subset of web fonts to support the visible portion on your device, and load everything else later for the swift perceptions.

After toying this idea with fontfaceobserver and fontfaceonload for couple hours, I find the latter solution is much harder than I thought:

  • We need to split the stylesheet into two parts: the essential part has to be inlined to the HTML page to avoid blocking the rendering pipeline; the remaining part will be loaded asynchronously with tools like loadCSS.
  • The web font promotion will trigger a drastic annoying relayout. The suggestion is to find the correct fallback system font to limit the transition, — but is the difference the whole point to use a web font?

Conclusion

A pragmatic optimization should strike the balance between the specialization and productivity. I think the CDN approach will do well enough for the time being, anyway this site is not popular.