- パフォーマンス改善の施策にはどういったものがある?
- より効果的な施策には何がある?
- パフォーマンス改善でやるべきことは分かったが、Nuxt.jsでの実装方法が分からない
本記事では、フロントエンドのパフォーマンス改善に焦点を当て、特にNuxtを使用している方向けに、具体的な施策とその実装例をご紹介します。
サイトパフォーマンス改善の施策一覧と効果
施策名 | 主な効果指標 | 効果度 | 施策内容 | 期待される効果 |
---|---|---|---|---|
画像の最適化 | FCP LCP CLS | 大 | widhth/heightの指定、画像のサイズ圧縮、WebP形式への変換、Lazy Load、読み込み優先度の指定 | 画像のダウンロード時間短縮、LCP(ページの表示速度)の改善、レイアウトシフトの防止 |
CSSの最適化 | FCP LCP | 大 | クリティカルCSSのインライン化、非クリティカルCSSの遅延読み込み、不要なCSSの削除、CSSファイルの分割 | FCP(最初の描画速度)の改善、レンダリングブロックの解消 |
Google Fontsの最適化 | FCP LCP TBT | 大 | フォントファイルのローカルホスト | FCP(最初の描画速度)の改善、レンダリングブロックの解消 |
リソースの事前圧縮 | FCP TTFB | 中 | CSS/JSをビルド時に圧縮する | ファイルダウンロード時間の短縮、FCP(最初の描画速度)の改善 |
サードパーティのドメイン事前接続 | FCP TTFB | 小 | サードパーティスクリプトのドメインを事前接続する | スクリプトのダウンロード開始までの時間を短縮 |
コンテンツ遅延読み込み | FCP | 小 | 初期描画時に不要なコンポーネント(コンテンツ)の遅延読み込み | FCP(最初の描画速度)の改善 |
以下は実現が難しく、途中で断念した施策です。
施策名 | 主な効果指標 | 効果度 | 施策内容 | 期待される効果 |
---|---|---|---|---|
サードパーティのメインスレッドブロック削減 | FCP LCP TBT | 大 | Google Analyticsなどのサードパーティスクリプトをworkerスレッドで実行する | メインスレッドブロックの解消 |
リソースの事前読み込み | FCP | 中 | 初期描画時に使用するリソース(画像など)を事前に読み込む | FCP(最初の描画速度)の改善 |
Nuxt.jsでの実装例
画像の最適化
@nuxt/imageモジュールを使用すると、画像の圧縮やサイズ最適化を簡単に実装できます。
npm i @nuxt/image
export default defineNuxtConfig({
modules: ['@nuxt/image']
})
LCP判定されている画像は以下サンプルコードのようにfetchpriorityとpreloadをつけることを推奨します。また、ファーストビューで表示しない画像はloading="lazy"
をつけると良いでしょう。
<NuxtImg
src="/img/hero.png"
alt="ヒーロー画像"
sizes="sm:290px md:400px"
width="400px"
height="600px"
:preload="{ fetchPriority: 'high' }"
fetchpriority="high"
/>
CSSの最適化
CSSファイルの分割
Nuxt.jsではデフォルトでCSSファイルが分割されるようになっています。
CSSファイルが分割されることで1ファイルあたりのサイズが減り、処理時間の短縮が見込めます
非クリティカルCSSの遅延読み込み
Nuxt.jsでは、デフォルトでよしなにCSSのインライン化と外部ファイル化をしてくれています。
ただ、外部ファイルのCSSは初期描画時にレンダリングをブロックするので、FCP悪化の大きな原因になります。
Nuxt.jsではnuxt-modules/crittersというモジュールがありますが、私が試したところデグレを起こしたため、外部ファイルのCSSをレンダリングブロックさせない処理を独自実装しました。
以下のコードをserver/plugins
配下に配置してください。
import isString from 'lodash-es/isString'
/** CSS link regex */
const CSS_LINK_REGEX = /<link([^>]*?)rel="stylesheet"([^>]*?)href="([^"]+\.css)"([^>]*?)>/g
/** media属性を取得するための正規表現 */
const MEDIA_ATTRIBUTE_REGEX = /media="([^"]+)"/
/** media属性を削除するための正規表現 */
const MEDIA_ATTRIBUTE_REMOVE_REGEX = /\s*media="[^"]+"/g
/**
* CSS link要素の変換処理
* @param {string} _ - マッチした文字列(使用しない)
* @param {string} beforeRel - rel属性の前の部分
* @param {string} betweenRelHref - rel属性とhref属性の間の部分
* @param {string} href - href属性の値
* @param {string} afterHref - href属性の後の部分
* @returns {string} 変換後のlink要素文字列
*/
const transformCssLink = (
_: string,
beforeRel: string,
betweenRelHref: string,
href: string,
afterHref: string
): string => {
const fullTag = `<link${beforeRel}rel="stylesheet"${betweenRelHref}href="${href}"${afterHref}>`
const mediaMatch = MEDIA_ATTRIBUTE_REGEX.exec(fullTag)
const originalMedia = mediaMatch?.[1] ?? 'all'
const lazyLink = fullTag
.replace(MEDIA_ATTRIBUTE_REMOVE_REGEX, '')
.replace(
/>$/,
` media="none" onload="if(media!=='${originalMedia}')media='${originalMedia}'" fetchpriority="high">`
)
return `${lazyLink}<noscript>${fullTag}</noscript>`
}
/**
* 非クリティカルCSSをレンダリングブロックせずに読み込むためのプラグイン
*/
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('render:html', (html) => {
html.head = html.head.map((headItem) => {
return isString(headItem) ? headItem.replace(CSS_LINK_REGEX, transformCssLink) : headItem
})
})
})
では、先ほどのコードを解説します。
<link rel="stylesheet">
タグにmedia="none"
を設定してレンダリングブロックを無効化し、CSSファイルが読み込み完了した時点でonloadイベントでmedia="all"
に変更して適用します。
またfetchpriority="high"
を付与することで、読み込みタイミング自体は変えずにレンダリングブロックのみを回避しています。
Google Fontsの最適化
Google Fontsは便利ですが、外部からの読み込みはFCPを遅延させる原因になります。
Nuxt.jsでは、@nuxtjs/google-fontsを使用してフォントファイルをローカルにホストすることで、初期描画時のレンダリングブロックを回避することができます。
npm i @nuxtjs/google-fonts
googleFonts
のfamilies
やその他のオプションは適宜変更してください。
export default defineNuxtConfig({
modules: ['@nuxtjs/google-fonts'],
googleFonts: {
families: {
'Roboto Condensed': true,
'Noto Sans': {
wght: '100..900'
}
},
display: 'swap',
prefetch: false,
preconnect: false,
preload: true,
download: true,
base64: false,
useStylesheet: true,
overwriting: true
}
})
上記の設定を追加することで、ビルド時にフォントファイルがローカルにダウンロードされます。
リソースの事前圧縮
CSSとJavaScriptのサーバー応答時間を短縮するために、ビルド時にファイルの圧縮を行います。
nuxt.config.ts
に設定を追加して事前圧縮を有効化
export default defineNuxtConfig({
nitro: {
compressPublicAssets: {
gzip: true,
brotli: true
}
}
})
サードパーティのドメイン事前接続
「Script Evaluation」 の前の段階であるスクリプトのダウンロード開始までの遅延を減らすため、サードパーティドメインへ事前接続を行います。
nuxt.config.ts
に設定を追加します。
以下はGoogle Tag Managerのドメインの設定例です。
export default defineNuxtConfig({
link: [
{
rel: 'preconnect',
href: 'https://www.googletagmanager.com'
},
]
})
コンテンツ遅延読み込み
初期描画時に必要のないコンポーネントをdefineAsyncComponentで遅延読み込みさせます。
const footerCta = defineAsyncComponent(() => import('@/components/footer/cta.vue'))
サードパーティのメインスレッドブロック削減
この施策は実現が難しく、現状はまだpendingになっていますが、どこまで調査したかも含めて記述しておきます。
やりたいこととしては、「workerスレッドでサードパーティスクリプトを実行してメインスレッドのブロック時間を削減する」です。
partytownというモジュールを使用すれば、実現できそうだったのですが、GTM経由でGAなどを実装する場合は、CORSエラーになるみたいです。(対象のissueはこちら)
もしかするとリバースプロキシを導入すれば、解消されるかもしれないのですが、GTM以外のサードパーティも上手く動作しなかったので、モジュールのアップデートを待つという結論になりました。
リソースの事前読み込み
この施策も実現が難しく、現状はまだpendingになっていますが、どこまで調査したかも含めて記述しておきます。
やりたいこととしては、「初期描画時に必要な画像などのリソースをhtmlのレスポンス前に読み込みを開始する」です。
NuxtConfigのオプションのEarly Hintsで実現できそうだったのですが、実験的機能ということもあり、動作が安定しなかったため、機能のアップデートを待つという結論になりました。(対象のissueはこちら)
まとめ
最後まで読んでいただき、ありがとうございました!
限られた工数では、効果の高い施策から優先的に取り組むことが重要です。
ユーザビリティ向上のため、ぜひハイパフォーマンスなサイト開発にお役立てください。