Next.js 13中Client Component引入Leaflet报window未定义求助
问题
将Leaflet导入到标记为'use client'的Foo组件所引入的文件中时,服务器仍会执行该代码并抛出ReferenceError: window is not defined错误。重试后网站能正常渲染,尝试在useEffect内使用动态导入但无效。
报错日志
ReferenceError: window is not defined at eval (webpack-internal:///(sc_client)/./node_modules/leaflet/dist/leaflet-src.esm.js:305:17) at Object.(sc_client)/./node_modules/leaflet/dist/leaflet-src.esm.js (path/to/app.next/server/app/page.js:473:1) at __webpack_require__ (path/to/app.next/server/webpack-runtime.js:33:42) at eval (webpack-internal:///(sc_client)/./src/js/index.js:5:89) at Object.(sc_client)/./src/js/index.js (path/to/app.next/server/app/page.js:1845:1) at __webpack_require__ (path/to/app.next/server/webpack-runtime.js:33:42) at eval (webpack-internal:///(sc_client)/./src/components/Foo.jsx:9:70) at Object.(sc_client)/./src/components/Foo.jsx (path/to/app.next/server/app/page.js:1834:1) # more logs ...
相关代码
app/page.js
// app/page.js import Foo from '@components/Foo'; export default function Base() { return ( <main> <Foo /> </main> ); }
components/Foo.jsx
// components/Foo.jsx 'use client'; import { useEffect } from 'react'; import { test } from '../js/index.js'; export default function Foo() { useEffect(() => { test(); }, []); return <div>foo</div>; }
js/index.js
// js/index.js // Error thrown here cause calling window import * as L from 'leaflet/dist/leaflet-src.esm.js'; const test = () => { console.log(L); }; export { test };
解决方案
问题根源:尽管Foo组件标记了'use client',但它顶层导入的../js/index.js会在服务器端渲染阶段被加载执行,而Leaflet的ESM模块在导入时就会访问window对象,导致服务器报错。
以下是几种可行的解决方法:
方法一:在函数内部动态导入Leaflet
修改js/index.js,将Leaflet的导入移到test函数内部,确保仅在客户端执行时才加载Leaflet:
// js/index.js const test = async () => { // 动态导入Leaflet,仅在客户端执行 const L = await import('leaflet/dist/leaflet-src.esm.js'); console.log(L); }; export { test };
同时更新Foo.jsx中的useEffect以支持异步调用:
// components/Foo.jsx 'use client'; import { useEffect } from 'react'; import { test } from '../js/index.js'; export default function Foo() { useEffect(() => { const runTest = async () => { await test(); }; runTest(); }, []); return <div>foo</div>; }
方法二:在组件中动态导入整个工具模块
直接在Foo.jsx的useEffect中动态导入../js/index.js,避免顶层导入导致服务器端加载:
// components/Foo.jsx 'use client'; import { useEffect } from 'react'; export default function Foo() { useEffect(() => { const loadAndRun = async () => { const { test } = await import('../js/index.js'); test(); }; loadAndRun(); }, []); return <div>foo</div>; }
此方法下,js/index.js中的Leaflet导入可以保留原样,但由于模块是在客户端动态加载的,window对象已存在,不会报错。
方法三:使用Next.js的动态导入禁用SSR
在app/page.js中使用Next.js的dynamic函数导入Foo组件,并禁用服务器端渲染:
// app/page.js import dynamic from 'next/dynamic'; // 禁用SSR,Foo组件完全在客户端渲染 const Foo = dynamic(() => import('@components/Foo'), { ssr: false }); export default function Base() { return ( <main> <Foo /> </main> ); }
这种方法会让Foo组件及其所有依赖完全跳过服务器端渲染,直接在客户端加载执行,从根源避免window未定义的问题。
内容的提问来源于stack exchange,提问作者orangesoda
相关产品推荐
相关产品推荐

