Gatsby中Helmet条件加载脚本首次不执行问题求助
解决Gatsby中条件加载脚本首次访问未执行的问题
这个问题我之前也踩过坑,核心原因是脚本加载和组件渲染的时机不匹配,再加上Gatsby服务端渲染(SSR)和客户端hydration的特殊机制——首次加载时,Helmet插入的脚本还没执行完毕,你的组件就已经尝试访问全局变量(比如Leaflet的L)了,自然会报“未定义”的错误;而二次访问时脚本已经被浏览器缓存,加载速度极快,组件渲染时脚本已经就绪,所以能正常工作。
下面给你几个靠谱的解决方案,按推荐程度排序:
方案1:利用Helmet的onLoad事件控制组件渲染时机
这个方法最贴合你当前的代码结构,核心是等待脚本加载完成后,再渲染依赖该脚本的组件:
import { useState, useEffect } from 'react'; import { Helmet } from 'react-helmet'; import Layout from '../components/Layout'; import Seo from '../components/Seo'; const SpecialMapPage = () => { // 标记脚本是否加载完成 const [isScriptReady, setIsScriptReady] = useState(false); // 脚本加载完成后触发的回调 const handleScriptLoad = () => { setIsScriptReady(true); }; return ( <Layout> <Seo /> <Helmet> {/* 先加载Leaflet的CSS */} <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossOrigin="" /> {/* 加载JS脚本,绑定onLoad事件 */} <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js" integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" crossOrigin="" onLoad={handleScriptLoad} // Leaflet依赖全局变量,这里建议关闭async确保加载顺序 async={false} /> </Helmet> {/* 只有脚本加载完成后,才渲染地图组件 */} {isScriptReady && <LeafletMap />} </Layout> ); }; // 单独抽离地图组件,确保只在脚本就绪后渲染 const LeafletMap = () => { useEffect(() => { // 额外判断window是否存在,避免SSR阶段报错 if (typeof window !== 'undefined' && window.L) { // 初始化地图 const map = window.L.map('map-container').setView([51.505, -0.09], 13); window.L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }).addTo(map); } }, []); return <div id="map-container" style={{ height: '400px', width: '100%' }} />; }; export default SpecialMapPage;
方案2:动态创建脚本标签(脱离Helmet,更灵活)
如果不想依赖Helmet,也可以在页面组件的useEffect里动态加载脚本,完全控制加载流程:
import { useState, useEffect } from 'react'; import Layout from '../components/Layout'; import Seo from '../components/Seo'; const SpecialMapPage = () => { const [isScriptReady, setIsScriptReady] = useState(false); useEffect(() => { // 加载Leaflet CSS const cssLink = document.createElement('link'); cssLink.rel = 'stylesheet'; cssLink.href = 'https://unpkg.com/leaflet@1.6.0/dist/leaflet.css'; document.head.appendChild(cssLink); // 加载Leaflet JS const script = document.createElement('script'); script.src = 'https://unpkg.com/leaflet@1.6.0/dist/leaflet.js'; script.onload = () => { setIsScriptReady(true); }; // 关闭async保证脚本按顺序执行 script.async = false; document.body.appendChild(script); // 页面卸载时清理资源(可选) return () => { document.head.removeChild(cssLink); document.body.removeChild(script); }; }, []); return ( <Layout> <Seo /> {isScriptReady && <LeafletMap />} </Layout> ); }; // 地图组件和方案1一致 const LeafletMap = () => { useEffect(() => { if (typeof window !== 'undefined' && window.L) { const map = window.L.map('map-container').setView([51.505, -0.09], 13); window.L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }).addTo(map); } }, []); return <div id="map-container" style={{ height: '400px', width: '100%' }} />; }; export default SpecialMapPage;
关键注意点
- SSR环境判断:一定要加
typeof window !== 'undefined',因为Gatsby在服务端渲染时没有window对象,直接访问window.L会报错。 - 脚本加载顺序:Leaflet这类依赖全局变量的库,建议关闭
async,确保脚本同步加载执行,避免依赖问题。 - 组件渲染时机:必须等到脚本加载完成后再渲染依赖组件,这是解决首次加载报错的核心。
内容的提问来源于stack exchange,提问作者Fabien
相关产品推荐
相关产品推荐

