You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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: '&copy; <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: '&copy; <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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 11:32:34