Node.js 25 + Next.js 15 で発生する localStorage.getItem is not a function エラーの原因と対処法

はじめに

Next.js 15 のプロジェクトで npm run dev を実行したところ、以下のエラーが発生して開発サーバーが正常に動作しなくなりました。

}[(Tn[dyoTip[dygeTepeEy:esrp2Etre4r:oE0rrr5o':r)r2o:8lrW9o:al2cro7alnc0loia3Scnl8tagS7ol:t9rSo'at`rgo-aer-g.aleggo.eecgt.aeIglttesIetttmIoetrmieasmgiesni-osfntionltaoet`afuawfnaucfsntucinptocrintoo]ivnoi]nd]e{d{wpiatgheo:ut/ajav'al}idpath

コード上で localStorage を直接呼び出している箇所はなく、原因の特定に時間がかかりました。本記事では、このエラーの根本原因と対処法を解説します。

環境

  • Node.js : v25.2.1
  • Next.js : 15.3.8
  • next-intl : 4.3.5
  • OS : macOS (Darwin 25.2.0)

エラーの根本原因

このエラーは Node.js 25 で導入された Web Storage API と Next.js 15 の組み合わせで発生します。

Node.js における localStorage の変遷

Node.js は v22 から Web Storage API(localStorage / sessionStorage)の実装を実験的に進めてきました。

バージョンlocalStorage の挙動
Node.js 21以前globalThis.localStorageundefined
Node.js 22〜24--localstorage-file フラグ指定時のみ有効(実験的機能)
Node.js 25.0.0Web Storage API がデフォルトで有効化

Node.js 25 では globalThis.localStorage オブジェクトがデフォルトで存在するようになりました。ただし、--localstorage-file フラグで有効なファイルパスを指定しないと、localStorage オブジェクト自体は存在するものの、メソッド(getItem, setItem 等)が機能しないという中途半端な状態になります。

具体的には、Node.js の Issue #60303 で報告されているように、localStorage が「すべてのプロパティアクセスに undefined を返す空のプロキシオブジェクト」になってしまう問題がありました。

なぜ Next.js 15 で問題になるのか

多くのライブラリでは、サーバーサイドでの localStorage アクセスを以下のようなガードで防いでいます。

i}fc(otnyspteovfalluoeca=lSltoocraalgSeto!r=a=ge'.ugnedteIftienme(d''k)ey{');

Node.js 24 以前では globalThis.localStorageundefined なのでこのガードが正しく機能しましたが、Node.js 25 では localStorage オブジェクトが存在するためガードをすり抜けてしまいます 。そして getItem を呼び出すと、メソッドが undefined なので TypeError: localStorage.getItem is not a function が発生します。

Next.js 15 の内部コードや依存ライブラリの一部がこのパターンに該当しており、サーバーサイドレンダリング時にエラーが発生していました。

12345.....NNlgeooexdctteaI..lt:jjSesostmro12r55aguen.dSgeSlefRoticInatetledySmpt(eo)orTfaygpleeoEcrarloSrtorage!=='undefined'

対処法の選択肢

この問題には主に2つのアプローチがあります。

方法A: Node.js のバージョンを下げる

Node.js を v24 以下にダウングレードすれば、globalThis.localStorageundefined に戻るため問題は解消します。ただし、これは一時的な回避策です。

方法B: Next.js 16 にアップグレード(推奨)

Next.js 16 ではこの問題が修正されています(vercel/next-learn#1129)。本プロジェクトではこちらの方法を採用しました。

Next.js 15 → 16 アップグレード手順

1. パッケージの更新

npmesilnisntta-lclonnfeixgt-@nleaxtte@sltatneesxtt-@ienstlli@nlta/teessltinrteracc@tl@altaetsetstreact-dom@latest\

更新されたバージョン:

パッケージ更新前更新後
next15.3.816.1.6
next-intl4.3.54.8.2
react19.1.119.2.4
react-dom19.1.119.2.4
eslint-config-next15.3.316.1.6

2. middleware.tsproxy.ts のリネーム

Next.js 16 の最大の破壊的変更の一つが、Middleware から Proxy へのリネームです。

mvsrc/middleware.tssrc/proxy.ts

ファイルの中身は変更不要です。middleware.ts は非推奨として残されていますが、将来のバージョンで削除予定です。

iie}e}mmx)x;pppl;pm]soooooarrrr.crtctttratc/olhpc{duece(rretPor?oerfirn:!xaoanes_ytuugft[n.etl,ietMitxcxsin:otdgcn|dr'fal}eaipeasgiwft-|maren=firoMeademie{vdddilfdecerldowo/e'namiw.r1aie'8rc.nneote/(|sxr{mtoo-uditeniltnslg|/'cm;oindfdilge|w.a*r\e\'.;.*|ort.wasm|.*\\.onnx).*)'

3. lint スクリプトの変更

Next.js 16 では next lint コマンドが削除されました。eslint を直接呼び出すように変更します。

{}"}sc"rliipntts""::"{eslint."

4. ESLint 設定の更新

eslint-config-next@16 はネイティブの flat config をエクスポートするようになりました。@eslint/eslintrcFlatCompat ラッパーが不要になります。

更新前:

iiiccc}c]emmmooo)o;xpppnnnb;n{}{}pooosssas,,orrrtttst.ir}rtttecgu,tcDeonl"{{{__oismoe@dfdmrlprsnedfFiipeiae:efiillracntsxarlaenttt.:{tunetnaoCe/laUCam=rox[ntmRomeynt"eeLmen:fesxeTp=einrts}oa=wgdc/lPtd_s/nifafiFd=(lonrt}irli"i-tohlnar[nbiCmfeatne/mo}rUmCaxogn"oReomtc-fpfmL(me/reiarT_p,c/lgto"o_ao*e;hm@Pftr*m"eai(e"e;"stl{-,nulhewtri(ne""lniabp:"tmm-u;/pevb"eo)ilosr;tifltacfi.l/"nmsm,te"ort,dcae"."l;unsre/lx*)t*;/"t]y,pescript"),

更新後:

iic]emmo;xppn{}{}poos,,orrt..ir}rttnngu,teeenl"nnsxxoe@deelttrsnexxiCTe:efttnoysxaCTtnp:{tuoyCfe/lnpoiS[ntfengc"eiSf,rsxegciirtsrgpc/lfit/nirp=Clonotoi-tmC[nbiCof/mo"niognefgc-fsi,reilg/lgi*e;nf*mtr"e-o,ncmto""n"p:feuisb"gllo-iifnncfet/"x-m,tco/odcneoflriseg/--*wn*ee"bx,-tv/"itstycaprleissp"ct;rsi/p*t*"";],

