はじめに
直近でパフォーマンスチューニングのタスクを実施させていただいていますが、現状までに行った対応策を記事にまとめようと思います。
本記事では、フロントエンドにおけるパフォーマンスチューニングの方法について記載します。
また、Nuxt と Vite におけるチューニング方法をまとめます。
実施内容
- Component のダイナミックインポート(非同期インポート)
- 画像の非同期読み込み
- 画像の圧縮
- デバイスごとに画像サイズを最適化
- chunk の細分化
- フレームワークのバージョンアップ対応
- サードパーティスクリプトのドメイン事前接続
- 重い API における、データ表示のスケルトン実装
Component のダイナミックインポート(非同期インポート)
こちらは、Nuxt であれば LazyXXX
でコンポーネントを非同期読み込みする方法や、Vue であれば defineAsyncComponent
で非同期読み込みする方法になります。
template>
/>
template>
script setup lang="ts">
import LazyXXX from '@/components/XXX/index.vue'
script>
LazyXXX
の定義をする際に、nuxt.config.ts
で components: false
だと使用できないので、その点にご注意ください。
template>
/>
template>
script setup lang="ts">
const LazyXXX = defineAsyncComponent(() => import('@/components/XXX/index.vue'))
script>
主に、ビューポートから溢れたコンポーネントや、サードパーティ製のパッケージにおける UI コンポーネント(例えば chart.js
など)に対して非同期読み込みを実施しました。
こちらの対応によって、build 後の js のサイズが縮小される効果を得られます。
非同期読み込みを実装することで、メインの js から非同期コンポーネントの処理が別の chunk に切り出されるためです。
こちらの対応によって、FCP / LCP の改善が見込めます。
画像の非同期読み込み
こちらはよくあるパフォーマンスチューニング内容かと思います。
単純に img 要素に対して、loading="lazy"
を付与するだけの対応になります。
ただ、その対応だけでもパフォーマンスが大きく上がったりします。
下記は NuxtImg を使用したサンプルコードになります。
alt="sample" src="/public/sample.png" loading="lazy" width="100" height="100" />
SEO の観点で alt
や width
/ height
の指定も必須で行うようにしましょう。sizes
などの指定も必要ですが、そちらはこの後に記載します。
こちらの対応によって、FCP / LCP の改善が見込めます。
画像の圧縮
画像は注意しないとファイルサイズの大きなものをコミットしてしまったりします。
そうならないように webp
など、よりファイルサイズが小さな画像となるように圧縮することも大事です。NuxtImg
を使用していれば format
属性で変換できたりしますが、パッケージによる圧縮で画像が崩れる可能性もあるため、cloudconvert などを使用して事前に webp
に変換します。
こちらの対応によって、FCP / LCP / TTFB の改善が見込めます。
デバイスごとに画像サイズを最適化
こちらは img タグにおける sizes / srcset の指定となります。
レスポンシブ対応のアプリケーションでは画像はそれぞれのブレイクポイントで適切なサイズを指定することが重要となります。
こちらに関しても NuxtImg を使用すれば簡単に設定できます。
必要に応じて、picture (Nuxt であれば NuxtPicture)タグの使用も検討します。
こちらの対応によって、FCP / LCP の改善が見込めます。
chunk の細分化
こちらは Vite の設定になります。
Nuxt3 から split chunk が実装され、細かく chunk が分けられるようにビルドされるようになりましたが、アプリケーションが大規模になるにつれて、エントリーポイントの js が非常に大きくなったりします。
その際に、Vite の設定で chunk をさらに細かく分割する設定を追加します。
下記は nuxt.config.ts
における vite の chunk 設定例です。(あくまでもサンプルコードです)
nuxt.config.ts
const libraryChunkMapping: Recordstring, string[]> = {
'vue-router-libs': ['vue-router'],
'date-libs': ['dayjs'],
'data-libs': ['lodash-es']
}
export default defineNuxtConfig({
vite: {
build: {
rollupOptions: {
output: {
manualChunks(id) {
const libraryChunkName = Object.keys(libraryChunkMapping).find((key) => {
return libraryChunkMapping[key].some((lib) => id.includes(lib))
})
if (libraryChunkName !== undefined) {
return libraryChunkName
}
}
}
}
}
},
// ... 省略
})
vite では 500kb を超えると、ビルド時に警告が出ますが、上記の対応を細かく設定することでその警告を出さないように修正することもできてます。
上記の対応により、FCP / LCP / TTFB などの改善が見込めます。
1点だけ注意が必要ですが、こちら、chunk ファイルの定義を記載するのはいいですが、非同期コンポーネントで使用しているソースは含めないように設定を書く必要があります。
というのも上記で設定した chunk は画面のファーストロードで必ず読み込まれるため、せっかく非同期コンポーネントにして非同期で読み込むように設定しても、js をブロックしてしまいます。
フレームワークのバージョンアップ対応
使用しているフレームワークも日々パフォーマンス改善をしてくれています。
そのため、アップグレードできるなら積極的にしておくと良いでしょう。
フレームワークはもちろん、アプリケーションで使用しているユーティリティや UI パッケージ等もリリースノートを確認し、アップグレードすることもパフォーマンス改善につながります。
実際に Nuxt のバージョンアップをしたら、パフォーマンスのスコアが微量ながらも向上しました。
サードパーティスクリプトのドメイン事前接続
こちらは、頻繁にアクセスする外部ドメインへの接続を事前に行う対応となります。
そうすることで、DNS ルックアップの事前実行や TCP ハンドシェイクの事前確立を行うことができます。
下記は nuxt.config.ts
で Google Tag Manager に対する事前接続の設定方法となります。
nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
link: [
{
rel: 'preconnect',
href: 'https://www.googletagmanager.com'
}
]
}
}
})
その他にも、外部フォントの配信元ドメインや、マイクロサービスアーキテクチャであれば、API 側のドメインも追加しておくといいでしょう。
ただ、設定しすぎも良くありません。
Chrome / Chromium 系では最大6個までなどの制約があったりするので、サポートブラウザに応じて適切な数に絞り、優先度の高いものだけを設定するようにしましょう。
重い API における、データ表示のスケルトン実装
こちらは、他にチューニングの余地がない場合や、施策の実施に半年以上の工数がかかるといった状況で検討する施策です。
UI の変更を伴うため、プロダクトマネージャー(PdM)やデザイナーに事前に確認をとってから実装するようにします。
マイクロサービスなどでは、API のレスポンス速度がどうしても遅くなってしまうものがあったりします。
その場合、該当データを表示している箇所を非同期で取得するように変更し、取得中はスケルトンUIを表示するようにします。
下記はスケルトン UI のイメージです。
まとめ
パフォーマンスチューニングは現在も継続して実施していますが、本記事では途中までの内容をまとめました。
ここまでの対応で、PageSpeed Insights でのパフォーマンススコアが 10〜30%ほど向上しています。
本記事が、他の方のパフォーマンスチューニングのヒントになれば幸いです。
Views: 0