Next.jsのSSRにおいてコンポーネントローディング中にスケルトンを表示する
公開日:
スケルトンとは
Next.js
で SSR
を利用してコンポーネントを表示する場合は、dynamic インポート時にssr: falseのオプションをつけることで、ssr せずにクライアントでコンポーネントをレンダリングするように設定することができます。
https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr
僕がインターン先で開発しているGROWIという OSS の wiki サービスでは現在 Next.js
の導入が進んでおり、
- wiki 本文のようなすぐに表示されてほしいコンポーネントはサーバーサイドでレンダリング
- その他のすぐに表示される必要のないコンポーネントはクライアントサイドでレンダリング
このような方針で Next.js
の導入が進められています。
上の gif 画像が SSR されるコンポーネントの例です。家のアイコンがあるページのパス部分はすぐに表示されてほしいのでサーバーサイドでレンダリングされていますが、その他のボタンなどは後からクライアントサイドでレンダリングが走っています。
このようにレンダリングを出し分けると、表示タイミングの差によって画面にカクつきが出てしまいます。これを防ぐのがスケルトンです。
実装したいもの
上の gif 画像のようにクライアントサイドでのレンダリングが走るまでの時間、本物のコンポーネントと同じ高さと幅を持ったスケルトンコンポーネントを表示します。 こうすることでコンポーネントがカクっと表示されるのを防ぎ、レンダリング待機中の UX 向上を目指します。
実装方法
スケルトン
import React from 'react';
type SkeltonProps = {
additionalClass?: string;
roundedPill?: boolean;
};
export const Skelton = (props: SkeltonProps): JSX.Element => {
const { additionalClass, roundedPill } = props;
return (
<div className={`${additionalClass}`}>
<div
className={`grw-skelton h-100 w-100 ${
roundedPill ? 'rounded-pill' : ''
}`}
></div>
</div>
);
};
このようなスケルトン用の React コンポーネントを作成します。
- 高さと幅は css で指定するために、クラスを外から指定できるようにする
- round-pill を props に持つことで角丸の有無を利用側が指定できる
- 本物のコンポーネントよりも細いスケルトンを実装したいことを考慮して、div タグの 2 層構造にする
上の三つのことを意識してコンポーネントを作成しました。詳細については後術しますが、grw-skelton
というクラスはスケルトンの背景色を指定するためだけのクラスなので特に気にしなくても大丈夫です。
スケルトンのスタイル
スケルトンのスタイルは出来るだけ、高さと幅を数値でハードコーディングするのはやめて、本物のコンポーネントと同じ高さを利用するようにしました。
@use '~/styles/bootstrap/init' as bs;
$grw-tag-label-font-size: 12px;
.grw-tag-labels :global {
.grw-tag-label {
font-size: $grw-tag-label-font-size;
font-weight: normal;
border-radius: bs.$border-radius;
}
}
.grw-tag-labels-skelton :global {
width: 137px;
height: calc(#{$grw-tag-label-font-size} + #{bs.$badge-padding-y} * 2);
font-size: $grw-tag-label-font-size; // set font-size to use the same em value in bs.$badge-padding-y(https://getbootstrap.jp/docs/5.0/components/badge/#variables)
}
上はスタイルの実装の一例です。上のgrw-tag-labels
というのが本物のコンポーネントが持つクラスで、下のgrw-tag-labels-skelton
というのがスケルトンコンポーネントが持つクラスです。
- 高さのハードコーディングはしない
- 本物のコンポーネントのスタイルと同一ファイルにまとめる
この 2 点を意識することで、本物のコンポーネントのスタイルが変更された時に、スケルトンのコンポーネントのスタイルも変更しやすいようになっています。また、UI がカクつく原因になるのはコンポーネントの高さなので、幅に関してはよしなにスタイルを当てています。
ローディング中にスケルトンを表示する
いよいよ、スケルトンコンポーネントをローディング中に表示する部分を実装していきます。Next.js
が用意している、dynamic インポートのloadingオプションを利用します。
https://nextjs.org/docs/advanced-features/dynamic-import#example
const TagLabels = dynamic(() => import('../Page/TagLabels'), {
ssr: false, // ssrしない
loading: () => (
<Skelton
additionalClass={`${TagLabelsStyles['grw-tag-labels-skelton']} py-1`}
/>
), // クライアントサイドでレンダリング中に表示するコンポーネント
});
上のように loading オプションにスケルトンコンポーネントを渡すことで、レンダリング中のスケルトン機能を実装します。この例では、grw-tag-labels-skelton
クラスによって高さと幅は決まっています。また、デザイン的に少し細めのスケルトンにしたかったのでpy-1
クラスを付与することで bootstrap
で上下に padding
を追加しています。
この画像のようなイメージです。このようなことを実現するために、先ほど書いたように、スケルトンコンポーネントを div
タグの 2 重構造にしています。
最後に
本物のコンポーネントと高さを揃えたりと実装に割と苦戦した節はありますが、やはりスケルトンがあるとないでは UX が違うような気がします。割と応用が効くコンポーネントだと思うので、よかったら活用してみてください。
では、Bye