Next.js中通过JavaScript动态加载外部主题脚本不执行问题求助
问题根因
现有实现存在3个核心缺陷导致脚本加载后不执行:
useScript钩子执行时机错误,直接在渲染同步阶段操作DOM,脱离React生命周期管控,无脚本加载状态监听逻辑。- 脚本插入位置为head,执行时body内的canvas元素还未完成客户端hydration,脚本获取不到DOM节点直接静默失败。
- 未处理主题切换、组件卸载时的脚本清理逻辑,重复插入同脚本也会导致执行异常。
解决方案
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
相关产品推荐
相关产品推荐

