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
関連記事

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

既存ディレクトリをgit管理する方法
github すでに開発を進めているローカルの環境(ディレクトリ)をgithub ...

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

Unable to emit headers. 見過ごすことも可能・・・だがしかし
php Unable to emit headers. PHP開発をやってるとW ...

Safariのajaxがfailに流れるという悲しい話
Safari タイトル通りなのですが、この原因に気づかずにお客さんを困らせてしま ...
ディスカッション
コメント一覧
まだ、コメントがありません