Top 25 Tips for Building an EXTREMELY FAST Website!!!
Dustin Brett
Posted on July 25, 2022
I recently did a live stream explaining what I considered the "Top 25" things I'd done to my website (dustinbrett.com) to make it "FAST". The video is over 2 hours so I was able to get into quite a bit of detail. An embedded video is at the bottom of this article.
In this article I will try and summarize each tip and add a screenshot/links.
1. Content Delivery Network (CDN)
This may be the best thing you can do to improve speed on your website. Getting the files to the user faster is something I've found to be the biggest gain in performance in my situation. My web server hosts files at quite a slow speed so it can take the user several seconds to resolve the DNS of my domain and get the initial files. Also my web server is centralized in one location. With the CDN it can serve cached versions of my static files from edge servers that are closer to the user requesting the files.
In the case of CloudFlare, I use their free plan and route my DNS for dustinbrett.com through them. The it points back to my actual web server which is where CloudFlare goes to get files whenever the cache becomes invalidated/purged. CloudFlare also has a lot of customizations and toggles to make things faster yet. I've linked to information about the free plan and their guide optimizing site speed.
- https://www.cloudflare.com/en-ca/plans/free/
- https://developers.cloudflare.com/fundamentals/get-started/task-guides/optimize-site-speed/
2. HTTP/2 & HTTP/3
This is a simple trick as long as your web server / CDN supports it. Make sure to serve your content on the latest HTTP protocol as it offers performance optimizations in some cases.
- https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
- https://en.wikipedia.org/wiki/HTTP/3#Comparison_with_HTTP/1.1_and_HTTP/2
3. Brotli Compression vs GZip
Another simple trick on the server side is to enable Brotli compression if it's supported. It's considered the successor to GZip and it does indeed seem to make things smaller, which ideally means faster and in this case it seems to be so.
- https://en.wikipedia.org/wiki/Brotli
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
4. HTTP Headers
This is important and ideally defaults to some sane values, but there are cases where if you don't setup rules you will serve files that are not cached and get requested every time. One place where I had ran into an issue was with .ini
files which the server didn't know were text and so served with a Content-Type
of application/octet-stream
which also seemed to set it's Cache-Control
to max-age=0
. It took me a while to notice this as I was going through my page load request headers in DevTools->Network. The solution on my server side was to properly associate those extensions with MIME types like text/plain
, which had a sane Cache-Control
value of 1 week. The lesson here is to make sure you are sending proper cache headers to your users so their browsers know not to request things from your server unnecessarily.
- https://developers.cloudflare.com/cache/about/cache-control/
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
5. Early Hints
I have mixed feelings on this suggestion as I was not able to make it work properly with my responsive images which make use of srcset
(<img>
)/imagesrcset
(<link>
). In theory though this seems like a very useful feature and if I had other files I care to do this with, I would consider setting it up. But my ideal use case right now for this would only be images and for that I need to wait for a server that supports a responsive Link
header. I'd be happy to be proven wrong on this, but to me it seemed not possible if your images are based on dpi, to have the 103 Early Hints depend on the requesting browsers dpi and only send them the relevant image, instead of all resolutions. For anyone who is using non-responsive link
headers and is on CloudFlare or any other server supporting 103 Early Hints, this seems like a good feature as it will tell your users to get images before they've even seen your HTML with the <link>
preload tags.
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103
- https://developer.chrome.com/blog/new-in-chrome-103/#http103
- https://developer.chrome.com/blog/early-hints/
- https://developers.cloudflare.com/cache/about/early-hints/
6. Handle Initial CDN MISS
This is partially a tip, although the more I think on it the more I question it's usefulness. For sites like mine that are under heavy development, to me it makes sense to purge cache often as I change quite a few files on a weekly basis. Because of this, every edge server needs to go to my slow web server before they can cache the file to serve to their nearer users. What I do is visit the site and make sure in the HTTP headers for CloudFlare that instead of a MISS
on cache from the edge server, it shows a HIT
. But when I think about this I realize it just caches it on the edge server that I happened to visit. So for me it's faster as subsequent visits are HIT
's, but for users around the world they will get that slow initial request if someone on their edge has not already triggered the MISS
.
7. HSTS header
I'm not sure on what kind of performance boost this could possibly have, but I do like the idea of my domain being on a browser list somewhere that says, always visit this domain via HTTPS. By doing this you can avoid the minute slowdown from users that may visit your site via HTTP and ideally get redirected to HTTPS.
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
- https://hstspreload.org/?domain=dustinbrett.com
8. <link />
preload & preconnect
I've found these preload's to be quite useful as I can see in DevTools->Network that the images begin loading before my dynamic website has decided it needs to show those images. With a site like mine where the content of the homepage is a desktop environment which the user can change, there is a chance that these preload headers may be of less use to users who have already visited my site and deleted the relevant dynamic content that would have shown those images. But to me it's worth it for most users that will do their first visit and see the images quicker because these HTML tags have told their browser to get images that I knew most users would need.
This is also useful after load and I make use of it when a user hovers their cursor over the menu button. At the time of hover I inject preload link headers into the document head as most users don't click the menu button at the exact millisecond their mouse goes over it, this gives the browser some time to preload the images which are very likely to be in the menu. Again my website being dynamic, it's possible the user could change the content of the menu which would make some preload requests unnecessary. But it's a minute cost for return visitors who want a dynamic experience.
- https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-type
9. fetchpriority
This is another new feature only available on Chromium browsers currently, but if your users support it, it seems worth using. The fetchpriority
concept can be used for img
, fetch
& link
. For any requests that I want to happen asap, I specify high
priority.
10. HTML Minify / Tag Removal
I've always liked to have a minimal amount of HTML when possible, so finding html-minifier-terser was quite nice as it removed tags I thought were require but it turns out they are not. Tags like <head>
, <body>
, </html>
. Also quotes often are not needed. This minification tool is quite good at removing useless HTML. Then I also run a script which removes other tags I don't care to have like <noscript>
and some of the Next.js JSON data which I do not need.
- https://github.com/terser/html-minifier-terser
- https://validator.w3.org/
- https://nextjs.org/docs/basic-features/pages#static-generation-recommended
- https://github.com/DustinBrett/daedalOS/blob/main/scripts/minifyHtml.js
11. Image Minify / Simplify
Another thing I usually try and do is have the smallest images possible. I mention it more in other tips on ways I do it, but one useful way is through minification. I use the Windows tool FileOptimizer to do lossless compression on all my images. I also use SVGO to make SVG's smaller as often times the path
value can be simplified without any data/quality loss. Finally another simplification technique that I do which might not be totally ideal for everyone, is to use a minimal favicon setup. I use the absolute bare minimum which is just a favicon.ico file in the root and no supporting HTML to point to high resolution versions. Depending on your use case you may want to have some favicon tags, but keeping it as minimal as possible is still ideal.
- https://nikkhokkho.sourceforge.io/static.php?page=FileOptimizer
- https://github.com/DustinBrett/daedalOS/blob/main/scripts/createIcons.bat
- https://jakearchibald.github.io/svgomg/
- https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML#adding_custom_icons_to_your_site
- https://en.wikipedia.org/wiki/Favicon
12. WEBP vs PNG vs AVIF
When it comes to which image format to use, it will depend a bit what type of image you want to represent. If it's a lossy photo you took on your camera, it's possible AVIF may be ideal. If it's a lossless thumbnail/icon, then WEBP may offer better results, especially if you don't need some of the new features AVIF provides. But as long as your users support it, I think we should happily migrate to the modern AVIF/WEBP image formats over JPG/PNG for most cases as it seems to be the same visual quality in a smaller file in my experience.
- https://avif.io/blog/comparisons/avif-vs-webp/
- https://caniuse.com/webp
- https://developers.google.com/speed/webp/docs/cwebp
13. Lazy Loading / Intersection Observer
I use several forms of lazy loading, but the most useful one for my load times has to be dynamic imports. This allows me to avoid bundling most of my app on load. Instead the components/modules are loaded on demand from chunks that Webpack has created.
Another way I do lazy loading is for all icons that represents files or folders. It does not load the icon until it detects that image has gone into the viewport. In the case of dynamic icons which require grabbing the file itself, for those I use JavaScript and the Intersection Observer to run the getIcon
function when the button of the icon reaches the viewport.
- https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading
- https://nextjs.org/docs/advanced-features/dynamic-import
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
14. Testing Lighthouse / GTMetrix / WebpageTest
Testing changes as you make them is the best way to know if what you are doing is going in the right direction. Many of the changes I've done have been based off of findings from tests like these. It's important to remember that these tests offer suggestions but that they don't fully understand your website and they can sometimes offer suggestions for things that are not worth doing and would make basically no impact to your users.
- https://github.com/GoogleChrome/lighthouse
- https://pagespeed.web.dev/
- https://gtmetrix.com/
- https://www.webpagetest.org/
15. Web Workers & Offscreen Canvas
This to me is a very cool browser technology that I love to try and use whenever possible. Both my clock and wallpaper run in web workers and both of them paint their updates to offscreen canvases. Another advantage of moving my website components to this system is that if the main thread freezes, the clock and wallpaper continue. At the moment most useful things still run on the main thread, but I hope one day to move everything into separate web workers.
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
- https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
- https://developer.mozilla.org/en-US/docs/Glossary/Main_thread
- https://partytown.builder.io/
16. Target Modern Browsers (Avoid Polyfills & ES5)
If you have the luxury to not need to support very old browsers like IE, then I think it's time to drop as many polyfills as possible and to rely on modern browsers to have the functionality we need without having to give the user more code.
17. Advanced Library Settings
This is going to be specific to what libraries and frameworks you are using. In my case, 3 places where I was able to add additional optimizations was with Next.js, Framer Motion & Styled Components. In all cases there are advanced configurations which I love to go through to find little tweaks I can add when possible. Whenever I add something from npm
I look for advanced configuration settings, just to know what's possible and if I like the defaults.
- https://nextjs.org/docs/advanced-features/compiler
- https://www.framer.com/docs/guide-reduce-bundle-size/
- https://styled-components.com/docs/tooling#dead-code-elimination
18. Prebuild JSON (fs, search, preloads)
This is an optimization I like to do whenever I notice I'm making the same JSON structure within my code. That is often a chance to make it once and access it via a static file, which is often faster, but not always, so test it.
- https://nodejs.org/api/fs.html
- https://github.com/DustinBrett/daedalOS/blob/main/scripts/searchIndex.js
- https://github.com/DustinBrett/daedalOS/blob/main/scripts/preloadIcons.js
- https://github.com/DustinBrett/daedalOS/blob/main/scripts/fs2json.js
19. Bundle Analyzer
When I actually took the time to look at my bundle and what was in it, I realized I had imported a lot of unnecessary things on the main app bundle that users were getting on load. This is a very helpful tool for seeing what is inside your Webpack files and then you can go in and use dynamic import
to split it into it's own chunk which will only be loaded when needed.
20. Inline CSS
Loading CSS in the <head>
element I think is still considered to be one of the fastest ways to get styling to the user. One advantage with using styled components and most CSS-in-JS solutions is that it can inline relevant CSS in the static HTML file so that it's ready to go as soon as possible. I don't personally use any CSS files, but if someone was to go that route, other tips such as the CDN, link preload & Early Hints can improve loading for those files.
21. Defer JavaScript
This tip comes for free with some frameworks which already use this attribute, but it's good to keep in mind that if you have <script>
tags in <head>
that you can use defer
so they aren't parser-blocking and they can execute after DOMContentLoaded
.
22. System Fonts
This may not work for everyone but for me who is making a desktop environment in the browser, it seemed like the perfect fit to just use the operating systems "system" font whenever possible. An important performance advantage of this is that the user doesn't have to download any font as they already have what they need. One issue with this will be consistency between operating systems, but I've found in general the fonts are similar and familiar to the users.
23. Async Image Decoding
I don't have much to say about this one other than to say that based on the description, if you want to "reduce delay in presenting other content", you should use decoding=async
. It likely makes a very minor difference, but perhaps with larger images this could be a noticeable change.
24. Responsive Images
Using <picture>
gives you a lot more control over images. Being able to load different images depending on what the browser supports and the state of media queries allows loading of the perfect sized image in any situation, so the browser doesn't need to resize something which either means the image was too small or too big, neither being ideal.
- https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
- https://caniuse.com/mdn-html_elements_link_imagesrcset
- https://developer.mozilla.org/en-US/docs/Web/CSS/@media/resolution
25. Define Image Dimensions
My final tip and an important one for content layout shift is to define the dimensions of images whenever possible. When you define the height and width the browser is able to allocate space ahead of time instead of moving elements on the page as the image loads and the browser realizes it's height and width which were not provided to it in advance.
Thanks for reading!
I appreciate you taking the time to read my article. If you'd like an in depth demonstration of these tips, below I've added my live stream where I spent over 2 hours going over them and showing their functioning within my website (dustinbrett.com).
Posted on July 25, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.