cypressで実行したときにmetaタグがmatchしないエラーでhydrationに失敗する

2023/03/19に公開

現象

localhostにアプリを立ち上げ、npx cypress open からcypressのコンソーつに入ってe2eテストを実行しようとすると、以下のwarningが出る。

Warning: Expected server HTML to contain a matching <meta> in <head>.
    at meta
    at V1Meta (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:4542:7)
    at Meta (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:4676:7)
    at head
    at html
    at App
    at RemixRoute (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:4289:3)
    at RenderedRoute (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:2653:5)
    at RenderErrorBoundary (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:3018:9)
    at Routes (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:2891:5)
    at Router (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:2838:15)
    at RouterProvider (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:2789:5)
    at RemixErrorBoundary (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:3917:5)
    at RemixBrowser (http://localhost:3000/build/_shared/chunk-WM4MSLGF.js:5305:55)

で、これが原因 (と思われる) でhydrationに失敗

react-dom.development.js:12507 Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
    at throwOnHydrationMismatch (react-dom.development.js:12507:9)
    at tryToClaimNextHydratableInstance (react-dom.development.js:12535:7)
    at updateHostComponent (react-dom.development.js:19902:5)
    at beginWork (react-dom.development.js:21618:14)
    at HTMLUnknownElement.callCallback2 (react-dom.development.js:4164:14)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:16)
    at invokeGuardedCallback (react-dom.development.js:4277:31)
    at beginWork$1 (react-dom.development.js:27451:7)
    at performUnitOfWork (react-dom.development.js:26560:12)
    at workLoopConcurrent (react-dom.development.js:26543:5)

ついでにこんなwarning/errorも。

Warning: An error occurred during hydration. The server HTML was replaced with client content in <#document>.
printWarning @ react-dom.development.js:86
error @ react-dom.development.js:60
errorHydratingContainer @ react-dom.development.js:11473
recoverFromConcurrentError @ react-dom.development.js:25846
performConcurrentWorkOnRoot @ react-dom.development.js:25750
workLoop @ scheduler.development.js:266
flushWork @ scheduler.development.js:239
performWorkUntilDeadline @ scheduler.development.js:533

Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
    at updateHostRoot (react-dom.development.js:19849:57)
    at beginWork (react-dom.development.js:21615:14)
    at beginWork$1 (react-dom.development.js:27426:14)
    at performUnitOfWork (react-dom.development.js:26560:12)
    at workLoopSync (react-dom.development.js:26466:5)
    at renderRootSync (react-dom.development.js:26434:7)
    at recoverFromConcurrentError (react-dom.development.js:25850:20)
    at performConcurrentWorkOnRoot (react-dom.development.js:25750:22)
    at workLoop (scheduler.development.js:266:34)
    at flushWork (scheduler.development.js:239:14)

cypressが自動で開くchromeウィンドウでだけエラーになり、cypress中に別ブラウザでアプリアクセスしても上記のエラーは出ない。

原因

cypressはブラウザのsame-origin policyを緩めてアプリを制御するために<script type='text/javascript'> document.domain = 'localhost'; </script>をheadに設定するらしい。

https://docs.cypress.io/guides/guides/web-security

https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#changing_origin

このscriptがサーバーhtmlにはなく、クライアントhtmlだけにあるためhydrationエラーになっているようだ。

原因調査メモ

エラーメッセージではmetaがサーバーとクライアントで異なるというが、serverとclientのhtmlを見るとscriptがあるかどうかの違い。

server:

<head>
    <script type='text/javascript'> document.domain = 'localhost'; </script>
    <meta charSet="utf-8"/>
    <title>Sign Up</title>
    <meta name="viewport" content="width=device-width,initial-scale=1"/>
    <link rel="stylesheet" href="/build/_assets/tailwind-DKMRCRP6.css"/>
</head>

client:

<head>
    <meta charset="utf-8">
    <title>Sign Up</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="stylesheet" href="/build/_assets/tailwind-DKMRCRP6.css">
</head>

別ブラウザでアクセスした時 (エラー出ない) にサーバーから送られるhtmlはscriptがない。

server in brave

<head>
    <meta charSet="utf-8"/>
    <title>Sign Up</title>
    <meta name="viewport" content="width=device-width,initial-scale=1"/>
    <link rel="stylesheet" href="/build/_assets/tailwind-DKMRCRP6.css"/>
</head>

以上からcypressが<script>を差し込んでいるのではないか?と思い、調べたら上記原因にたどり着いた。

対処法

今の所根本的な対処法はわからない。画面描画には影響なく、本番環境ではエラーも発生しない。

remix公式の提供するテンプレート (stack) では握りつぶしている。

https://github.com/remix-run/grunge-stack/blob/c64891d437a6d82a9c17ffbd3b70efe22c931fc1/cypress/support/e2e.ts#L5

Discussion