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

Next.js中通过JavaScript动态加载外部主题脚本不执行问题求助

问题根因

现有实现存在3个核心缺陷导致脚本加载后不执行:

  1. useScript 钩子执行时机错误,直接在渲染同步阶段操作DOM,脱离React生命周期管控,无脚本加载状态监听逻辑。
  2. 脚本插入位置为head,执行时body内的canvas元素还未完成客户端hydration,脚本获取不到DOM节点直接静默失败。
  3. 未处理主题切换、组件卸载时的脚本清理逻辑,重复插入同脚本也会导致执行异常。

解决方案

1. 重写useScript钩子

import { useEffect, useRef } from "react"
import { BASE_URL as base } from "../config"

const useScript = (scriptName) => {
  const executedRef = useRef(null)

  useEffect(() => {
    if (!scriptName || typeof window === "undefined") return
    // 同主题脚本已执行过直接跳过
    if (executedRef.current === scriptName) return

    // 清理旧脚本和canvas残留
    const oldScript = document.querySelector(`script[data-theme="${scriptName}"]`)
    oldScript?.remove()
    const canvas = document.getElementById("canvas")
    const ctx = canvas?.getContext('2d')
    ctx?.clearRect(0, 0, canvas.width, canvas.height)

    // 创建新脚本
    const element = document.createElement("script")
    element.src = `${base}/static/canvasThemes/${scriptName}.js`
    element.type = "text/javascript"
    element.dataset.theme = scriptName

    element.onload = () => {
      executedRef.current = scriptName
    }
    element.onerror = () => {
      console.error(`主题脚本${scriptName}加载失败`)
    }

    // 插入到body末尾,确保canvas已存在
    document.body.appendChild(element)

    // 清理逻辑
    return () => {
      element.remove()
      ctx?.clearRect(0, 0, canvas.width, canvas.height)
    }
  }, [scriptName])
};

export default useScript

2. 修改PageContent逻辑

import React, { useContext, useEffect, useState } from "react";
import { ThemeContext } from "../../../contexts/ThemeContext";
import { FontContext } from "../../../contexts/FontContext";
import { ColourContext } from "../../../contexts/ColourContext";
import StyledPageContent from '../../../styled/StyledPageContent';
import useScript from "../../../hooks/useScript";
import cookie from 'cookie-cutter'

function PageContent(props) {
  const { isDarkTheme } = useContext(ThemeContext);
  const { isLegacyFont } = useContext(FontContext);
  const { colour } = useContext(ColourContext)
  const [theme, setTheme] = useState("")

  useEffect(() => {
    const savedTheme = cookie.get("isDarkTheme")
    setTheme(savedTheme || "light") // 补充默认值避免空值
  }, [isDarkTheme]) // 监听主题切换自动加载对应脚本

  useScript(theme)

  return (
    <StyledPageContent
      isDarkTheme={isDarkTheme}
      isLegacyFont={isLegacyFont}
      colour={colour}
    >
      {props.children}
    </StyledPageContent>
  );
}

export default PageContent;

3. 修正_document.jsx的DOM结构

你当前把NextScript和Main放在了错误的嵌套层级,修改body部分如下:

<body>
  <div className="canvas-wrapper">
    <canvas id="canvas" className="canvas-bg"></canvas>
  </div>
  <Main />
  <NextScript />
  <script
    defer
    src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
    integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
    crossOrigin="anonymous"
  ></script>
  <script
    defer
    src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
    integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
    crossOrigin="anonymous"
  ></script>
  <script
    defer
    src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
    integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
    crossOrigin="anonymous"
  ></script>
</body>

可选排查项

如果修改后还是无效果,先在主题脚本开头加DOM加载判断,避免执行时DOM未就绪:

// 主题脚本开头添加
function initCanvas() {
  const canvas = document.getElementById('canvas')
  // 原有canvas逻辑写在这里
}

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initCanvas)
} else {
  initCanvas()
}

同时确认主题脚本文件放在项目public/static/canvasThemes/目录下,路径匹配你配置的BASE_URL。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 02:57:03