この記事は、ニフティグループ Advent Calendar 2022 8日目の記事です。
はじめに
こんにちは!会員システムグループの渡邊です。普段はニフティトップページの開発運用を担当しています。
今回はニフティトップページでも採用しているNext.jsを使ったサイトの表示速度を改善する方法について紹介します。
実行環境
- MacBook Pro(M1、2021)
- macOS v12.6.1
- Next.js v13.0.6
next/image
画像ファイルはウェブページ全体のバイト数の半分を占めると言われるほど、ページを読み込むときに負荷がかかる部分です。
こういった問題を解決するために画像サイズを縮小させるTinyPNGなどのサイトを使ったり、軽量な画像フォーマットに変換するという作業を行うことがあると思います。
Next.jsでは、next/imageと呼ばれるコンポーネントを呼び出すことで、軽量な画像フォーマットに自動で変換する仕組みを備えています。
使い方は単純で以下のようにコンポーネントを呼び出し、必要なプロパティを渡してあげるだけです。
以下の例では、1920×1080の画像を読み込み500×250リサイズしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import Image from 'next/image' import styles from '../styles/Home.module.css' export default function Home() { return ( <div className={styles.container}> <h1>画像テスト</h1> <div>next/image</div> <Image src="/tree.jpg" alt="tree" width={500} height={250} /> <div>imgタグ</div> <img src="/tree.jpg" alt="tree" width={500} height={250} /> </div> ) } |
仕組みとしてはImageコンポーネントで読み込んだ画像をGoogleが開発している次世代画像フォーマットのwebpに変換します。
テスト用にnext/imageとimgタグを使って2枚の画像を表示してみると以下のようになります。特に劣化は感じることはなく、サイズを見ると1/10以下に削減できています。
next/dynamic
次にES2020から追加された新機能の動的インポートをNext.jsでも行えるようにする機能です。
トグルやモーダルなどの初回ロード時では不要な処理をユーザーが操作したときに都度読み込みを行うようにするものです。
一部の処理を動的インポートにすることで初回ロード時に読み込む量を減らすことができます。
静的インポートと動的インポートの書き方の違いは以下のようになります。
1 2 3 4 5 6 7 |
// static import import List from './list' import List2 from './list2' // dynamic import import dynamic from 'next/dynamic'; const List = dynamic(() => import('./list')) const List2 = dynamic(() => import('./list2')) |
next/script
外部スクリプト読み込みは、パフォーマンスに大きな影響を与える原因の一つです。
外部スクリプトはこちらから手を入れることができず、そのまま読み込むしかないため、パフォーマンス改善の中では一番苦しめられる部分だと個人的には思います。
こういった問題を改善するために登場したのがnext/scriptです。
next/scriptは、外部スクリプトの実行順序を制御することができ、strategyというプロパティを使用することで自動的に優先順位をつけることができます。
書き方は以下のようにScriptコンポーネントにsrcとstrategyプロパティを渡してあげるだけです。
1 |
<Script src="<https://example.com/script.js>" strategy="beforeInteractive" /> |
strategyには3つのプロパティが用意されています。
- beforeInteractive
- ページが表示される前に読み込みが開始する
- バンドルされたJavaScriptを読み込むより前に実行することができる
- 最優先で読み込まれるので、実行速度が求められる広告スクリプトなどで有用
- afterInteractive
- プロパティを何も指定していないと選ばれるもの
- ページが表示された直後に読み込みが開始される
- タグマネージャーやGAなどのスクリプトに有用
- lazyOnload
- ページが表示されてアイドル状態になった後に読み込みが開始される
- すぐに読み込む必要がなく、ページが完全に表示されてから使われるようなチャットボットやソーシャルメディアウィジェットなどで有用
以上のことから beforeInteractive > afterInteractive > lazyOnloadの順番でスクリプトが読み込まれるということになります。
バンドルサイズを確認する
最後にバンドルされたファイルに含まれる各パッケージの容量を可視化するツールを使って、アプリケーションのどこの読み込みに時間がかかっているか可視化してみます。
webpack-bundle-analyzerをインストールします。
1 |
yarn add -D @next/bundle-analyzer |
next.config.jsに以下の設定を追加します。
1 2 3 4 5 |
const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', }); module.exports = withBundleAnalyzer({}); |
最後にコマンドを実行します。
1 |
ANALYZE=true yarn build |
今回はライブラリによる読み込みサイズを見るために日付ライブラリを複数導入し、同じ処理を実行してみました。
日付ライブラリでメジャーなday.js、date-fns、luxonを入れて検証しました。
3つ比較してみましたが、luxonが圧倒的に重い処理をしていることがひと目で分かります。同じような処理をしているライブラリでもここまでの差が生まれるので、慎重に選ばないといけないことがわかります。
加えてコンポーネント単位での確認もできるので、どの機能に原因があるかの特定が容易にできるのも良い点です。
まとめ
以上がニフティトップで採用しているパフォーマンス改善の一部になります。どれもNext.jsを採用しているサイトならすぐに実施できることなので、積極的に活用することをおすすめします。
パフォーマンス改善において大事なのは可視化できることなので、原因を自動で特定し、数値化するLightHouse ciの導入もおすすめです。
明日は、nakanowaiさんの「AWS re:Invent 2022で発表されたこと」のまとめ記事です。 お楽しみに!