﻿---
title: Blog Performance Optimization Notes
date: 2024-09-27
excerpt: Optimizations adopted while maintaining this site, from image compression to lazy loading and CDN.
tags: [Hexo, Web, Blog, Performance]
cover: https://assets.vluv.space/cover/FrontEnd/blog_performance.webp
updated: 2026-07-08 21:17:37
lang: en
i18n:
  cn: /blog_performance_optimization
  translation: 2
---

## Use Efficient Encodings

Encode assets in formats like `webp` and `webm`. Images can be compressed with the `cwebp` command; videos can be converted with online tools.

Recommended formats:

- Images: webp, avif
- Videos / animated images: webm
- Fonts: woff2

> **Best Practice**
> While MP4 has been around since 1999, WebM is a relatively new file format initially released in 2010. WebM videos are much smaller than MP4 videos, but not all browsers support WebM so it makes sense to generate both.

### Batch-Converting Images

Install the `libwebp` library to get the `cwebp` command. Below is the Python script I use for batch conversion.

> The built-in Windows screenshot tool and QQ's screenshot tool don't support saving screenshots as webp;
> recent versions of third-party tools like PixPin and Snipaste do.

```python
from pathlib import Path
import subprocess
import shutil
import argparse

covert_these_types = (".png", ".jpg", ".jpeg")

def convert_to_webp(input_dir: Path, output_dir: Path):
    """
    Convert files in the given directory (and subdirectories) to .webp.
    Output files are saved under the output directory, preserving the directory structure.
    """

    for file_path in input_dir.rglob("*"):
        # Check whether the file is one of the types to convert
        if file_path.suffix.lower() in covert_these_types:
            # Build the output path; the / operator joins output_dir and relative_path into a new path object
            relative_path = file_path.relative_to(input_dir)
            output_path = output_dir / relative_path.with_suffix(".webp")
            output_path.parent.mkdir(parents=True, exist_ok=True)

            # Convert with the cwebp command
            try:
                subprocess.run(
                    ["cwebp", str(file_path), "-o", str(output_path)], check=True
                )
            except subprocess.CalledProcessError as e:
                print(f"Failed to convert {file_path}: {e}")
        else:
            output_path = output_dir / relative_path
            shutil.move(file_path, output_dir)

def main():
    # Create the command-line argument parser
    parser = argparse.ArgumentParser(
        description="Convert PNG, JPG, and JPEG files to WEBP format."
    )
    parser.add_argument(
        "input_dir", type=str, help="Input directory containing images to convert"
    )
    parser.add_argument(
        "output_dir", type=str, help="Output directory to save converted images"
    )
    args = parser.parse_args()
    input_dir = Path(args.input_dir)
    output_dir = Path(args.output_dir)
    convert_to_webp(input_dir, output_dir)


if __name__ == "__main__":
    main()
```

```bash
[USAGE]
python ./cwebp.py ../source/img/unused/ ./output
```

web.dev has an article recommending replacing GIFs with video formats for better performance: [Replace GIFs with video](https://web.dev/articles/codelab-replace-gifs-with-video?hl=en)

```shell
ffmpeg -i input.gif -c vp9 -b:v 0 -crf 41 output.webm
```

### Results

Here is a before/after size comparison of this site's images:

```wikitext
╭───┬───────────────────┬──────┬───────────╮
│ # │       name        │ type │   size    │
├───┼───────────────────┼──────┼───────────┤
│ 0 │ gallery           │ dir  │  66.2 MiB │
│ 1 │ gallery_origin    │ dir  │ 186.8 MiB │
│ 2 │ thumbnails        │ dir  │  19.6 MiB │
│ 3 │ thumbnails_origin │ dir  │  88.0 MiB │
│ 4 │ unused            │ dir  │  18.4 MiB │
╰───┴───────────────────┴──────┴───────────╯
```

- The gallery directory shrank by about 64.57%
    - Original size: `gallery_origin: 186.8 MiB; 54 images (jpg, png), 3 videos (mp4)`
    - Compressed size: `gallery: 66.2 MiB`
    - Saved: `186.8 MiB - 66.2 MiB = 120.6 MiB`
- The thumbnails directory shrank by about 77.73%
    - Original size: `thumbnails_origin: 88.0 MiB; 100 images (jpg, png)`
    - Compressed size: `thumbnails: 19.6 MiB`
    - Saved: `88.0 MiB - 19.6 MiB = 68.4 MiB`

## Minify HTML/CSS/JS

See [[post-build|another post of mine]]. Consider using [wilsonzlin/minify-html](https://github.com/wilsonzlin/minify-html) and [ESBuild](https://esbuild.github.io/) to minify HTML/CSS/JS assets.

## Lazy-Loading Images

`{shell} npm install hexo-native-lazy-load --save`

```yaml
lazy_load:
  enable: true
  onlypost: false
```

Run `{shell} hexo clean`, deploy, then open devtools on the page; you'll see that img elements now carry `loading="lazy"`.

![devtool demo](https://assets.vluv.space/博客性能优化-2024-09-28-15-26-49.avif)

## Instant Page (Migrated Away)

> instant.page uses just-in-time preloading — it preloads a page right before a user clicks on it.

Usage: add a `scripts/instant-page.js` file in the root directory and register a `hexo injector`.

```js scripts/instant-page.js
hexo.extend.injector.register(
  "body_end",
  '<script src="//instant.page/5.2.0" type="module" integrity="sha384-jnZyxPjiipYXnSU0ygqeac2q7CVYMbh84q0uHVRRxEtvFPiQYbXWUorga2aqZJ0z"></script>',
  "default",
);
```

> Update, 2026-06: this site has replaced instant.page with the browser-native Speculation Rules API. For why I migrated, how to write the rules, and how it interacts with Swup/PJAX, see [[speculation-rules-api|Replacing instant.page with the Speculation Rules API]].

## Pjax

Icarus currently has only preliminary Pjax support, see <https://github.com/ppoffice/hexo-theme-icarus/pull/1287>

## OSS + CDN

A widely used free option: GitHub as an image host with jsDelivr as the CDN. It's free, but access from mainland China is mediocre, and it violates their terms of service — it's an abuse of public resources.

Cloudflare offers a free quota of R2 + CDN, which is worth considering; it requires a credit card, and binding a domestic UnionPay card through PayPal works. This is what this site currently uses.

## To Be Continued(?)

## Tools

- [convert2.cn](https://convert2.cn)
- [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview)
- [Hexo Injector](https://hexo.io/zh-cn/api/injector.html)

## Further Optimizations

- [Core Web Vitals](https://web.dev/explore/learn-core-web-vitals?hl=zh-cn)
- [Use WebP images](https://web.dev/articles/serve-images-webp)
- [Best practices for fonts](https://web.dev/articles/font-best-practices)
- [Faster, and faster still: reducing my blog's white-screen time](https://blog.skk.moe/post/improve-fcp-for-my-blog/)
- [Optimizing Google PageSpeed Insights scores for the hexo icarus theme](https://www.huihongcloud.com/2021/10/17/hexo/%E8%AE%B0hexo%20icarus%E4%B8%BB%E9%A2%98%E4%BC%98%E5%8C%96Google%20PageSpeed%20Insights%E5%88%86%E6%95%B0/#%E5%A4%84%E7%90%86font-awesome%E5%9B%BE%E6%A0%87%E7%9A%84%E5%8A%A0%E8%BD%BD)
