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 に記述してアプリケーション全体に適用します。htmlbody など使用箇所は限定的なものになると考えられます。

コンポーネントに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のコンテンツ(コンポーネント)をスイッチする呼び出し側の想定です。sppc に渡したコンテンツが切り替えて表示されて欲しいです。

簡易実装 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 を返すため、そのときの結果がブラウザ表示時の初期表示となります。

初期表示をコントロールしたい状況は以下が考えられます。

  1. PC/SP のどちらかの内容を初期表示に選びたい
  2. ちらつきを防ぐために、初期表示は 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 を返すため、 displaySPdisplayPC どちらも 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 の設計・実装を得意とするフロントエンドエンジニア募集要項

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!


投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。