Next.jsでbodyタグの属性切り替えはどうするの?
最近なにかとお触りしてるNext.js。
Reactを扱いやすく、特にルーティング周りが簡易的にできるフレームワークとあって採用。
SEO対策は不要なので完全にSSG(Server Side Generation)に振り切ってる。
ただ、厳密にはサーバー側に必要な情報はフェッチする必要があるのでSSG + CSR(Client Site Rendering)となってる。
SSG(Static Site Generation)
ビルド時に事前にHTMLを生成してしておく。静的ページなのでCDNのキャッシュが効くのでパフォーマンス(画面描画速度)が良い、早い。
動的コンテンツの生成に向かないため、SEO的には不利。
getStaticPropsというファンクションで外部データからデータを取り込んだ上でビルドし、静的データに置換しておける。すごい。
SSR(Server Side Rendering)
リクエストの都度HTMLを生成し、それをフロントに返す。データもバインドして描画するので、動的コンテンツもへっちゃら。
SEO対策が必要な場合はSSRが必須といえる。
CSR(Client Side Rendering)
クライアント側で必要なタイミングでDOMをレンダリングする仕組み。SSGだけど外部からデータをフェッチしたいときに使う。
実際はuseEffect内でaxiosなどをつかってデータを取得しStateでバインドしてレンダリングする。
Next.js(Vercel社)はuseSWRライブラリを推奨している。
※useSWRはうまく非同期でデータを取得するエラーハンドリングやキャッシュの機能をラップ(内包)してはくれているが
Next.jsではhtmlやbodyタグなど、どの画面でも共通かつ不変要素は基本的に_document.tsxに書くのが望ましいのです。(拡張子がtsxなのはtypescriptで書かれてるからです。本来はjsx)
_document.tsxは共通タグの定義(揺るぎないタグの生成)
_app.tsxは画面共通で走らせる処理の定義(セッション状態の監視など、揺るぎない共通処理)
と理解しておけば棲み分けには困らない。
_document.tsx内では属性切り替えはできない
当然といえば当然なのですが、先述の通り_document.tsxは共通タグを定義しておくため、動的に変わる属性というのは定義できません。
例えば以下の感じ。
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<body data-page='hoge'>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
ここでいうdata-page=’hoge’は全画面共通であるため、例えばStyleClassでdata-pageの属性によってスタイルを切り分けたりしてると詰みます。
なので各pageで<body>要素から書いたり色々とやってたりしたのですが、そうすると
<body data-page="hoge">
<div id="__next">
<body>
という感じでbodyタグが2つできたりするわけです。
※これは_document.tsxからbodyタグを削除しても同じ。その場合、各pageで書かれたbodyが<div id="__next">の上位にも生成される。(結局bodyが2つになる)
どうでも良いし、もしかすると理解が違ってるかもしれませんが、どうやらNext.jsのルーティング(画面切り替え)は<div id="__next">配下の要素をリレンダリングすることで実現してるっぽいです。
解決策
じゃあ結局どうやるのさって話です。
結論を言うと
・_document.tsxのbodyタグにid属性をつける
・_app.tsxのuseEffectでbodyタグの要素を付け替える
ということをやります。
以下のようにします。
_document.tsx
class Document extends NextDocument<Props> {
render() {
return (
<Html>
<Head></Head>
<body id='hoge_body' data-page=''>
<Main />
<NextScript />
</body>
</Html>
);
}
}
_app.tsx
import { updateBodyData } from 'src/libs/Hooks/updateBodyData';
..
const App = ({ Component, pageProps, router }: AppProps) => {
// パスを見てdata-page属性にセットする値を変えるフック処理
updateBodyData();
return <Component {...pageProps} />;
};
src/libs/Hooks/updateBodyData
import { useRouter } from 'next/router';
import { useEffect } from 'react';
/**
* 画面レンダリングの際のフック処理
*/
export function updateBodyData() {
const { pathname } = useRouter();
useEffect(() => {
// パスによってbodyのdata属性を切り替える
let element = document.getElementById('hoge_body');
element.dataset.page = fetchDataPage(pathname);
}, [pathname]);
}
export const fetchDataPage = (pathname: string): string => {
switch (pathname) {
case '/':
return 'home';
case '/auth/login':
return 'login';
default:
return '';
}
};
bodyタグを切り替えるライブラリを別に作っておくとコードの見通しが良きです。
_app.tsxで呼び出すだけ。
描画される画面のパスを見てdata属性にセットする値を切り分けています。
フック処理をuseEffect外に書いてしまうと
SSR(サーバサイドレンダリング)と認識されビルドに失敗するので注意です。
これで各画面に応じたbodyのdata属性を切り替えることができました。
めでたし、めでたし。
React,プログラミングCSR,JavaScript,next.js,React,SSG,SSR,typescript,useEffect,_app.tsx,_document.tsx
Posted by ryurin
関連記事

【即解決】CakePHP4でAPIを実装してCORSが解消しないあなたへ【疑うべきはプリフライト】
プログラミング Webシステムを、フロントサイドとサーバーサイドを分けて構築した ...

【セッション管理】Next.js + CakePHP4【基本編】
SPAとAPIが別々のサーバーで稼働している場合、セッション管理をどのように行う ...

【環境構築】PythonとNext.jsで分断型Webサイトの下準備
ChatGPT、激アツですね。ここ最近はAIに頼りっきりな私です。情報の鮮度は落 ...

【超初心者向け】Reactの概要をおさえる【3分解説】
誰得かわかりませんが、Reactを触り始めた者として現状の理解を簡単にまとめます ...

既存ディレクトリをgit管理する方法
github すでに開発を進めているローカルの環境(ディレクトリ)をgithub ...
ディスカッション
コメント一覧
まだ、コメントがありません