
next.js 中直接通过 document.queryselector 查询 css modules 生成的类名会失败,因为类名被哈希化;应改用 useref 获取真实 dom 节点,并注意服务端渲染(ssr)下 window 和 document 不可用的问题。
next.js 中直接通过 document.queryselector 查询 css modules 生成的类名会失败,因为类名被哈希化;应改用 useref 获取真实 dom 节点,并注意服务端渲染(ssr)下 window 和 document 不可用的问题。
在 Next.js 中使用 useEffect 操作 DOM 时,需特别注意两个关键问题:CSS Modules 的动态类名机制和服务端渲染(SSR)环境限制。你遇到的 neonLine is null 错误,根本原因在于:
- CSS Modules 会将 styles["nLine"] 编译为类似 Neon_nLine__abc123 的唯一哈希类名,而非字面量 .nLine,因此 document.querySelector(".nLine") 必然返回 null;
- useEffect 在 SSR 期间不执行(这是正确的),但若代码中未做环境判断,直接访问 window 或 document 可能导致构建时报错或 hydration 不一致。
✅ 正确做法是:用 useRef 绑定目标元素,并在 useEffect 中安全访问 DOM。以下是优化后的完整实现:
"use client"; // ⚠️ Next.js 13+ 必须显式标记为 Client Component
import React, { useState, useEffect, useRef } from "react";
import styles from "./Neon.module.css";
const Neon = () => {
const [scrolling, setScrolling] = useState(false);
const neonLineRef = useRef<HTMLDivElement>(null); // 类型化 ref,更安全
useEffect(() => {
// ✅ 确保仅在浏览器环境中运行
if (typeof window === "undefined") return;
const handleScroll = () => {
setScrolling(true);
const neonLine = neonLineRef.current;
if (!neonLine) return; // 防御性检查
const scrollTop = window.scrollY;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollPercentage = (scrollTop / (documentHeight - windowHeight)) * 100;
neonLine.style.height = `${scrollPercentage}%`;
};
let scrollingTimeout: NodeJS.Timeout;
const checkScrollStop = () => {
if (scrolling) {
setScrolling(false);
clearTimeout(scrollingTimeout);
} else {
// ✅ 直接操作 ref.current,无需 querySelector
if (neonLineRef.current) {
neonLineRef.current.style.transition = "height 0.5s ease-in-out";
}
}
};
window.addEventListener("scroll", handleScroll);
window.addEventListener("scroll", checkScrollStop);
// 清理函数
return () => {
window.removeEventListener("scroll", handleScroll);
window.removeEventListener("scroll", checkScrollStop);
clearTimeout(scrollingTimeout);
};
}, [scrolling]); // 注意:依赖项包含 scrolling,确保逻辑一致性
return (
<div className={styles.con}>
<div className={styles.neon}></div>
<div className={styles.line}>
{/* ✅ 使用 ref 绑定,不再依赖类名字符串 */}
<div ref={neonLineRef} className={styles.nLine}></div>
</div>
</div>
);
};
export default Neon;? 关键改进点总结:
- 添加 "use client" 指令,明确组件为客户端渲染组件;
- 使用 useRef<HTMLDivElement> 替代 document.querySelector,避免类名哈希导致的查询失败;
- 在 useEffect 开头增加 typeof window === "undefined" 判断,防止 SSR 时访问浏览器 API;
- 对 ref.current 做空值检查(if (!neonLine) return),提升健壮性;
- 移除重复监听器注册(原代码中 checkScrollStop 也绑定在 scroll 上,易引发竞态),建议改用防抖(debounce)或 requestAnimationFrame 优化性能(进阶可选);
- useEffect 依赖数组中加入 [scrolling],使逻辑响应状态变化更可控(也可根据实际需求调整)。
? 额外提示: 若需更高性能的滚动效果,推荐使用 IntersectionObserver 或 useScroll(如 framer-motion 提供)替代频繁的 scroll 事件监听,减少重绘压力。


















