フロントエンドのパフォーマンス改善 Vue3/Nuxt/Vite
概要
仕事でフロントエンド基盤(vue3/vite)のパフォーマンス改善に取り組んだので、備忘録的に記載していく。主に使用したツールは以下
-
light house
https://developer.chrome.com/docs/lighthouse/overview/?hl=ja -
chrome dev tool (memory/performance計測)
https://developer.chrome.com/docs/devtools?hl=ja
proxサーバ(nginx)の対応
proxyサーバとしてnginxを使っていたので、その設定を最適化した。
nginxのgzip対応
gzipによるネットワーク通信ファイルの圧縮を行った。
これが、パフォーマンス改善に一番効いたと思う。
nginx.conf
server {
gzip on;
gzip_types text/css application/javascript application/json application/font-woff application/font-tff image/gif image/png image/jpeg application/octet-stream;
}
nginxのブラウザキャッシュ対応
cache-controllを設定し、ネットワーク通信によってサーバから取得されたファイルがブラウザにキャッシュされるようにした。
maxageに関しては今回は基本的に静的なファイルをやり取りすることが多かったので、気にしなかったが、
動的ファイルを使用する場合は、もっと低く設定する必要がある。
nginx.conf
location ~* \.(gif|jpe?g|png|webp|ico|svg|css|js)$ {
add_header Cache-Control "s-maxage=86400";
}
Viteの対応
SplitVendorChunkPluginの導入
Vite 2.8 まではデフォルトのチャンク戦略は index と vendor にチャンクを分割していました。これは SPA にはよい戦略の場合もありますが、すべての Vite ターゲットのユースケースに対して一般的な解決策を提供するのは困難です。Vite 2.9 からは、manualChunks はデフォルトでは変更されなくなりました。設定ファイルに splitVendorChunkPlugin を追加すれば、vendor を分割するチャンク戦略を引き続き使用できます:
チャンク戦略とはJSのバンドルファイルを複数の小さなファイルに分割することで、サイトのパフォーマンスを改善しようとする戦略。
ブラウザが必要なコードだけをダウンロードすることが、できるので通信量が減る。
viteではsplitVendorChunkPluginを導入することで、チャンク戦略が利用できる
// vite.config.js
import { splitVendorChunkPlugin } from 'vite'
export default defineConfig({
plugins: [splitVendorChunkPlugin()],
})
動的importの導入
動的importを導入することで、viteがチャンクをもっと細かく分離してくれるらしい。
実際に導入してみたが、あまりパフォーマンス改善は見られなかった。
// 静的ロードでは、アプリケーションを起動する際にモジュールが読み込まれる。
// モジュールは実際にそのコード部分が呼ばれる前に事前に読み込まれる。
import 'sample' from 'Sample'
const sampleInstance = new Sample()
// 動的ロードでは、アプリケーションを起動する際ではなく
// アプリケーションを起動して実際にそのコードが呼ばれた際に、モジュールが読み込まれる
import('Sample')
.then(sample => {
// 動的に読み込まれたsampleクラス
const sampleInstance= new sample();
});
パフォーマンスに関わる設定項目の確認
build.minify
ミニファイを無効にするには false を設定するか、使用するミニファイツールを指定します。デフォルトは esbuild で、これは terser に比べて 20~40 倍速く、圧縮率は 1~2%だけ低下します。ベンチマーク
build.cssMinify
このオプションによって、デフォルトの build.minify を使うのではなく、CSS ミニファイを具体的に上書きすることで、JS と CSS のミニファイを別々に設定できるようになります。Vite はデフォルトでは esbuild を使用して CSS をミニファイしています。
build.cssCodeSplit
CSS コード分割を有効/無効にします。有効にすると、非同期 JS チャンクでインポートされた CSS はチャンクとして保存され、チャンクがフェッチされるときに一緒にフェッチされます。
メモリリークの検査
memLabを使ってメモリリークを検出したページについて、
Chrome Dev Toolでメモリリークを詳しく調べた。
Chromeのdev toolでメモリ使用量のsnpp shotを取り、それをもとにメモリリークが起きている箇所を調べた。
Source Mapをviteで生成し、それをdev toolで読み込むことで原因箇所を特定する
CSSの非同期ロード
画面描画時にすぐに必要ないCSSを非同期的にロードすることで
CSSによるレンダリングブロックを避ける
<link rel="stylesheet" href="https://test/sample.css" media="print" onload="this.media='all'">
Nuxt3
コンポーネントの動的ロード
Lazy
をつけることで必要になったときのみcomponentをロードできるらしい。
今回はページの読み込み時にすべてのcomponentを読み込みたかったので実施しなかった。
<script setup>
const show = ref(false)
</script>
<template>
<div>
<h1>Mountains</h1>
<LazyMountainsList v-if="show" />
<button v-if="!show" @click="show = true">Show List</button>
</div>
</template>
hydrationの遅延
ハイドレーションとは
Webページを作る際、サーバー側でHTMLを生成してブラウザに送るが、このHTMLは静的である。
だが、ユーザーがページで何か操作をしたり、動的な内容(変わる内容)が必要な場合は、JavaScriptが必要になる。このJavaScriptがページに機能を追加して「生きた」ページにする過程を「ハイドレーション」と与呼ぶ。
「nuxt-delay-hydration」はこのハイドレーションを遅らせる機能を提供する。通常、ページを読み込むときには、HTMLがブラウザに届いてすぐにJavaScriptが動き始めて、ハイドレーションが行われる。でも、これを遅らせると、ページがもっと速く表示されることがある。特に、JavaScriptがたくさんあるページでは、この違いが大きい。
「nuxt-delay-hydration」を使うと、特定の条件が満たされるまでハイドレーションを遅らせることができる。例えば、ユーザーがスクロールしたり、特定の時間が経過した後にハイドレーションを開始するように設定できる。
使用する場合はpluginに設定する
server/plugins/compression.ts
import { useCompression } from 'h3-compression'
export default defineNitroPlugin((nitro) => {
nitro.hooks.hook('render:response', async (response, { event }) => {
if (!response.headers?.['content-type'].startsWith('text/html'))
return
await useCompression(event, response)
})
})
参考
参考になったサイト