Next.js における レスポンシブの実装
フロントエンドエンジニアの茶木です。
前回 Next.js におけるレスポンシブ、画面幅でのコンテンツの出し分けについてSSRの観点も含めて考察しました。
今回は、考察結果を踏まえて実装を考えます。
おさらい
前回の考察のおさらいです。
- スタイルの変更は CSS Media Query が良い
- 要素自体の描画切り替えは MUI useMediaQuery が良い
以上が、レスポンシブの切り替えの方法のおすすめでした。
CSS Media query
React では、DOMツリーの構造の変化をもとに再レンダリングが行われます。再レンダリングは高コストなのでDOMツリーの構造変化を伴わない、スタイルの変更は CSS Media Query による切り替えが最適と考えます。
グローバルCSSを使う方法
@media screen and (min-width: 1024px) {
/* PC */
body { padding: 20px; }
}
@media screen and (max-width: 1023px) {
/* SP */
body { padding: 10px; }
}
グローバルCSS に記述してアプリケーション全体に適用します。html
や body
など使用箇所は限定的なものになると考えられます。
コンポーネントにCSSを使う方法
import { styled } from "@mui/material/styles";
const ShowCase = styled("ul")({
display: 'flex',
flexDirection: "column",
'@media(minWidth: 1024px)' : {
/* PC */
flexDirection: "row",
maxWidth: "100%",
}
});
上記の例では、PCでは横並びのリスト、SPでは縦積みのリストを作る、 ul
を想定しています。MUI の styled
を使っていますが media query が記述できれば同じように実装できます。
また、前項でグローバルCSS が限定的になると言ったのは、個別の要素に関しては、このようにコンポーネントに閉じ込めた CSS を使うほうが影響範囲を限定できるからです。
import { styled } from "@mui/material/styles";
export const BreakSP = styled("br")({
'@media(minWidth: 1024px)' : { display: "none" }
});
export const BreakPC = styled("br")({
'@media(maxWidth: 1023px)' : { display: "none" }
});
同じくコンポーネントに CSS を当てる方法でもうひとつ、display: "none"
で非表示にする方法です。これは改行位置をPC/SPで切り替える例です。CSS で隠しても、HTML 上には残るので SEOの観点から PC/SP で二重に記載が残っても問題ないものにだけ使うのが良いでしょう。
MUI useMediaQuery
を使う場合
ここでは、MUI の提供する useMediaQuery
フックを使った例を示します。MUIを使わない環境でもスクラッチで同様のフックが作れます。それについては前回の記事が参考になると思います。
呼び出し
return (
<Switcher
sp={<SimpleCalendarSelect />}
pc={<CalendarSelect />}
/>
)
上記は、PC/SPのコンテンツ(コンポーネント)をスイッチする呼び出し側の想定です。sp
と pc
に渡したコンテンツが切り替えて表示されて欲しいです。
簡易実装 Ver.
interface Props {
sp: React.ReactElement;
pc: React.ReactElement;
}
function Switcher({ pc, sp, initial = "sp" }: Props): React.ReactElement {
const displaySP = useMediaQuery("(max-width: 1023px)");
if (displaySP) return sp;
return pc;
}
実装部はこのようになります。 useMediaQuery
で PC/SP の出し分けができます。
しかし、簡易実装 Ver. には考慮していない点があります。それは初期表示のコントロールです。useMediaQuery
は SSR時に false
を返すため、そのときの結果がブラウザ表示時の初期表示となります。
初期表示をコントロールしたい状況は以下が考えられます。
- PC/SP のどちらかの内容を初期表示に選びたい
- ちらつきを防ぐために、初期表示は PC/SP のどちらも表示したくない
1は、SEO 観点による理由です。2は、まず、SSR時のコンテンツが描画され、そのあと実際のブレイクポイントのコンテンツが表示されるためちらつく問題です。簡易実装 Ver. では、SPで表示すると 一瞬 PC版が表示されてから、SP版の表示に切り替わります。
これらは SSRでレンダリングされるコンテンツを指定できないことが原因です。
次に、初期表示のコントロールを考慮した実装を示します。
初期表示コントロール実装 Ver.
interface Props {
sp: React.ReactElement;
pc: React.ReactElement;
initial?: "sp" | "pc" | "none";
}
function Switcher({ pc, sp, initial = "sp" }: Props): React.ReactElement {
const displaySP = useMediaQuery("(max-width: 1023px)");
const displayPC = useMediaQuery("(min-width: 1024px)");
if (displaySP) return sp;
if (displayPC) return pc;
if (initial === "sp") return sp;
if (initial === "pc") return pc;
return <></>;
}
initial
で指定した値のものが、初期表示になるようにしました。"none"
を指定すれば、初期状態ではPC/SPのどちらもレンダーされません。
ロジックを簡単に解説すると、 useMediaQuery
は、window.innerWidth
が取得できない状態では false
を返すため、 displaySP
と displayPC
どちらも false
のとき初期表示と判定できます。このときは、 initial
の値によって、コンポーネントを出し分けます。
まとめ
可能な限り、CSS Media Query だけで完結させるのが良いです。コンテンツの内容がスタイルの切り替えだけで完結しない場合に useMediaQuery
などでの切り替えを考えてください。その際には、初期表示状態が SEOとちらつきに影響する点を加味すると良いです。
改良点
今回挙げた Switcher
は PC/SPのみのものでしたが、実際には、タブレットなど複数のブレイクポイントに対応したいユースケースがあると思います。そういった汎用的な Switcher を作る場合は、useMediaQuery
より、window.matchMedia
を使ったほうが作成しやすそうな印象を持ちました。 useMediaQuery
が Reactフックのため、可変数のブレイクポイントをループで回して必要回数呼び出すということができないためです。
Gaji-Laboでは、 Next.js 経験が豊富なフロントエンドエンジニアを募集しています
弊社では Next.js の知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違う Gaji-Labo を味わいに来ませんか?
Next.js の設計・実装を得意とするフロントエンドエンジニア募集要項
もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!