Next.js服务端渲染模式下Video.js运行异常问题求助
Next.js集成Video.js报错解决方案
问题根因
你遇到的黑屏后播放器消失、控制台输出VIDEOJS: WARN: The supplied element is not included in the DOM警告,由几个问题共同导致:
- Video.js是纯浏览器端依赖,Next.js默认的服务端渲染逻辑会在DOM不存在的阶段尝试执行播放器初始化
- React 18 严格模式下开发环境会重复执行useEffect挂载/销毁流程,原有销毁逻辑会提前删除DOM元素,导致二次初始化时找不到挂载节点
- 原有组件useEffect依赖配置错误:依赖数组中加入了不会变化的ref对象,更新分支中直接使用未定义的
player变量触发JS错误,同时缺失必要的依赖项导致闭包问题 - 配置冲突:同时开启
fluid: true自适应和固定宽高,会导致播放器尺寸渲染异常
修复代码
1. 修正ThorPlayer组件逻辑
// Next.js App Router 下必须加下面这行,Pages Router可省略 'use client'; import React from 'react'; import videojs from 'video.js'; import 'video.js/dist/video-js.css'; export const ThorPlayer = (props) => { const videoRef = React.useRef(null); const playerRef = React.useRef(null); const { options, onReady } = props; React.useEffect(() => { // 仅在播放器未初始化、DOM节点已挂载时执行初始化 if (!playerRef.current && videoRef.current) { const player = videojs(videoRef.current, options, () => { player.log('player is ready'); onReady?.(player); }); playerRef.current = player; } else if (playerRef.current) { const player = playerRef.current; player.autoplay(options.autoplay); player.src(options.sources); } // 移除无意义的videoRef依赖,补充缺失的onReady依赖 }, [options, onReady]); React.useEffect(() => { // 仅在组件真正卸载时销毁播放器,兼容React18严格模式 return () => { if (playerRef.current) { playerRef.current.dispose(); playerRef.current = null; } }; }, []); return ( <div data-vjs-player> <video ref={videoRef} className='video-js vjs-big-play-centered' style={{ width: "800px", height: "400px" }} /> </div> ); } export default ThorPlayer;
2. 修正页面引入方式(禁用SSR)
不管是App Router还是Pages Router,都需要通过动态导入关闭组件的服务端渲染,避免服务端执行Video.js逻辑:
import React from 'react' import dynamic from 'next/dynamic'; // 动态导入播放器组件,禁用SSR const ThorPlayer = dynamic(() => import('../../components/_ThorPlayer'), { ssr: false }); export default function index() { const playerRef = React.useRef(null); const videoJsOptions = { autoplay: true, controls: true, responsive: true, fluid: false, // 和固定宽高冲突,需要自适应就删除内联宽高,把这个值改为true muted: true, // 浏览器自动播放策略要求,非静音状态下自动播放会被拦截 sources: [{ src: 'https://obj-gravscale.zadarazios.com:443/v1/AUTH_f57eb386f52e4dc0bcdf19764aecc205/ct/bl_se.mp4', type: 'video/mp4' }] }; const handlePlayerReady = (player) => { playerRef.current = player; player.on('waiting', () => { player.log('player is waiting'); }); player.on('dispose', () => { player.log('player will dispose'); }); }; return ( <> <h1>Teste de Player: </h1> <ThorPlayer options={videoJsOptions} onReady={handlePlayerReady} /> </> ) }
注意事项
- 如果需要开启播放器自适应(
fluid: true),删除video标签上的固定宽高内联样式即可,不要同时保留两个配置 - 生产环境构建后React严格模式的重复执行问题会自动消失,但上述兼容逻辑不会影响生产环境运行
- 若不需要自动播放,可以移除
muted: true配置,否则用户进入页面默认静音
内容的提问来源于stack exchange,提问作者Abner Matheus Gomes Silva
相关产品推荐
相关产品推荐

