エンジニアになった元数学教師の記録

microCMSで埋め込みリンクを使おうとしたらハマった話(Next.js使用)

January 24, 2024 01:44
January 24, 2024 02:20

概要

本ブログはヘッドレスCMSとしてmicroCMSを使用し、Next.jsでフロント部分を作成しているのですが、埋め込みリンクを使おうとしたら途端に次のエラーが必ず出るようになりました。

1Unhandled Runtime Error
2Error: Hydration failed because the initial UI does not match what was rendered on the server.
3
4Warning: Expected server HTML to contain a matching <a> in <div>.
5
6See more info here: https://nextjs.org/docs/messages/react-hydration-error
1Unhandled Runtime Error
2Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

これを解決するまでにハマったポイントがあったので備忘録として残しておきます。

エラーについて

ハイドレーション

上記2つに共通しているのがHydration(hydrating)というワードで、今回のエラーは仮想DOM使用するReactやVue特有のハイドレーションエラーというものになります。

In web development, hydration or rehydration is a technique in which client-side JavaScript converts a static HTML web page, delivered either through static hosting or server-side rendering, into a dynamic web page by attaching event handlers to the HTML elements.

つまり、サーバサイドで生成されたHTMLをクライアントサイドがJavaScriptコードとともにダウンロードし、そこからJavaScriptを実行してイベントリスナー等を登録することを『ハイドレーション』と呼んでいます。(日本語訳で水分補給

ここで期待される動作としてはサーバサイドのHTMLとクライアントサイドのHTMLが一致していることなのですが(そうでないとイベントリスナーが登録できない)、これが一致していないとクライアントサイドがおかしいと判断しエラーを吐きます。これがハイドレーションエラーです。

今回のエラー

ということで今回ハイドレーションエラーを吐いているということは、サーバサイドのHTMLとクライアントサイドのHTMLとに齟齬があるということです。

また、埋め込みリンクを入れ始めてからエラーになっていたため、この部分のAPIを見てみることにしました。

すると下記のscriptタグが埋め込まれていることが分かります。

1<div class=\"iframely-embed\">
2    <div class=\"iframely-responsive\" style=\"height: 140px; padding-bottom: 0;\">
3        <a href=\"埋め込みリンク先" data-iframely-url=\"//cdn.iframe.ly/api/iframe?card=small&amp;url=~"></a>
4    </div>
5</div>
6<script async src=\"//cdn.iframe.ly/embed.js\" charset=\"utf-8\"></script>

しかし、エラーを吐いているページのソースを見ても最終行のscriptタグはどこにもないことに気が付きました。

そのため、このタグを消すためにhtml-react-parserのoptionに下記のようにhtmlタグに影響を及ぼさないReactフラグメントに変換してみると…

1const options: HTMLReactParserOptions = {
2  replace: ({ attribs, name }: any) => {
3    if (!attribs || Object.keys(attribs).length === 0) return;
4
5    // ハイドレーションエラーが生じるため、悪さをしているscriptタグを削除
6    if (name === "script" && attribs.src === "//cdn.iframe.ly/embed.js") {
7      return <></>;
8    }
9
10  },
11};
Before → After

なんとエラーが消えているではありませんか

ただ、その代わりに埋め込みリンクも消えてしまったため、layout.tsx (もしくはlayout.jsx)のbodyタグに下記を追記します。

layout.tsx
1<Script src="//cdn.iframe.ly/embed.js" />

すると晴れてエラーもない想定通りの挙動をしてくれます。やったね!