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

Gatsby中如何为指定页面按依赖顺序加载多个script脚本

Gatsby指定路由下按顺序加载存在依赖关系的脚本方案

你的实现失效核心是两点:

  • 所用的自定义useScript钩子默认给动态创建的脚本设置了async=true,异步脚本本身不保证执行顺序
  • react-helmet注入脚本时默认采用异步加载逻辑,哪怕你通过状态判断控制脚本标签渲染时机,也无法保证插入DOM后的执行顺序,才会出现第二个脚本提前执行的报错。

下面给出两种可直接落地的实现方案,都只会在/apply路由加载对应脚本,不会全局引入。

方案1:使用Gatsby内置Script组件(推荐,适配Gatsby 4.15及以上版本)

Gatsby官方提供的Script组件原生支持脚本依赖顺序控制,不需要手写状态判断、不需要额外处理加载时序,是最稳定的方案。

  1. 4.15以下版本先安装对应插件,高版本可跳过此步:
npm install gatsby-plugin-script
  1. 在项目根目录的gatsby-config.js中添加插件配置:
module.exports = {
  plugins: [
    // 其他已有插件配置
    `gatsby-plugin-script`
  ]
}
  1. 在/apply路由对应的页面文件中直接引入使用,通过dependencies参数绑定依赖关系:
import { Script } from "gatsby"

const Apply = () => {
  return (
    <>
      {/* 第一个前置脚本 */}
      <Script 
        src="https://myscripturl/script1.js" 
        strategy="beforeInteractive" 
        id="dep-script-1" 
      />
      {/* 第二个脚本绑定前置脚本id,保证前置脚本加载执行完成后才会加载执行 */}
      <Script 
        src="https://myscripturl/script2.js" 
        strategy="afterInteractive"
        id="dep-script-2"
        dependencies={["dep-script-1"]}
      />
      {/* 页面其余业务内容 */}
    </>
  )
}

export default Apply

这个方案的优势:

  • 脚本仅在当前路由加载,不会在其他页面引入冗余资源
  • 框架底层自动处理加载时序,不需要手动维护加载状态
  • 支持加载成功、失败回调,方便做异常兜底
  • 不会出现react-helmet注入脚本的异步时序问题

方案2:自定义Hook串行加载(适配低版本Gatsby)

如果你的项目Gatsby版本较低无法使用内置Script组件,可以放弃用react-helmet注入依赖脚本,全部通过动态创建script标签的方式串行加载,从根源避免异步注入的时序问题。

  1. 改造原有useScript钩子,支持控制脚本加载属性,修复默认async导致的顺序问题:
import { useState, useEffect } from 'react'

function useScript(src, config = { async: false, defer: false }) {
  // 维护脚本加载状态:idle/loading/ready/error
  const [status, setStatus] = useState(src ? "loading" : "idle");

  useEffect(() => {
    if (!src) {
      setStatus("idle");
      return;
    }

    // 避免重复加载同地址脚本
    const existScript = document.querySelector(`script[src="${src}"]`);
    if (existScript) {
      setStatus(existScript.getAttribute("data-status") || "ready");
      return;
    }

    // 动态创建脚本标签
    const script = document.createElement("script");
    script.src = src;
    script.async = config.async;
    script.defer = config.defer;
    script.setAttribute("data-status", "loading");
    document.body.appendChild(script);

    // 加载状态回调
    const onLoad = () => {
      script.setAttribute("data-status", "ready");
      setStatus("ready");
    };
    const onError = () => {
      script.setAttribute("data-status", "error");
      setStatus("error");
    };

    script.addEventListener("load", onLoad);
    script.addEventListener("error", onError);

    // 组件卸载时移除事件监听
    return () => {
      script.removeEventListener("load", onLoad);
      script.removeEventListener("error", onError);
    };
  }, [src, config.async, config.defer]);

  return status;
}

export default useScript
  1. 在页面组件中串行触发两个脚本的加载,等第一个脚本加载完成后再加载第二个:
import { useState, useEffect } from 'react'
import useScript from './useScript'

const Apply = () => {
  // 先加载第一个前置脚本,关闭async模式
  const script1Status = useScript("https://myscripturl/script1.js", { async: false });
  const [canLoadScript2, setCanLoadScript2] = useState(false);

  // 监听第一个脚本加载状态,加载完成后再触发第二个脚本加载
  useEffect(() => {
    if (script1Status === "ready") setCanLoadScript2(true);
  }, [script1Status]);

  const script2Status = useScript(
    canLoadScript2 ? "https://myscripturl/script2.js" : null,
    { async: false }
  );

  // 两个脚本都加载完成前可以展示加载态,避免业务逻辑调用未加载的脚本方法报错
  if (script1Status !== "ready" || script2Status !== "ready") {
    return <div>页面加载中...</div>
  }

  return (
    <>
      {/* 页面业务内容,不需要再通过Helmet引入脚本 */}
    </>
  )
}

export default Apply

注意:存在依赖关系的脚本不要通过react-helmet注入,该组件插入脚本标签时默认采用异步加载逻辑,不会保证多个脚本的执行顺序,很容易出现依赖报错。

内容的提问来源于stack exchange,提问作者zyfygi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 15:15:19