notebook

都内でWEB系エンジニアやってます。

GatsbyのwrapPageElementをssrとbrowser両方で設定し、Hydrationエラーに対応する

Gatsbyで作っているブログで今までずっとDevTools上に下記のようなエラーが出ていた

内容的にはhydrationエラーだよというもの

react-dom.production.min.js:131 Uncaught Error: Minified React error #418; visit https://reactjs.org/docs/error-decoder.html?invariant=418 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
    at sa (react-dom.production.min.js:131:159)
    at xi (react-dom.production.min.js:293:379)
    at bs (react-dom.production.min.js:280:389)
    at vs (react-dom.production.min.js:280:320)
    at gs (react-dom.production.min.js:280:180)
    at ls (react-dom.production.min.js:268:209)
    at S (scheduler.production.min.js:13:203)
    at MessagePort.T (scheduler.production.min.js:14:128)
Uncaught Error: Minified React error #423; visit https://reactjs.org/docs/error-decoder.html?invariant=423 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
    at xi (react-dom.production.min.js:293:39)
    at bs (react-dom.production.min.js:280:389)
    at vs (react-dom.production.min.js:280:320)
    at gs (react-dom.production.min.js:280:180)
    at as (react-dom.production.min.js:271:88)
    at ls (react-dom.production.min.js:268:429)
    at S (scheduler.production.min.js:13:203)
    at MessagePort.T (scheduler.production.min.js:14:128)

結構長いこと放置していたが、ふとGatsbyのドキュメントを読んでいたら次のページを見つけた

Gatsby Browser APIs | Gatsby

www.gatsbyjs.com

一部抜粋すると

注意: wrapPageElementとwrapRootElementのAPIは、ブラウザとサーバーサイドレンダリング(SSR)の両方のAPIに存在します。一般的には、gatsby-ssr.jsとgatsby-browser.jsの両方に同じコンポーネントを実装して、SSRで生成されたページがブラウザでハイドレートされた後も同じであるようにする必要があります。

しれっと両方にwrapPageElement,wrapRootElementを使う場合はbrowser,ssr両方に実装しようと書いてある

Hydrationエラーはそういうものだよねっていうのは調べたので知ってはいたが…

たしかに同じコンポーネント実装すれば同じになるよねという感じ

型の対応

TypeScriptで書いているので型定義もしてあげないといけないが最初うまく行かなかった

同じ実装ということで共通のコンポーネントを書いてそれをgatsby-browser.ts,gatsby-ssr.tsから読めるようにする

  • gatsby-browser.tsx
export { wrapPageElement } from "./src/wrap-page-element"
  • gatsby-ssr.tsx
export { wrapPageElement } from "./src/wrap-page-element"

このような感じ

wrapPageElementに用意されている型は

  • browser

    • GatsbyBrowser["wrapPageElement"]
  • ssr

    • GatsbySSR["wrapPageElement"]

のようだったが、次のようにすると引数がanyになってしまい困る

import type { GatsbyBrowser, GatsbySSR, WrapPageElementNodeArgs } from "gatsby"

type WrapPageElement = GatsbyBrowser["wrapPageElement"] | GatsbySSR["wrapPageElement"]

export const wrapPageElement: WrapPageElement = ({ element, props }) => {
  return <Layout {...props}>{element}</Layout>
}

いくつかGitHubを探してみたら受け取る引数の型定義をWrapPageElementNodeArgsにしているコードを見つけた

たしかにBrowser、Serverともに結局引数はこの型になるんだし型的には問題ないはず

ということで、現在のところ下記のような形に落ち着いた

  • src/wrap-page-element.tsx
// custom typefaces
import "typeface-montserrat"
import "typeface-merriweather"
import "prismjs/themes/prism-coy.css"
import "prismjs/plugins/line-numbers/prism-line-numbers.css"
import "./styles.scss"

import { default as React } from "react"

import Layout from "./components/layout"

import type { GatsbyBrowser, GatsbySSR, WrapPageElementNodeArgs } from "gatsby"

type WrapPageElement = GatsbyBrowser["wrapPageElement"] | GatsbySSR["wrapPageElement"]

export const wrapPageElement: WrapPageElement = ({ element, props }: WrapPageElementNodeArgs) => {
  return <Layout {...props}>{element}</Layout>
}

これでHydrationエラーは消えた

最初から一通りドキュメント読んだほうがよいやつ…

参考PR