🌊

【Next.js × dnd-kit】Hydration Error の対処法

に公開

事象

https://qiita.com/mkthrkw/items/447b88f87ae92dc1ad08
こちらの記事のサンプルをコピペで動かしてみたところ、
Hydration Error が発生

エラー全文
Error: A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:

- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.

https://react.dev/link/hydration-mismatch

  ...
    <RedirectBoundary>
      <RedirectErrorBoundary router={{...}}>
        <InnerLayoutRouter url="/test1" tree={[...]} cacheNode={{lazyData:null, ...}} segmentPath={[...]}>
          <ClientPageRoot Component={function Page} searchParams={{}} params={{}}>
            <Page params={Promise} searchParams={Promise}>
              <DndContext onDragEnd={function handleDragEnd}>
                <div className="flex flex-...">
                  <div className="flex h-20">
                    <Draggable id="draggable">
                      <button
                        ref={function useNodeRef.useCallback[setNodeRef]}
                        style={{transform:undefined}}
                        onPointerDown={function useSyntheticListeners.useMemo}
                        onKeyDown={function useSyntheticListeners.useMemo}
                        role="button"
                        tabIndex={0}
                        aria-disabled={false}
                        aria-pressed={undefined}
                        aria-roledescription="draggable"
+                       aria-describedby="DndDescribedBy-0"
-                       aria-describedby="DndDescribedBy-1"
                      >
                  ...
                ...
                ...
          ...

    at createConsoleError (http://localhost:3000/_next/static/chunks/node_modules_next_dist_445d8acf._.js:1484:71)
    at handleConsoleError (http://localhost:3000/_next/static/chunks/node_modules_next_dist_445d8acf._.js:2090:54)
    at console.error (http://localhost:3000/_next/static/chunks/node_modules_next_dist_445d8acf._.js:2243:57)
    at http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:3012:25
    at runWithFiberInDEV (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:890:74)
    at emitPendingHydrationWarnings (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:3011:13)
    at completeWork (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:6234:102)
    at runWithFiberInDEV (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:890:131)
    at completeUnitOfWork (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:8301:23)
    at performUnitOfWork (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:8238:28)
    at workLoopConcurrentByScheduler (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:8232:58)
    at renderRootConcurrent (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:8214:71)
    at performWorkOnRoot (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:7846:176)
    at performWorkOnRootViaSchedulerTask (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc06._.js:8820:9)
    at MessagePort.performWorkUntilDeadline (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_0f1b9fd4._.js:2588:64)

原因

useDraggable 関数の返り値である attributes に含まれているであろう aria-describedby 属性の値がサーバー側とクライアント側で違ってしまっている。
それによる Hydration Error である。

対処法

https://github.com/clauderic/dnd-kit/issues/926
同様の問題が Issue として報告されていた。
対処法としては、DndContext コンポーネントに id props を渡す。

const id = useId();
return <DndContext id={id} />;

React の useId はサーバー側とクライアント側のプリレンダリングで同じ値であることが保証される。
https://ja.react.dev/reference/react/useId

Discussion