How I improved the performance of my Hugo site to reach 0.5s LCP while staying under 512kB

Kovasky Buezo | Mar 28, 2026 min read

Intro

I have been using Hugo to build my personal website for a few years now. I love how easy it is to use and how customizable it is through themes, modules, and templates. I am always looking for ways to improve my site and make it more performant. Over time, I have accumulated a few optimizations, which I thought I’d share in the hopes that other people find them useful.

My setup

My package versions are a bit dated, but this is my reliable, tried-and-tested setup. The syntax may have changed with newer releases, but the underlying methods still apply.

I am using:

I have pinned lazyimg and hugo-profile due to personal modifications.

Testing environment

For the purposes of this write-up, I ran tests against a local Hugo development server using sitespeed.io. All tests were run on the blog post pfsense_haproxy_mtls due to its large amount of images. In my screenshots, I removed coach scores, as they would not be as accurate without edge optimizations such as SSL, cache headers, and compression.

My live site is hosted behind Cloudflare and benefits from edge optimizations. For a live test, I used GTmetrix, and its result can be found in the cover image of this post.

The unoptimized starting point

A fresh Hugo site using the hugo-profile theme has lackluster performance. It makes 35 requests and weighs 5.6MB, with more than half of that coming from images. This is definitely not good, and it is what initially prompted me to start looking for ways to squeeze more performance out of my site.

Sitespeed.io performance result of a Hugo site without optimizations.

Image optimization

This is probably the most common recommendation for websites out there, but it really does make a difference. To easily convert images, I am self-hosting a copy of Squoosh.app.

Sitespeed.io performance result of a Hugo site with WebP images.

Converting images to WebP reduces the total image transfer to less than a third of the original size.

Lazyimg plugin

Converting images to WebP is definitely a major improvement, but image handling does not end there. Using the lazyimg plugin, images are not only delayed until they are needed, but it also adds blur-up animations and responsive sizes.

Sitespeed.io performance result of a Hugo site with the lazyimg plugin.

Enabling the lazyimg plugin ensures only the visible images are loaded, reducing the initial image transfer to about one tenth of the total.

Asset optimization

One factor considered in website scores is the number of requests a site makes to load its assets. My website relies on almost the same number of JavaScript and CSS files on each page, each of which is downloaded in a separate request. One improvement is to minify and concatenate these files to reduce the number of requests.

JavaScript files

To merge all JS files and minify them, I used the following Go templating script.

{{ $JS := resources.Match "js/**.js" }}
{{ $ConcatenatedJS := $JS | resources.Concat "js/scripts.js" }}
{{ $scriptJS := $ConcatenatedJS | js.Build (dict "minify" true "sourcemap" "external" "target" "es2015" "keepNames" true) | resources.Fingerprint }}
<script src="{{ $scriptJS.Permalink }}" integrity="{{ $scriptJS.Data.Integrity }}" defer></script>
Sitespeed.io performance result of a Hugo site with JavaScript minification and concatenation.

This modification reduced the number of JS requests by half.

CSS files

Similarly, for CSS files, I used the following Go templating script.

{{ $allCSS := resources.Match "css/**.css" }}
{{ $finalCSS := $allCSS | resources.Concat "css/style.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ $finalCSS.RelPermalink }}" integrity="{{ $finalCSS.Data.Integrity }}">
Sitespeed.io performance result of a Hugo site with CSS minification and concatenation.

This modification reduced the number of CSS requests to less than half.

Bootstrap

Concatenation and minification did not really move the needle much in terms of size. This is because the JS scripts for hugo-profile are minuscule in comparison to the third-party libraries used by the theme. It is very common to ship bootstrap.bundle.min.js as well as bootstrap.min.css. It is very plug-and-play, but with the caveat of shipping more code than needed. The solution to this is optimizing Bootstrap by importing only the components the site actually uses. It took me a bit of trial and error to identify all the scss and js components needed by the site. After doing so and adding them to the assets folder, I modified the Go templating shown in the previous subsections.

{{ $sass := resources.Get "scss/main.scss" }}
{{ $bootstrapCSS := $sass | toCSS }}
{{ $otherCSS := resources.Match "css/**.css" }}
{{ $allCSS := slice $bootstrapCSS | append $otherCSS }}
{{ $finalCSS := $allCSS | resources.Concat "css/style.css" | minify | fingerprint }}
<link rel="stylesheet" href="{{ $finalCSS.RelPermalink }}" integrity="{{ $finalCSS.Data.Integrity }}">

{{ $JS := resources.Match "js/**.js" }}
{{ $ConcatenatedJS := $JS | resources.Concat "js/scripts.js" }}
{{ $scriptJS := $ConcatenatedJS | js.Build (dict "minify" true "sourcemap" "external" "target" "es2015" "keepNames" true) | resources.Fingerprint }}
<script src="{{ $scriptJS.Permalink }}" integrity="{{ $scriptJS.Data.Integrity }}" defer></script>
Sitespeed.io performance result of a Hugo site with Bootstrap optimization.

This modification reduced the number of CSS and JS requests while also saving about 140kB in asset transfers.

Font Awesome

The greatest savings of all came from getting rid of Font Awesome in favour of hand-picking the SVGs to include. The hugo-profile theme only needs 8 icons, and modifying the theme to load them from the assets folder yielded great results.

To load the icons from the assets/icons folder, I added a partial named icon.html.

{{ $icon := .icon }}
{{ $class := .class | default "" }}
{{ $svg := printf "icons/%s.svg" $icon | resources.Get }}
{{ if $svg }}
  <span class="icon {{ $class }}">{{ $svg.Content | safeHTML }}</span>
{{ end }}

And for example, to embed the angle-up icon, you can use it as follows.

{{ partial "icon.html" (dict "icon" "angle-up" "class" "fas fa-angle-up") }}
Sitespeed.io performance result of a Hugo site after removing Font Awesome and hand-picking SVG files.

The total transfer size went down from 2MB to less than 512kB.

Fonts

The last optimization revolves around fonts. The theme I picked uses a few Google Fonts, meaning it has to perform requests to external sites in order to display properly. The fix is to include the required fonts with the site itself. This not only reduces transfer overhead, but also reduces the latency at which the resources load.

Sitespeed.io performance result of a Hugo site with fonts included in the site's assets.

This reduced the Fully Loaded time from 500ms to 100ms.

Done!

These optimizations allowed my Hugo site to reach a 100% GTmetrix performance score, a 0.5s LCP, and an overall A grade.