VTRyo Blog

一歩ずつ前に進むブログ

Hugo+Netlifyで構成している静的サイトのポートフォリオを約20%高速化してみた

パフォーマンスチューニング(ISUCON)でついた傷は同じパフォーマンスチューニングでしか癒えないどうも私です。

自分のWebサイトがいっつも遅えなと思っていたので、これを気にチューニングしてみました。なお計測と改善ポイントはGoogleのPageSpeed Insightsに従っていくだけのかんたんなお仕事にします。

対象のWebサイトはHost on Netlifyの静的サイトとして構築してあります。

gohugo.io

vtryo.me

初回計測

計測すると、モバイルの速度に難ありのようです。

Result PageSpeed Insights

大変親切な指摘です。

シークレットブラウザで初回アクセスしたときのNetwork状況です。画像の表示に時間がかかっていることがわかります、指摘のとおりですね。

この数値を取ったときのインターネット速度はこちらです。

最も大きなボトルネックになっている画像配信周りから改善していきます。

次世代フォーマットでの画像の配信

WebP や AVIF などの画像形式は、一般的に PNG や JPEG より圧縮率が高く、ダウンロード時間やデータ消費量が抑えられます

とあります。WebPといった画像形式があることを初めて知りました。

WebPとAVIF

JPEGやPNGに比べて優れた圧縮特性を持つ画像形式。この形式でエンコードをすれば読み込みが高速化されてデータ消費量を節約できるようです。

AVIF and WebP are image formats that have superior compression and quality characteristics compared to their older JPEG and PNG counterparts. Encoding your images in these formats rather than JPEG or PNG means that they will load faster and consume less cellular data.

Serve images in modern formats

AVIFはChrome、Firefox、Operaでサポートしていて、WebPを始め他の形式よりもファイルサイズが小さくなるとのことです。ただしその分サポートしているブラウザが少ないのでもう一歩足りない印象があります。
WebPはChrome、Firefox、Safari、Edge、Operaの最新バージョンでサポートしているため使い勝手が良さそうです。

コマンドラインによる変換や、サイトでの変換が可能なので画像形式を変換するタイミングはいくらでも考えられるでしょう。

developers.google.com

どちらを採用するか

ポートフォリオの性質上、あらゆる環境の人に閲覧される想定です。サポートしているブラウザの数的にも有利なWebPが良さそうです。

また、私のポートフォリオサイトはHugoを使っており、現時点でAVIFをサポートしていないようです。仮にHugoに画像処理を任せようとした場合ここが後でネックになるのはちょっと避けたいです。

github.com

がんばって自力で実装することは可能でしょうが、主旨と外れてくるので今回はサポート済みのWebPにしておきましょう(今回画像処理をHugoに任せるとはいってない)

gohugo.io

WebPに変換する

Hugo自体に画像処理を任せるのは一旦あとにしておきます。まずは画像形式を変更することで高速化することを確かめます。

変換する方法は何でもいいです。Webサイトをはっておきます。

lab.syncer.jp

僕はDevToysを使います。

devtoys.app

MacOSの人は、DevToysをこちらからインストールできます。

qiita.com

github.com

なおこのnote.pngの元のサイズは3.6MBでした。改めて見ると激重すぎる。

もちろんWebP形式をサポートしていますので、これを画像処理します。

Result Image Converter

37KBになりました。

note.webp

画質も何ら問題なさそうです。

他の画像もコンバートしたあとDeployしてみました。

after Deploy site

2s程度かかっていた箇所は300msを切っています。やったぜ。

vtryo.me

今一度計測する

変更したらもう一度計測してみます。結果が変わっていることがわかりました。

もう少し高速化できる余地がありそうなのでやっていきます。

レンダリングを妨げるリソースの除外

僕個人はフロントエンド事情に詳しいわけではないので一から考えていきます。

レンダリングをブロックしているリソース(第三者リソースは除く)は以下でした。

  • bootstrap.min.css
  • experiences.css

web.dev

DevToolsを使ってどれくらい使われているかを確認します。

ほぼ使っていないようです。bootstrapなので、おそらく全部入りなのだと推察します。使ってないけどダウンロードしてしまって遅くなってるというわけです。

この当たりの解決策として、ない知識の中で浮かんだ方法論としては以下の内容です。

  • 使っていないCSSを削除する
  • (ドキュメントに記載の通り)インライン化する

工数は置いておいて、使っていない部分を削除するのは良さそうな手です。 しかし、htmlにCSSをインライン化するのは可読性を下げるのではないかと心配です。そもそもそれを嫌がってインライン化してないと考える方が自然でしょう。

