🐈

[workaround] Next.jsの_document.js内でcookieの値を参照する

2020/10/15に公開
2

概要と注意点

この記事はNext.js_document.js内でcookieの値を参照するためのやり方を記載したものとなります。
コードは基本的にTypeScriptで書いているので、ファイル名の表記も以降は_document.jsではなく_document.tsxと表記している場合もありますが、どちらも同じファイルを想定しています。

わざわざ_document.js内でcookieの値を参照したくなるケースというのは、それほど多くはないかもしれません。
が、例えば、cookieの値を見て、<head>タグ内でスクリプトの読み込みのON/OFFを制御したいケースなどがあります。
(例えば、Google Analyticsの有効・無効を切り替えたいとか)

なお、最初に注意点がいくつかあります。
まず、今回ここに書くやり方はworkaround的解決方法となります
それはNext.js側で提供されている機能ではない気がするからです。
(Next.jsの挙動見ていると、これは予め想定された手法なのかが判別つかないから)

そのため、このやり方を使わないで済むならそのほうが良いでしょう。
私自身もっと分かりやすい方法がないのか調べているところです。

続いて、今回実現したいことを達成するための手段としてNext.jsに備わっているgetServerSidePropsの処理を動かす必要があります。
そのためNelifyなどに静的サイトとしてホスティングしている場合、getServerSidePropsは使用できないので、このやり方は使えません。
(代替手段があるのかどうかは調べていません)

注意

先ほども書きましたが、果たしてこれがNext.jsで想定されている動きなのか個人的には確信が持てないので、 Next.jsのバージョンによって挙動が変わる可能性も一応明記しておきます。

今回のコードは version "9.5.5"で検証しています。

Next.jsの_document.js内でcookieの値を参照する

まずは実際に検証用のコードを書いたので、挙動を確認したい場合はこちらのコードをローカルに落として実行してみてください。

Next.js sample of get cookie value at document.tsx

具体的な、_document.js内でcookieの値を参照する方法は上のコードの通りで、

  • 呼び出し対象となるページ( pages/index.tsxとか) でgetServerSideProps関数を設置する。
    • 別に内部の処理は何も書かなくて良い。-> getServerSidePropsをページ描画時に一度実行したいので
  • _document.js内でctx.req.headers.cookieからcookieの値を参照する

という過程を踏むことで実現が可能です。

実際に _document.tsx 内でcookieの値を参照している箇所はこちらです。一部抜粋しています。

export default class MyDocument extends Document<Props> {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);

    let hasCookie: boolean = false;

    if (ctx.req && ctx.req.headers.cookie) {
      console.log('cookie values: ', ctx.req.headers.cookie);
      hasCookie = true;
    }

    return { ...initialProps, hasCookie };
  }

  render() {
    console.log('hasCookie: ', this.props.hasCookie);

_document.tsx には getServerSidePropsは実装されていない?ようでしたので、getInitialPropsを使って、一度cookieの値を取得するようにしています。

ただ、このコード、cookieの値が実際に入っていたとしても、必ずctx.req.headers.cookieに値が入っているとは限りません。
呼び出し先のコードである pages/index.tsx にて getServerSidePropsが動きますが、こちらをコメントアウトすると、上記の_document.tsx内ではcookieが参照できなくなります。

diff --git a/pages/index.tsx b/pages/index.tsx
index 99ea97d..23a6337 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -12,9 +12,11 @@ const IndexPage: React.FC = (): JSX.Element => (
   </div>
 );

+/*
 export async function getServerSideProps() {
   console.log('----------> Call getServerSideProps');
   return { props: {} };
 }
+*/

具体的にはctx.req配下がundefinedとなります。
どうやら getServerSideProps が動いたあとだけ、_document.tsx内でctx.req内に値が格納されるようです。

このような動きについては、Next.js内のこちらのコードが参考になるかと思います。
(自信はありません。間違えていたらすみません!)

https://github.com/vercel/next.js/blob/5cab03fef0782b4ecd7dbe4ec5bd10d10554650f/packages/next/next-server/server/render.tsx#L717-L728

getServerSidePropsが動いたあと、ctx.reqに値が入るという流れです。
そのため現状、一度サーバサイドで動かしたあとにdocument側でそれらの値(今回の場合、ctx.req)を参照する必要があるように感じました。

もっと良い方法をご存じの方がいらっしゃいましたらコメント頂けますと幸いですm(_ _)m

Discussion

catnosecatnose

呼び出し先のコードである pages/index.tsx にて getServerSidePropsが動きますが、こちらをコメントアウトすると、上記の_document.tsx内ではcookieが参照できなくなります。

知らなかったです…これはハマりポイントですね。

本筋から外れますが、以前_document.js or _app.jsgetInitialPropsを使おうとしたことがあったのですが、getStaticPropsによりページを静的ファイルとして配信したい場合もgetInitialPropsが動いてしまいレスポンスが遅くなってしまう点がネックとなり断念しました。

このあたりのNext.jsのノウハウがGitHubのIssues以外であまり見つからないので、僕もどこかで知っていることを記事にしようと思います。

Yuki ShindoYuki Shindo

getStaticPropsによりページを静的ファイルとして配信したい場合もgetInitialPropsが動いてしまいレスポンスが遅くなってしまう点がネックとなり断念しました。

これ、知りませんでした。たぶん同じ状況になったらハマっていたと思うので、知れて良かったです。
情報共有、ありがとうございます。

このあたりのNext.jsのノウハウがGitHubのIssues以外であまり見つからないので、僕もどこかで知っていることを記事にしようと思います。

楽しみにしています!