Next.js TypeScript集成TinyMce报错:navigator is not defined
问题描述
我在TypeScript版本的Next.js项目中,采用本地打包方式集成TinyMCE(无需API key),参考官方文档创建了BundledEditor.jsx组件,正常使用无问题,但刷新页面时会抛出ReferenceError: navigator is not defined错误。
错误原因
Next.js默认采用服务端渲染(SSR),TinyMCE的核心依赖代码会尝试访问navigator这个浏览器专属全局对象,而服务端环境中不存在该对象,因此刷新页面(服务端重新渲染组件)时会触发错误。
解决方案
只需确保TinyMCE相关组件仅在客户端环境中渲染,以下是两种简洁的实现方式:
方案一:动态导入组件并禁用SSR(推荐)
在使用BundledEditor的tsx文件中,改用Next.js提供的dynamic函数导入组件,明确禁用服务端渲染:
import { useRef } from 'react' import dynamic from 'next/dynamic' // 动态导入BundledEditor,指定仅在客户端渲染 const BundledEditor = dynamic(() => import('../BundledEditor'), { ssr: false, loading: () => <div>加载编辑器中...</div> // 可选的加载占位UI }) const editorRef = useRef<any>(null) return ( <> <BundledEditor onInit={(evt: any, editor: any) => (editorRef.current = editor)} initialValue='' init={{ height: 500, menubar: false, plugins: [ 'advlist', 'anchor', 'autolink', 'help', 'image', 'link', 'lists', 'searchreplace', 'table', 'wordcount' ], toolbar: 'undo redo | blocks | ' + 'bold italic forecolor | alignleft aligncenter ' + 'alignright alignjustify | bullist numlist outdent indent | ' + 'removeformat | help', content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }' }} /> </> );
方案二:在组件内部判断客户端环境
修改BundledEditor.jsx,通过useEffect和状态判断,确保仅在客户端环境渲染编辑器:
import React, { useEffect, useState } from 'react' import { Editor } from '@tinymce/tinymce-react' // TinyMCE so the global var exists // eslint-disable-next-line no-unused-vars import tinymce from 'tinymce/tinymce' // DOM model import 'tinymce/models/dom/model' // Theme import 'tinymce/themes/silver' // Toolbar icons import 'tinymce/icons/default' // Editor styles import 'tinymce/skins/ui/oxide/skin.min.css' // importing the plugin js. // if you use a plugin that is not listed here the editor will fail to load import 'tinymce/plugins/advlist' import 'tinymce/plugins/anchor' import 'tinymce/plugins/autolink' import 'tinymce/plugins/autoresize' import 'tinymce/plugins/autosave' import 'tinymce/plugins/charmap' import 'tinymce/plugins/code' import 'tinymce/plugins/codesample' import 'tinymce/plugins/directionality' import 'tinymce/plugins/emoticons' import 'tinymce/plugins/fullscreen' import 'tinymce/plugins/help' import 'tinymce/plugins/image' import 'tinymce/plugins/importcss' import 'tinymce/plugins/insertdatetime' import 'tinymce/plugins/link' import 'tinymce/plugins/lists' import 'tinymce/plugins/media' import 'tinymce/plugins/nonbreaking' import 'tinymce/plugins/pagebreak' import 'tinymce/plugins/preview' import 'tinymce/plugins/quickbars' import 'tinymce/plugins/save' import 'tinymce/plugins/searchreplace' import 'tinymce/plugins/table' import 'tinymce/plugins/template' import 'tinymce/plugins/visualblocks' import 'tinymce/plugins/visualchars' import 'tinymce/plugins/wordcount' // importing plugin resources import 'tinymce/plugins/emoticons/js/emojis' // Content styles, including inline UI like fake cursors /* eslint import/no-webpack-loader-syntax: off */ import contentCss from '!!raw-loader!tinymce/skins/content/default/content.min.css' import contentUiCss from '!!raw-loader!tinymce/skins/ui/oxide/content.min.css' export default function BundledEditor(props) { const { init, ...rest } = props const [isClient, setIsClient] = useState(false) useEffect(() => { // 仅在客户端环境执行此逻辑 setIsClient(true) }, []) // 服务端渲染时返回占位UI if (!isClient) { return <div>加载编辑器中...</div> } return ( <Editor init={{ ...init, skin: false, content_css: false, content_style: [contentCss, contentUiCss, init.content_style || ''].join('\n') }} {...rest} /> ) }
说明:两种方案都能解决问题,方案一代码更简洁,且直接利用Next.js特性隔离服务端渲染逻辑,是更推荐的做法。
内容的提问来源于stack exchange,提问作者Morton
相关产品推荐
相关产品推荐

