运行时错误详解
Template data validation failed、模板渲染失败、截图失败、CSS 加载失败等运行时错误的完整排查
运行时错误详解
运行时错误发生在开发面板预览或真实渲染(Karin 指令触发)时。本页覆盖数据校验失败、SSR 渲染错误、截图失败、CSS 加载失败等场景。
Invalid hook call
症状
真实渲染时报错,堆栈里组件的 React 和 react-dom-server 来自不同的 node_modules 路径:
Invalid hook call. Hooks can only be called inside of the body of a function component...
TypeError: Cannot read properties of null (reading 'useContext')原因
进程里存在两份 React 实例:模板组件解析到的 react 和渲染器解析到的 react-dom/server 不在同一个 node_modules 树。典型场景是把 ktr 仓库 link: 进下游开发——渲染器的静态导入会从 ktr 仓库自己的 node_modules 解析出另一份 react-dom。
当前版本已自动处理:加载 .ts 源注册表(dev / link 场景)时,createTemplateRenderer 会从下游包根解析 react 和 react-dom/server 注入渲染器,保证与组件共用同一实例;生产 bundle(组件与渲染器打在同一产物里)不受影响。
解决方案
- 把
@karinjs/template-react升级到包含此修复的版本; - 如果仍遇到,检查模板组件是否从别的 node_modules 树解析了 React(比如 monorepo 里另一个包提供了自己的 react 副本),保证下游项目只有一份
react/react-dom; - 手动渲染(直接
createRenderer)且确实存在双副本时,可通过RendererOptions.ssrRuntime显式传入组件侧的createElement/renderToReadableStream。
Template data validation failed
症状
开发面板预览或真实渲染时报错:
Error: Template data validation failed或者:
模板渲染失败 hello/card:Template data validation failed原因
传入的 data 没有通过模板定义的 validate 函数检查。
解决方案
1. 检查模板的 validate 函数
export default ({
: ,
: (): is CardData =>
typeof === 'object' && !== null && typeof ( as CardData). === 'string' && .(( as CardData).)
})2. 检查传入的数据
真实渲染时:
// ❌ 错误:缺少 items 字段
await ('hello/card', { : '卡片标题'
// 缺少 items
})
// ✅ 正确:所有必填字段都提供
await ('hello/card', {
: '卡片标题',
: [{ : '状态', : '正常' }]
})开发面板预览时,检查 data/*.json 或 mock.ts 的数据结构。
3. 改进 validate 提供详细错误信息
export default ({
: ,
: (): is CardData => {
if (typeof !== 'object' || === null) {
.('[validate] data 不是对象:', )
return false
}
const = as CardData
if (typeof . !== 'string') {
.('[validate] title 不是字符串:', .)
return false
}
if (!.(.)) {
.('[validate] items 不是数组:', .)
return false
}
return true
}
})控制台会打印具体哪个字段不符合预期。
4. 临时禁用 validate 调试
注释掉 validate 字段,看组件能否正常渲染:
export default ({
:
// validate: ... ← 临时注释
})如果渲染成功,说明是 validate 逻辑过严;如果仍然失败,说明是组件本身的问题。
模板渲染失败:React 组件错误
症状
Error: Objects are not valid as a React child (found: object with keys {x, y})或者:
TypeError: Cannot read properties of undefined (reading 'map')原因
组件代码访问了不存在的字段、或直接渲染了对象(React 不支持)。
解决方案
1. 检查字段访问
import { , type } from '@karinjs/template-react'
interface CardData {
: string
?: <{ : string; : string }>
}
// ❌ items 可能是 undefined
const = ({ }: <CardData>) => (
<>
{data.items.(() => ( < ={.}>{.}</>
))}
</>
)
export default ({ : })修复:
import { , type } from '@karinjs/template-react'
interface CardData {
: string
?: <{ : string; : string }>
}
// ✅ 可选字段加可选链或默认值
const = ({ }: <CardData>) => (
<>{.?.(() => < ={.}>{.}</>) ?? <>暂无数据</>}</>
)
export default ({ : })2. 避免直接渲染对象
import { , type } from '@karinjs/template-react'
interface CardData {
: { : string; : number }
}
// ❌ 直接渲染对象
const = ({ }: <CardData>) => <>{data.user}</>
export default ({ : })修复:
import { , type } from '@karinjs/template-react'
interface CardData {
: { : string; : number }
}
// ✅ 只渲染字符串或数字
const = ({ }: <CardData>) => (
<>
{..} ({..}岁)
</>
)
export default ({ : })3. 在开发面板调试
开发面板会展示完整的 React 错误栈,打开浏览器 DevTools Console 查看详细错误信息。
截图失败:Puppeteer 超时
症状
Error: Navigation timeout of 30000 ms exceeded或者:
Error: Execution context was destroyed, most likely because of a navigation原因
Puppeteer 打开 HTML 超时、或页面加载过慢、或 SSR 生成的 HTML 有 JavaScript 错误。
解决方案
1. 增加截图超时时间
export const = async < extends keyof & string>(
: ,
: <[]>,
?: <string, unknown>
): <ImageElement[]> => {
const { , } = await (, )
if (!) throw new ('渲染失败')
const = await .({
: `plugin/${}`,
: ,
: '#container',
: 'png',
: true,
: 60000, // ← 增加到 60 秒
...
})
const = .() ? : []
return .(() => segment.(`base64://${}`))
}2. 检查 HTML 是否能正常打开
找到 SSR 生成的 HTML 文件(通常在 karinPathHtml/<插件名>/ 下),用浏览器直接打开,看是否有 JavaScript 错误。
3. 检查网络资源
如果模板引用了外部图片或字体(CDN),确保网络可达:
import { , type } from '@karinjs/template-react'
// ❌ 外部图片可能加载慢或失败
const = ({ }: <any>) => (
<>
< ="https://example.com/slow-image.png" ="" />
</>
)
export default ({ : })改用本地静态资源:
import { , type } from '@karinjs/template-react'
// ✅ 本地静态资源
const = ({ }: <any>) => (
<>
< ="/logo.png" ="" />
</>
)
export default ({ : })4. 禁用 JavaScript(SSR 不应依赖客户端 JS)
SSR 生成的 HTML 应该是完全静态的,不依赖浏览器执行 JavaScript。检查组件是否误用了浏览器 API:
import { , type } from '@karinjs/template-react'
// ❌ SSR 时 window 不存在
const = ({ }: <any>) => {
const = .
return < ={{ }}>内容</>
}
export default ({ : })修复:
import { , type } from '@karinjs/template-react'
// ✅ 写死尺寸或从 data 传入
const = ({ }: <{ : number }>) => < ={{ : . }}>内容</>
export default ({ : })CSS 加载失败或样式不生效
症状
真实渲染的截图里,所有 Tailwind 类名(如 bg-background、text-foreground)都不生效,但开发面板预览正常。
原因
生产环境的 CSS 路径配置错误、或产物目录中的 style.css 未生成。
解决方案
1. 检查构建产物
确保产物目录里 style.css 存在(createTemplateRenderer 默认按 lib/style.css 发现,自定义 outDir 时用 renderer.cssPath 显式指定):
ls -la lib/style.css缺失时检查 ktrBuildPlugin 配置(见 构建问题详解 - CSS 未生成)。
2. 检查 CSS 是否内联到 HTML
SSR 生成的 HTML 应该包含内联的 <style> 标签:
# 找到 SSR 生成的 HTML 文件
find ~/.karin/temp/html -name "*.html" -type f | head -1 | xargs head -50输出应包含:
<!DOCTYPE html>
<html>
<head>
<style>
/* Tailwind reset + HeroUI 样式 + 实际用到的类 */
*,
::before,
::after {
box-sizing: border-box;
}
/* ... */
</style>
</head>
<body>
<div id="container"><!-- 模板内容 --></div>
</body>
</html>如果 <style> 标签是空的或不存在,说明 CSS 注入失败。
3. 检查 extraStylePaths 配置
如果自定义了额外样式路径,确保文件存在:
import { } from '@karinjs/template-react'
export default ({
: [
'ktr/template/custom.css' // ← 确保文件存在
]
})4. 清理缓存重新构建
rm -rf node_modules/.vite lib/
pnpm install
vite build截图透明背景失效
症状
模板给根元素设置了 rounded-3xl,但截图还是直角矩形,或者透明背景变成了白色。
原因
render.render 的 omitBackground 未设置或模板根元素有不透明背景色。
解决方案
1. 确保 omitBackground: true
const = await .({
: `${.}/hello/card`,
: ,
: '#container',
: 'png',
: true // ✅ 必须启用
})2. 模板根元素不要设置不透明背景
import { , type } from '@karinjs/template-react'
// ❌ bg-white 是不透明的
const = ({ }: <any>) => < ="rounded-3xl bg-white p-8">内容</>
export default ({ : })修复:
import { , type } from '@karinjs/template-react'
// ✅ 用语义类 bg-background,继承主题
const = ({ }: <any>) => < ="rounded-3xl bg-background p-8">内容</>
export default ({ : })3. 需要裁剪内部内容时加 overflow-hidden
import { , type } from '@karinjs/template-react'
// ✅ overflow-hidden 确保内部内容不超出圆角
const = ({ }: <any>) => (
< ="overflow-hidden rounded-3xl bg-background p-8">
< ="/wide-image.png" ="" ="w-full" />
</>
)
export default ({ : })开发面板 WebSocket 连接失败
症状
开发面板浏览器 Console 报错:
[vite] failed to connect to websocket.
[vite] WebSocket connection to 'ws://localhost:5180/' failed修改模板或数据后预览不更新。
原因
Vite HMR WebSocket 被阻断、或代理配置错误。
解决方案
详见 常见问题 - 热更新不工作。
renderImage 调用成功但 reply 不显示图片
症状
const images = await renderImage('hello/card', {/* ... */})
await event.reply(images)
// 指令执行成功但群里没有图片Karin 日志显示指令执行成功,但用户收不到图片。
原因
renderImage 返回的图片元素格式错误、或 base64 编码问题。
解决方案
1. 检查返回值结构
// ✅ 确保返回 ImageElement[]
const = .() ? : []
const : ImageElement[] = .(() => segment.(`base64://${}`))2. 打印调试信息
const = await ('hello/card', { : '测试', : [] })
.('渲染结果:', )
.('图片数量:', .)
.('第一张图片类型:', [0]?.type)
await .reply()输出应该类似:
渲染结果: [ { type: 'image', file: 'base64://iVBORw0KGgo...' } ]
图片数量: 1
第一张图片类型: image3. 检查 base64 前缀
// ✅ 正确:base64:// 双斜杠
segment.image(`base64://${img}`)
// ❌ 错误:单斜杠
// segment.image(`base64:/${img}`)4. 测试直接回复 base64
import { , segment } from 'node-karin'
// 测试 karin 的图片发送是否正常
export const = .('^#测试图片$', async () => {
// 1x1 透明 PNG 的 base64
const = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
await .([segment.(`base64://${}`)])
return true
})如果测试图片能发送,说明是 renderImage 的问题;如果测试图片也发不出,说明是 Karin 配置或网络问题。