iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🚄

Making Astro Blazing Fast: Comprehensive Performance Optimization

に公開

Note

This article is a re-edit of a piece I wrote around April 2024. Please note that some information may be slightly outdated or not fully verified.

Introduction

I'm sure you all often use Astro for web development (pressure).
While building a site with Astro results in a blazing-fast site even without specific measures, this article is about taking proper steps to optimize and further increase that speed.
Please keep in mind that the content is very simple.

Lighthouse

Lighthouse is frequently used to measure page performance. This service is an open-source page insight tool developed by Google. It's quite familiar, isn't it?
Most people probably use this for site measurement.
By installing the Chrome extension, you can measure a localhost site even in an offline environment.
When testing pages that are already published, it is easy to test with a web app called PageSpeed Insights. This is simply the web app version of Lighthouse, so there is no difference in functionality. You can verify your site in the same way with either.
Once the site is somewhat close to completion, the next step is to check it with Lighthouse and improve the site while looking at the results.
Lighthouse analyzes the site in five categories: Performance, Accessibility, Best Practices, SEO, and PWA. This time, we will focus on Performance, which is the speed of the site. The other four categories will not be mentioned in this article.
As a note when measuring the site, please do not test in a development environment. Be sure to build it properly and launch the production environment before measuring. In the case of Astro, it is possible to launch the production environment with npm run preview after executing npm run build.

Image Optimization

In Astro, images within the src directory are automatically converted to WebP.

To put it simply, WebP is an extremely lightweight image format developed by Google. Since it offers the benefit of faster load times, I have the impression that it is currently being actively adopted in the web industry.

To use this feature, you must use the Image component.

---
import { Image } from "astro:assets";
import Logo from "../assets/logo.png";
---
<Image src={Logo} alt="Logo" />

Writing it this way ensures optimization. However, you might feel that the Image component lacks extensibility when trying to handle complex displays.

<img src={Logo.src} width={Logo.width} height={Logo.height} alt="Logo" >

Using the img tag allows you to use images just as you would in HTML. In this case, the optimization process is less effective, so it is better to use the Image component as much as possible.

However, if you do not want to use WebP for some reason, you can place image files in the public directory to use them in their original format without processing. This is where I keep things like favicons.

Font Optimization

Web fonts come in many varieties, but this chapter focuses on Google Fonts. When using Google Fonts, you can increase speed by applying simple optimization.

npm install astro-google-fonts-optimizer

This tool mimics the font optimization process of Next.js. It optimizes by fetching the font's CSS at build time, inlining it, and embedding it directly into the HTML, which eliminates the time spent fetching CSS from Google's servers.

---
import { GoogleFontsOptimizer } from "astro-google-fonts-optimizer";
---
<GoogleFontsOptimizer url="https://fonts.googleapis.com/css2?family=Inter:wght@200;400;500;700&display=swap" />

Writing it like this will download the CSS during the build process. The GoogleFontsOptimizer component should be placed inside the head tag. The same optimization is possible with Bunny Fonts, which is Google Fonts compatible, but I haven't used it yet as Google Fonts suffices for now.

YouTube Embedding

Usually, you use an iframe tag to embed YouTube, but it takes time to load. To load it efficiently, we use lite-youtube-embed.

npm install @astro-community/astro-embed-youtube

A tool for using lite-youtube-embed with Astro.

---
import { YouTube } from '@astro-community/astro-embed-youtube';
---
<YouTube id="AAAAAAA" />

How it works: it doesn't load the YouTube video initially, but only loads the thumbnail from i.ytimg.com. However, if you want to host the thumbnail image on your own server, downloading it manually every time is inefficient.

Astro provides a function called getImage. By using this, remote images can be downloaded during the build and placed in the _astro directory.

---
import { getImage } from "astro:assets";
const posterSource = await getImage({ src: posterURL, inferSize: true });
---
<lite-youtube style={`background-image:url('${posterImage.src}');`} />

Further optimization is possible by adding such code to astro-embed-youtube.

Analytics

Calling external scripts such as Google Analytics or advertisements inevitably reduces site performance. When I embedded Google Analytics and checked with Lighthouse, I confirmed a drop of about 5 points.

npx astro add partytown

Partytown eliminates the impact on the site's speed by executing the scripts you want to load in a separate thread instead of the main thread. It's easy to use: just add type="text/partytown" to the script tag you want to use with Partytown, and add settings to astro.config.mjs.

partytown({
  config: {
    forward: ["dataLayer.push"],
  },
}),

It works by forwarding dataLayer.push to the window object. Since Google Analytics needs to access the global window object, it will not function correctly without this setting.

File Compression

The smaller the file size received from the server during access, the less data is transferred and the shorter the load time.

npm run astro add @playform/compress

Mentioned here for the first time, this astro command performs package installation and additions to astro.config.mjs interactively. It's very convenient because it simplifies complex configurations.

Simply using this tool will automatically compress the build files. I actually tried compressing a test project.

No Compression With Compression
786 KB 164 KB

As shown in Table 1, you can see that it was compressed to less than 1/4 of its original size. It automatically compresses CSS, HTML, JavaScript, and images through various processes, such as removing line breaks from the code.

Since the explanation in this article is insufficient, please check the GitHub of @playform/compress for more details. As mentioned earlier, Astro itself also has image compression capabilities. Unlike Astro's built-in features, @playform/compress also compresses images within the public folder. Rest assured that the file format itself will not be converted.

CSS Inlining

In web development, it is standard practice to load CSS first. This is to prevent the user from seeing an unstyled page for a split second before the layout changes, but in reality, it even loads CSS for parts of the page that aren't visible to the user.

npm install -D -E @playform/inline

Since this package is new, it does not support the astro command mentioned earlier, so you need to add the code to astro.config.mjs manually.

import playformInline from "@playform/inline";
export default defineConfig({
  integrations: [playformInline()],
});

This process utilizes Critters. No special configuration is required by default.

However, when I applied this to the website I operate, I encountered an issue where web fonts failed to load specifically in the production environment. The cause is currently unknown, but it might be incompatible with the GoogleFontsOptimizer I introduced earlier.

According to the README, Critters settings can be used as-is, allowing you to disable the inlining process for fonts only by setting fonts to false.

playformInline({ Critters: { fonts: false }})

Although this was meant to prevent font inlining, it still didn't work correctly.

When weighing whether to prioritize fonts or CSS,
I've decided to resume using Critters once the cause has been identified.

Conclusion

That's all!

Discussion