5. next.config.ts

webpack のカスタム設定(WASM / ONNX 対応)はそのまま残します。Next.js 16 では Turbopack がデフォルトのバンドラーになりますが、webpack 設定は next build --webpack で引き続き使用可能です。本プロジェクトでは Turbopack で問題なくビルドできたため、変更不要でした。

6. 変更不要だった箇所

  • params / searchParams: 既に await を使った非同期アクセスに移行済み
  • API routes : NextRequest.nextUrl.searchParams を使用しており変更不要
  • Client components : useParams() / useSearchParams() フックは変更不要
  • i18n 設定 (routing.ts, request.ts): 変更不要

検証結果

チェック項目結果
npm run typecheck✅ パス
npm run lint✅ パス(既存の warning 1件のみ)
npm run build✅ Turbopack でビルド成功
npm run dev✅ localStorage エラー解消

まとめ

  • 根本原因 : Node.js 25 で Web Storage API がデフォルト有効化され、localStorage オブジェクトが存在するが --localstorage-file 未設定時にメソッドが機能しない状態になった。Next.js 15 の内部コードがこの「壊れた localStorage」に対応できていなかった
  • 対処法 : Next.js 16 へのアップグレードで解消。Node.js のダウングレードでも回避可能
  • アップグレード時の注意点 : middleware.tsproxy.ts のリネーム、next lint の廃止、ESLint 設定のネイティブ flat config 対応が必要

Node.js のバージョンアップに伴う Web API の追加は、サーバーサイドで動作するフレームワークに思わぬ影響を与えることがあります。特に typeof ガードに頼った実装は、新しい API の追加で壊れる可能性があることを認識しておく必要があります。

参考リンク