Blog of Raivo Laanemets

Stories about web development, consulting and personal computers.

Adding a custom font

On 2018-12-18

Last week I reworked the blog typography a bit and added a custom font. I had used only the available built-in fonts on various platforms until now. I used Tahoma on Windows, which renders pretty poor on Windows 7 and 10, compared to DejaVu Sans on Linux and Helvetica on Mac OS.

Tahoma has strong color shadows around letters due to subpixel hinting:

Tahoma color shadows on Windows 10

The screenshot uses the default Windows 10 font rendering options:

  • Font smoothing enabled;
  • ClearType enabled;
  • Default ClearType options.

The image was scaled 2x without any interpolation. Windows 7 with default font settings renders the text exactly the same as Windows 10.

I decided to play around with custom fonts and font sizes to improve rendering on Windows 7 and 10.

After boosting the font size to 18px and switching to Open Sans:

Open Sans subpixel rendering on Windows 10

It can be seen that subpixel hinting is less stronger, resulting in less severe color shadows around the letters. It is also a lot more regular. This is especially noticable on letters I, C, A, l, and s.

This is the comparative result at the normal scale:

Tahoma vs Open Sans on Windows 10

I selected Open Sans based on this article. Besides, Open Sans renders very similar on Windows, Mac, and Linux.

Font files

Getting the custom font installed was harder than I expected at first.

Google Fonts is likely the easiest way to install a custom font on a web site. However, it is not optimal:

  • Google URLs are blocked in some countries;
  • Google fonts are blocked by many ad-blockers;
  • You cannot use font-display tuning;
  • You cannot use preloading.

Google Fonts work by loading a CSS file that uses @font-face rules to define and load the custom fonts. It uses browser sniffing to serve browser-specific font file formats. There are 5 widely used formats:

  • Embedded OpenType (eot);
  • OpenType (ttf);
  • Web Open Font Format (woff);
  • Woff 2;
  • SVG.

The most widely supported format is OpenType. WOFF and WOFF 2 are essentially the same format as OpenType but with added compression.

I decided to serve the font file myself and include the necessary @font-face rule in my main stylesheet. I'm using the OpenType file format to support IE11 (everything else on this blog works with IE11, so why not). The file size is reduced by server-side compression.

Open Sans font can be divided into several files, depending on the supported Unicode range. I went with the full support and got my font file from FontSquirrel. The font file size is pretty large (~200kb, ~100kb compressed) and I might optimize it in the future. There is a good online helper to optimize the font files.

Update 2018-12-22

I have optimized my font files by only including Latin and Latin-ext. These cover the majority of european languages. The characters missing from the font file will be replaced by those from the fallback font (which is platform-dependent sans-serif in my CSS).

Server-side compression

I have a NGINX reverse proxy in front of the blog. It also serves static files. There are 2 things that need to configured.

Add mime type for .ttf files (in /etc/nginx/mime.types):

font/ttf ttf;

And enable compression for this type of file (in /etc/nginx/nginx.conf):

gzip_types rest-of-compressed-types font/ttf;

I really do not understand why none of the mime font types are configured in NGINX. The configuration paths are for NGINX running on Debian. On some other systems, the paths might be different. The compression instructions assume that compression was already configured for some other file types.

Server-side compression gives about 50% bandwidth saving on a font file.


Preloading instructs the browser to start downloading the font file before the CSS file is loaded and the font is used for any elements. This can be achieved by using the <link rel="preload"> tag in the page header.

This is what I use:


Preloading fonts from another domain (from a CDN, for example) is more complex but explained in the MDN reference. Loading fonts from another domain will require CORS headers on the server (CDN) that handles the font files.

Long-term caching

Long-term caching ensures that the content will not flash/flicker in another font for a short time period during sequential pageviews. The caching headers injected by the server instruct the browser to not re-download the font files at each page load.

I configured this setting in NGINX (along with other static files):

location ~* \.(ico|css|js|gif|jpe?g|png|ttf|woff|woff2)$ {
    access_log off;
    expires max;
    try_files $uri @upstream;

The caching configuration depends a lot on the site setup. For some files, it is also necessary to use a cache buster to invalidate the browser cache when the files change. It is unlikely for the font files to change, but common for stylesheets to be modified when the site is maintained.

Font swapping

Font swapping occurs during the first pageview when the font file is not yet in the browser cache. It can be configured using the font-display attribute on the @font-face rule. This defines how and when font swapping happens.

The MDN reference explains the possible values. I have chosen fallback. It gives an extremely small block time (invisible text) and a small swap time. This is something I will likely tune in the future once I get access to more devices to test with.

IE11 fixes

Last but not least, IE11 requires again more work to perform correctly. The OpenType font file downloaded from FontSquirrel does not work on IE11. The IE11 debugger displays the following message:

@font-face failed OpenType embedding permission check. Permission must be Installable.

You will need a special tool to make the font file usable. This is best explained in this Stack Overflow thread.

However, the optimized font files from the Webfonts helper application don't have this issue.