前端开发踩坑实录:14 个真实 Bug 的根因与解法
来源:tiyukains-blog 项目实战。当反复犯同样的错误、或者踩已知的坑时,读这篇。
坑 1:CSS 镂空效果(椭圆卡片窗口)
需求:白色卡片中间挖一个椭圆洞,透出背后的 3D 模型。
尝试 5 种方案均失败:mask-image + inline SVG、mask-image + radial-gradient、CSS clip-path + evenodd、SVG <mask> + CSS 引用(坐标错)、CSS border-radius 双卡片拼椭圆(两条独立曲线不统一)。
最终方案:两张 SVG,同一段 path + fill-rule="evenodd",不同 viewBox 窗口切出上/下半,不同 z-index 夹住 Canvas。
教训:CSS 遮罩/裁剪跨浏览器兼容性极差。SVG 原生能力(viewBox + path)是最可靠的。不要死磕 CSS mask。
坑 2:CSS transform 互相覆盖
.r-pop 的 transform: scale(.88) 覆盖了 translateY(-50%) 定位 → blob 飞到页面顶部。
教训:任何需要独立 transform 做定位的元素,绝对不能同时用带 transform 的动效 class。
坑 3:IntersectionObserver 边界抖动
元素露出 8% → toggle .on → 动画播放 → 边缘处 isIntersecting 反复翻转 → 疯狂抖动。
最终方案:reveal-once + 动画锁。阈值 0.25,触发后锁定 1.7s 不被打断。
坑 4:npm uninstall 删掉运行时依赖
代码里没 import 不代表包没用。shadcn 组件内部依赖 @base-ui/react、class-variance-authority。删掉后整个页面白屏。
教训:安全审查时只删确认无用的包。不确定用 npx depcheck 先扫描。
坑 5:CSP 安全头在 dev 模式拦截 HMR
方案:CSP 仅生产环境启用,开发环境跳过。
但上线后接连踩了两个生产 CSP 坑:
script-src 'self'拦了 Next.js 内联脚本 → 页面空白style-src 'self'拦了 Google Fonts CSS → 中文字体不加载
教训:CSP 策略要在本地用 production build 验证,dev 模式不触发。
坑 6:字体选择的 AI 惯性
AI 默认输出 Inter/Roboto/Space Grotesk → 全站长一个样。Fraunces 的 SOFT 轴让衬线显得软绵绵 → 换 Playfair Display。
教训:字体是品牌最强的视觉信号。不选 AI 默认字体。
坑 7:配置文件分散
链接、名字散落在 5+ 个文件 → 改一处漏一处。
方案:所有配置集中在一个 site.config.ts,所有页面从这里读取。footerLinks 引用 links 而非重复写 URL。
坑 8:ClientShell 移到 layout.tsx 后页面空白
layout.tsx 中的 Client Component 在页面导航时不卸载不重挂载。useReveal() 的 useEffect([]) 空依赖导致 IntersectionObserver 只执行一次,新页面的 .reveal 元素永远不可见。
方案:改为依赖 usePathname(),每次路由变化自动重建 observer。
坑 9:Next.js <Link> vs 浏览器原生后退
从文章页点「← Back」,Next.js <Link href="/"> 是正向导航,滚到页面顶部。
方案:BackLink 组件用 router.back() 触发真正的浏览器后退。
坑 10:文章目录式结构
MDX 平铺 vs 文件夹自包含。图片放 public/ 和文章分两处,删文章时图片漏删。
方案:content/posts/YYYY-MM-DD-slug/slug.mdx + assets/,图片用相对路径通过动态路由服务。
坑 11:ArrowLink 装饰线影响居中
装饰线作为 inline-flex 子元素 = 整体居中 → 文字偏左。
方案:对称双线 — Text —,左线 origin-right 往左伸,右线 origin-left 往右伸。
坑 12:hydration mismatch — useEffect DOM 操作时机
useReveal() 的 observer 在水合期间给可见元素加 data-locked 和 .on class。requestAnimationFrame 不够,setTimeout(0) 也不够。
方案:setTimeout + requestAnimationFrame 双层异步延迟到水合完成后。
坑 13:配置文件重构后字段未消费
aboutBlurb.paragraph1/2 定义了但页面仍用硬编码 JSX。改 config 值不生效。
教训:配置重构后必须逐一验证每个字段的消费者。
坑 14:跨模块函数复制粘贴
parseFrontmatter、parseTagList、calcReadingTime 在 posts.ts 和 projects.ts 里一模一样。
方案:抽取 frontmatter.ts 单一真相源,两个模块和测试都 import 它。