Tips for (Static) Website Optimization

CONTENTS

Too long, didn’t read

  • Try to keep page load smaller than 50KB per request if you’re not dealing with images, a webpage should never be larger than a copy of Doom. Don’t add 10MB images to your website, generate thumbnails and compress them; link to the bigger images if needed.
  • Try to serve progressive JPEG images and stay away from Google’s WebP format.
  • Self-host everything.
  • Use HTTPS if you have login forms, process user data and such.
  • DO NOT use JavaScript to manage links. Linking in HTML is a solved problem, don’t add an extra unnecessary layer.
  • DO NOT track your website visitors or if you must do it, get consent.
  • Load everything from the same domain or subdomain.
  • Have a responsive design for your website as a lot of people use smartphones now.
  • DO NOT geoblock your visitors.
  • Keep in mind that some people have JavaScript disabled in their browser; plan accordingly for a failsafe and do not greet them with a blank page. It’s rude.
  • Inline critical CSS in a tag.
  • Try to inline images/js smaller than 4KB. Favicons are a good candidate for that.
  • Minify and compress everything. Use gzip. Leverage browser caching.
  • DO NOT override scroll events.
  • Basically a webpage should load in 300ms or less.
  • Please for the love of your favorite divinity do not use Cloudflare. Your visitors don’t need to prove they are not robots while robots get free access to your website anyway. Just don’t.

Let’s begin.

Streamlining resources

If you’re searching for ways to optimize the speed and functionality of your website, then you should first take a moment and review the size of the files the server is delivering to your visitors. You may be surprised that the culprit responsible for sluggish performance is actually the size of your primary resource files (HTML, JS, CSS, images).

Minify code

The easiest way to reduce the size of your pages is to minify the HTML/JavaScript/CSS code. Because of its text-based format, code can be minified by reducing white space, comments, newlines, or optional closing tags.

We can use various tools for that, my favorite one being Taco de Wolff’s minify.

$ minify -o "public/" --recursive --match=\.*ml "public/"
$ minify -o "public/" --recursive --match=\.*css "public/"
$ minify -o "public/" --recursive --match=\.*js "public/"

Code minify, before and after

Compress images

You can compress your images so that smaller versions get served by your webserver; there are several apps and web services for that, ImageOptim for example, Compress PNG and Compress JPEG or console utilities (jpegoptim, jpegtran, pngoutimagemin or svgcleaner).

Use ImageMagick to convert a single image to progressive JPEG:

$ convert <SOURCE>.jpg -interlace plane <DESTINATION>.jpg

or convert all JPEG images inside a directory:

$ mogrify -interlace plane *.jpg

or convert all JPEG images inside a directory and recursively all directories inside:

$ find . -iname \*.jpg -exec convert {} -interlace plane {} \;

or use jpegtran:

$ jpegtran -copy none -progressive <SOURCE>.jpg <DESTINATION>.jpg

Lazy-loading images

Add loading="lazy" to the big images on your website but keep in mind that not all browsers support it (all major ones do).

The loading attribute gives browsers the option to delay off-screen images until users scroll to their location on the page.

  • lazy: works great for lazy loading.
  • eager: instructs the browser to load the specified content right away.
  • auto: leaves the option to lazy load or not to lazy load up to the browser.
<img
	src="this/is/an/image.png"
	loading="lazy"
	alt="This is an image" />

Be sure to read more info on lazy-loading.

Fonts

Custom web fonts are popular and available to everyone through services like Google Fonts. However, ask yourself if you really need them, as an alternative, consider using a system font stack relying on fonts already installed on the user’s system. Or you can self-host Google’s fonts.

Preload essential fonts:

<link
	rel="preload"
	href="/fonts/yourfont.woff2"
	as="font"
	type="font/woff2"
	crossorigin />

Nginx configuration

This guide uses Nginx as the software serving all your web files, if you use ApacheCaddy or IIS (ffs) you can stop reading here.

Enable HTTPS

First of all, a static website doesn’t need to be served over HTTPS but for the “health of the Internet” let’s say you want to do that. It’s not a bad idea to enable HTTPS and get a SSL certificate from LetsEncrypt but you need to keep in mind that some of the old browsers and operating systems will not be able to connect to your website. For example, if you have a retro website with Amiga stuff on it, people checking the Internet of their Amiga computers (there are dozens of us, DOZENS!) can’t browse your fancy website over HTTPS but they could browse it over HTTP just fine. Your options are:

  1. HTTP-only.
  2. HTTPS-only (and redirect HTTP traffic to HTTPS).
  3. HTTP and HTTPS.

As long as we’re talking about a static website that doesn’t process payments, log users in, process personal data, any of the options are good.

If you want to enable HTTPS add the line below to your Nginx configuration for that specific server.

