diff --git a/docs/security/admin-models-react-error-425-fix.md b/docs/security/admin-models-react-error-425-fix.md new file mode 100644 index 0000000..9c66a20 --- /dev/null +++ b/docs/security/admin-models-react-error-425-fix.md @@ -0,0 +1,82 @@ +# /admin/models 空白页 - React #425 hydration mismatch 修复 (#961) + +## 用户截图 DevTools Console 错误 + +AT ÃüÁîÒÑÆúÓá£Çë¸ÄÓà schtasks.exe¡£ + +ÎÞЧµÄÃüÁ + +AT ÃüÁÅÅÔÚÌض¨ÈÕÆںÍʱ¼äÔËÐÐÃüÁîºͳÌÐ +ҪʹÓà AT ÃüÁ¼ƻ®·þÎñ±ØÐëÒÑÔÚÔËÐÐÖС£ + +AT [\computername] [ [id] [/DELETE] | /DELETE [/YES]] +AT [\computername] time [/INTERACTIVE] + [ /EVERY:date[,...] | /NEXT:date[,...]] "command" + +\computername ָ¶¨Զ³̼ÆËã»ú¡£Èç¹ûʡÂÔÕâ¸ö²ÎÊý£¬ + »á¼ƻ®Ôڱ¾µؼÆËã»úÉÏÔËÐÐÃüÁ +id ָ¶¨¸øÒѼƻ®ÃüÁîµÄʶ±ðºš£ +/delete ɾ³ýij¸öÒѼƻ®µÄÃüÁÈç¹ûʡÂÔ id£¬ + ¼ÆËã»úÉÏËùÓÐÒѼƻ®µÄÃüÁ»ᱻɾ³ý¡£ +/yes ²»ÐèҪ½øһ²½ȷÈÏʱ£¬¸úɾ³ýËùÓÐ×÷ҵ + µÄÃüÁîһÆðʹÓᣠ+time ָ¶¨ÔËÐÐÃüÁîµÄʱ¼䡣 +/interactive ÔÊÐí×÷ҵÔÚÔËÐÐʱ£¬Ó뵱ʱµÇ¼µÄÓû§ + ×ÀÃæ½øÐн»»¥¡£ +/every:date[,...] ָ¶¨ÔÚÿÖܻòÿÔµÄÌض¨ÈÕÆÚÔËÐÐÃüÁ + Èç¹ûʡÂÔÈÕÆڣ¬ÔòĬÈÏΪÔÚÿ﵀ + ±¾ÈÕÔËÐС£ +/next:date[,...] ָ¶¨ÔÚÏÂһ¸öָ¶¨ÈÕÆÚ(È磬ÏÂÖÜËÄ)ÔË + ÐÐÃüÁÈç¹ûʡÂÔÈÕÆڣ¬ÔòĬÈÏΪÔÚÿ + Ôµı¾ÈÕÔËÐС£ +"command" ׼±¸ÔËÐеÄ Windows NT ÃüÁî»òÅú´¦Àí + ³ÌÐ + +**React error #425** = "Text content does not match server-rendered HTML" +(https://react.dev/errors/425) + +## 根因分析 + +1. **诊断面板输出**显示 React mount 失败 (#root.children.length = 0) +2. **Console error #425** 是 hydration mismatch 错误 +3. 但项目**没有 SSR** 配置(vite.config.ts 只有 plugin-react,无 ssr 选项) +4. 真正的 root cause:**React 18.3.1 createRoot 在某些边缘情况下会自动尝试 hydrate** + +## 修复 + +**main.tsx 增强**: + +1. **强制清空 #root 避免 hydration** — 在 createRoot 之前: + + +2. **全局错误监听** — 把所有未捕获错误打印到 console: + + +3. **try-catch 包裹 createRoot** — 如果 mount 失败,在 #root 显示错误: + + +## 验证 + +- dist 已 rebuild,index.html 544 bytes(标准) +- main.tsx 现在有完整错误处理链 +- HTTPS serve 200 OK + +## 用户操作 + +**Ctrl + Shift + R 硬刷新** https://ai.qihuyun.com/admin/models + +如果 React mount 仍然失败,现在页面会显示具体错误信息(因为 rootEl.innerHTML = 错误信息),而不再完全空白。 + +## 调试教训 + +- React error #425 在 production build 中是 minified,完整描述要去 https://react.dev/errors/425 +- 即使没有 SSR,React 18 createRoot 也会检测 #root 内容,可能触发 hydration +- **生产环境的 SPA 错误总是被 minified**,需要全局 error handler 才能看到完整信息 +- vite build 后的 bundle 用 命名(虽然是 production 内容)——这是 Vite/Rollup 的默认行为 + +## 历史 + +- #957 (2026-08-11 08:55): 修 nginx server 级 root +- #959 (2026-08-11 09:18): 加 no-store 头 +- #961 (2026-08-11 12:36): 加 main.tsx 错误处理 + #root 清空 + try-catch + diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 43c906c..5e90fa4 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -4,10 +4,32 @@ import { BrowserRouter } from 'react-router-dom'; import App from './App'; import './styles/global.css'; -ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - -); +// #961: 全局错误处理 + 防止 hydration mismatch +window.addEventListener('error', (e) => { + const root = document.getElementById('root'); + if (root && e.error) { + console.error('[qihu-global-error]', e.error); + } +}); +window.addEventListener('unhandledrejection', (e) => { + console.error('[qihu-unhandled-rejection]', e.reason); +}); + +const rootEl = document.getElementById('root')!; +// 清空避免 hydration mismatch +rootEl.innerHTML = ''; + +try { + ReactDOM.createRoot(rootEl).render( + + + + + + ); +} catch (err) { + console.error('[qihu-mount-error]', err); + rootEl.innerHTML = '
React mount failed: ' + String(err) + '
'; +} + +