お悩みどころを具体化してググラビリティを上げたところで調べると、静的サイトのパフォチューをしている記事発見。

qiita.com

PurgeCSSというツールがあるようです。

purgecss.com

PurgeCSS is a tool to remove unused CSS. It can be part of your development workflow. When you are building a website, you might decide to use a CSS framework like TailwindCSS, Bootstrap, MaterializeCSS, Foundation, etc... But you will only use a small set of the framework, and a lot of unused CSS styles will be included.

まさしく今抱えている課題を解決してくれそうです。

This is where PurgeCSS comes into play. PurgeCSS analyzes your content and your CSS files. Then it matches the selectors used in your files with the one in your content files. It removes unused selectors from your CSS, resulting in smaller CSS files.

まずは試してみましょう。Hugoもサポートしています。

PurgeCSS

  • config.yamlの最後に設定を追加
build:
  writeStats: true
  • npmでパッケージインストール

プロジェクトのルートディレクトリでパッケージをインストールしておきます(Node.jsが入ってなかったので適当にインストールした)。

これでpackage.jsonが作成されました。

npm install postcss postcss-cli @fullhuman/postcss-purgecss
  • postcss.config.jsを作成

同じくルートディレクトリに、ファイルを作成します。

purgecss.com

const purgecss = require('@fullhuman/postcss-purgecss')({
  content: ['./hugo_stats.json'],
  defaultExtractor: content => {
    const els = JSON.parse(content).htmlElements;
    return [
      ...(els.tags || []),
      ...(els.classes || []),
      ...(els.ids || []),
    ];
  },
  safelist: []
});

module.exports = {
  plugins: [
    ...(process.env.HUGO_ENVIRONMENT === 'production' ? [purgecss] : [])
  ]
};
  • head にコードを挿入
    {{ $css := resources.Get "css/bootstrap.min.css" | resources.PostCSS }}
    {{ if hugo.IsProduction }}
      {{ $css = $css | minify | fingerprint | resources.PostProcess }}
    {{ end }}
    <link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}" >
  • hugo serve したらエラーになった
$ hugo serve
Start building sites … 
hugo v0.101.0+extended darwin/arm64 BuildDate=unknown
Error: Error building site: failed to render pages: render of "home" failed: "vtryo.me/themes/toha/layouts/index.html:14:64": execute of template failed: template: index.html:14:64: executing "index.html" at <resources.PostCSS>: error calling PostCSS: type <nil> not supported in Resource transformations
Built in 24 ms

<resources.PostCSS>: error calling PostCSS: type not supported in Resource transformations が突破できない

あちこちのissueやドキュメントを探し回ってみたものの、PostCSSがnilになる理由がどうしてもわからず困惑。残念ながら知見もなく解決できそうにない。

高速化も推定2sという状況なので、最悪諦めてもいいかという気持ちになってきました。

仕方ないのでコマンドラインでCSSを圧縮することにします。

npm i purgecss && purgecss --css themes/toha/static/assets/css/bootstrap.min.css --content themes/toha/layouts/index.html --output public/assets/css/bootstrap.min.css 

ビルドしたらレイアウトが壊れてしまいました。抜けられないハマりでもうだいぶ疲れている。

圧縮後のサイトの様子

HTML, CSSなんもわからん。

PurgeCSSを諦め、他のpngをWebPに変換してDeloy

見出しの通り諦め、残っていた画像もWebpにしてから三度目の測定です。

ふむ。特段変化無しです。まあそりゃそうか。

残念ですが力不足です。

ちなみにアクセス速度

2s近くかかっていた画像はなくなって一番大きなボトルネックは解消しました(初回もNo throttlingだったので最後も合わせてある)。

チューニング前
チューニング後

その結果としてレンダリングをブロックしているリソースが目立つようになりましたね。

なおFast 3Gでアクセスを試行してみても800ms程度でした。1s切っているので今回は妥協点とします*1

おわりに

日頃HTML, CSSなんもわからんという感じなので、画像形式だけで速くなるとか、CSSがレンダリングブロックしてるとか諸々勉強になりました。もうちょっとまともにWebサイトのこと知るべきだなと痛感。
そもそも計測の仕方はこの方がええでっていうのがあればぜひ教えて下さい(フロントのチューニングド素人すぎて泣ける)。

次にやるとしたら、Netlifyのキャッシュ設定を変えることでしょうか。

NetlifyのキャッシュはデフォルトでCache-Control: max-age=0,must-revalidate,publicのようです。クラスメソッド社のブログがわかりやすいので置いておきます。

dev.classmethod.jp

キャッシュ設定も考えることがたくさんあるので、楽しみですね。ではまた。

*1:時間も有限なので…