server {
	listen 443 ssl;
}

Enable IPv6 (if possible)

If you have IPv6 enabled on your server you can make Nginx listen for requests on the IPv6 interface too. Enable IPv6 for HTTP (port 80) or HTTPS (port 443) depending on what option you need.

server {
	server_name bla_bla_bla;
	listen	:80; # HTTP IPv4
	listen	:443 ssl; # HTTPS IPv4
	listen	[::]:80; # HTTP IPv6
	listen	[::]:443 ssl; # HTTPS IPv6
}

You can specify the IPv6 address by checking the output of the ifconfig utility:

$ sudo ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
		inet 194.182.114.12  netmask 255.255.252.0  broadcast 194.182.117.255
		inet6 2a04:c44:e00:789c:423:c0ff:fe00:d1e  prefixlen 64  scopeid 0x0<global>

And add the address to your Nginx server config:

server {
	server_name bla_bla_bla;
	listen [2a04:c44:e00:789c:423:c0ff:fe00:d1e]:443 ssl;
}

If you want a server block that only listens on IPv6 and not IPv4, use the ipv6only flag.

server {
	server_name bla_bla_bla;
	listen [::]:443 ipv6only=on;
}

Don’t forget to update your DNS AAAA record with your IPv6 address.

Disable logging (if possible)

Unless you’re into peeking access and error logs for metrics (you shouldn’t) you should disable both Nginx access logs and error logs; for a static website you shouldn’t have any errors. If you want access logs but no error logs, adjust the lines below.

	access_log off;
	error_log /dev/null;

Security

Enable HTTP Strict Transport Security and other security mitigations.

	add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
	add_header X-Content-Type-Options "nosniff" always;
	add_header X-Frame-Options "SAMEORIGIN" always;
	add_header X-Xss-Protection "1";
	add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' data:; object-src 'none'; img-src 'self' data:; script-src 'self' data: 'unsafe-inline'";

If you need a different Content Security Policy (if you request external javascript files, for example) you can use this CS Policy Generator.

Gzip and caching

Allow Nginx to compress on request the files on the server so the browser downloads smaller files; and cache files that are downloaded by the browser for a certain length of time so they aren’t downloaded again when browsing a page which uses those same files.

Add the lines below to your Nginx server config.

	gzip on;
	gzip_disable "msie6";
	gzip_comp_level 6;
	gzip_min_length 1100;
	gzip_buffers 16 8k;
	gzip_proxied any;
	gzip_types text/plain
		text/css
		text/js
		text/xml
		text/javascript
		application/javascript
		application/json
		application/xml
		application/rss+xml
		font/opentype
		font/otf
		font/sfnt
		font/ttf
		font/woff
		font/woff2
		image/svg+xml
		image/x-icon;

	location ~* \.(js|woff|woff2|css|png|jpg|jpeg|gif|ico|svg|json)$ {
		expires 30d;
		add_header Cache-Control "public, no-transform";
	}

LetsEncrypt (if you go HTTPS way)

If you enabled HTTPS mode on your server you will also need a SSL certificate; you can get free certificates courtesy of LetsEncrypt and certbot. Install certbot for your favorite Linux distro (out of the scope of this guide) and use it to request a certificate for your domain.

$ sudo certbot --nginx -d <DOMAIN> -d www.<DOMAIN>

Let’s say you want a certificate for the getwild.se domain. In this case, the line above would look like this:

$ sudo certbot --nginx -d getwild.se -d www.getwild.se

Make sure the domain you are requesting a certificate for matches the server_name line in your Nginx server config else automatic retrieval won’t work.

Note

Let’s Encrypt certificates expire after 90 days so you will need to automatically renew them.

Use SSL Server Test to check the configuration of your server.

It’s recommended to specify which entity can issue a certificate for your domain, in our case LetsEncrypt, by adding a DNS CAA record. You can use the SSLMate CAA generator for it but it’s pretty simple (replace example.org with your domain and dump the line below in your DNS config):

example.org. CAA 128 issue "letsencrypt.org"

CAA creates a DNS mechanism that enables domain name owners to whitelist CAs that are allowed to issue certificates for their hostnames. It operates via a new DNS resource record (RR) called CAA (type 257). Owners can restrict certificate issuance by specifying zero or more CAs; if a CA is allowed to issue a certificate, their own hostname will be in the DNS record.

Before issuing a certificate, CAs are expected to check the DNS record and refuse issuance unless they find themselves on the whitelist.Source

Performance Reports

We can use Google Lighthouse (yes, I know) since it not only analyzes raw performance but also provides insight into how well your website performs according to:

  • Accessibility
  • Best practices
  • Search engine optimization

This website

Web.dev report for this website

bbc.co.uk

Web.dev report for bbc.co.uk