故障排查

运行时错误详解

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 会从下游包根解析 reactreact-dom/server 注入渲染器,保证与组件共用同一实例;生产 bundle(组件与渲染器打在同一产物里)不受影响。

解决方案

  1. @karinjs/template-react 升级到包含此修复的版本;
  2. 如果仍遇到,检查模板组件是否从别的 node_modules 树解析了 React(比如 monorepo 里另一个包提供了自己的 react 副本),保证下游项目只有一份 react / react-dom
  3. 手动渲染(直接 createRenderer)且确实存在双副本时,可通过 RendererOptions.ssrRuntime 显式传入组件侧的 createElement / renderToReadableStream

Template data validation failed

症状

开发面板预览或真实渲染时报错:

Error: Template data validation failed

或者:

模板渲染失败 hello/card:Template data validation failed

原因

传入的 data 没有通过模板定义的 validate 函数检查。

解决方案

1. 检查模板的 validate 函数

ktr/template/hello/card/index.tsx
export default ({
  : ,
  : ():  is CardData =>
    typeof  === 'object' &&  !== null && typeof ( as CardData). === 'string' && .(( as CardData).)
})

2. 检查传入的数据

真实渲染时:

src/apps/template.ts
// ❌ 错误:缺少 items 字段
await ('hello/card', {
Argument of type '{ title: string; }' is not assignable to parameter of type '{ title: string; items: { label: string; value: string; }[]; }'. Property 'items' is missing in type '{ title: string; }' but required in type '{ title: string; items: { label: string; value: string; }[]; }'.
: '卡片标题' // 缺少 items }) // ✅ 正确:所有必填字段都提供 await ('hello/card', { : '卡片标题', : [{ : '状态', : '正常' }] })

开发面板预览时,检查 data/*.jsonmock.ts 的数据结构。

3. 改进 validate 提供详细错误信息

ktr/template/hello/card/index.tsx
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 字段,看组件能否正常渲染:

ktr/template/hello/card/index.tsx
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. 检查字段访问

ktr/template/hello/card/index.tsx(错误)
import { , type  } from '@karinjs/template-react'

interface CardData {
  : string
  ?: <{ : string; : string }>
}

// ❌ items 可能是 undefined
const  = ({  }: <CardData>) => (
  <>
    {data.items.(() => (
'data.items' is possibly 'undefined'.
< ={.}>{.}</> ))} </> ) export default ({ : })

修复:

ktr/template/hello/card/index.tsx(正确)
import { , type  } from '@karinjs/template-react'

interface CardData {
  : string
  ?: <{ : string; : string }>
}

// ✅ 可选字段加可选链或默认值
const  = ({  }: <CardData>) => (
  <>{.?.(() => < ={.}>{.}</>) ?? <>暂无数据</>}</>
)

export default ({ :  })

2. 避免直接渲染对象

ktr/template/hello/card/index.tsx(错误)
import { , type  } from '@karinjs/template-react'

interface CardData {
  : { : string; : number }
}

// ❌ 直接渲染对象
const  = ({  }: <CardData>) => <>{data.user}</>
Type '{ name: string; age: number; }' is not assignable to type 'ReactNode'.
export default ({ : })

修复:

ktr/template/hello/card/index.tsx(正确)
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. 增加截图超时时间

src/utils/render.ts
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),确保网络可达:

ktr/template/hello/card/index.tsx(错误)
import { , type  } from '@karinjs/template-react'

// ❌ 外部图片可能加载慢或失败
const  = ({  }: <any>) => (
  <>
    < ="https://example.com/slow-image.png" ="" />
  </>
)

export default ({ :  })

改用本地静态资源:

ktr/template/hello/card/index.tsx(正确)
import { , type  } from '@karinjs/template-react'

// ✅ 本地静态资源
const  = ({  }: <any>) => (
  <>
    < ="/logo.png" ="" />
  </>
)

export default ({ :  })

4. 禁用 JavaScript(SSR 不应依赖客户端 JS)

SSR 生成的 HTML 应该是完全静态的,不依赖浏览器执行 JavaScript。检查组件是否误用了浏览器 API:

ktr/template/hello/card/index.tsx(错误)
import { , type  } from '@karinjs/template-react'

// ❌ SSR 时 window 不存在
const  = ({  }: <any>) => {
  const  = .
  return < ={{  }}>内容</>
}

export default ({ :  })

修复:

ktr/template/hello/card/index.tsx(正确)
import { , type  } from '@karinjs/template-react'

// ✅ 写死尺寸或从 data 传入
const  = ({  }: <{ : number }>) => < ={{ : . }}>内容</>

export default ({ :  })

CSS 加载失败或样式不生效

症状

真实渲染的截图里,所有 Tailwind 类名(如 bg-backgroundtext-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 配置

如果自定义了额外样式路径,确保文件存在:

karin.template.ts
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.renderomitBackground 未设置或模板根元素有不透明背景色。

解决方案

1. 确保 omitBackground: true

src/utils/render.ts
const  = await .({
  : `${.}/hello/card`,
  : ,
  : '#container',
  : 'png',
  : true // ✅ 必须启用
})

2. 模板根元素不要设置不透明背景

ktr/template/hello/card/index.tsx(错误)
import { , type  } from '@karinjs/template-react'

// ❌ bg-white 是不透明的
const  = ({  }: <any>) => < ="rounded-3xl bg-white p-8">内容</>

export default ({ :  })

修复:

ktr/template/hello/card/index.tsx(正确)
import { , type  } from '@karinjs/template-react'

// ✅ 用语义类 bg-background,继承主题
const  = ({  }: <any>) => < ="rounded-3xl bg-background p-8">内容</>

export default ({ :  })

3. 需要裁剪内部内容时加 overflow-hidden

ktr/template/hello/card/index.tsx
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 不显示图片

症状

src/apps/template.ts
const images = await renderImage('hello/card', {/* ... */})
await event.reply(images)
// 指令执行成功但群里没有图片

Karin 日志显示指令执行成功,但用户收不到图片。

原因

renderImage 返回的图片元素格式错误、或 base64 编码问题。

解决方案

1. 检查返回值结构

src/utils/render.ts
// ✅ 确保返回 ImageElement[]
const  = .() ?  : []
const : ImageElement[] = .(() => segment.(`base64://${}`))

2. 打印调试信息

src/apps/template.ts
const  = await ('hello/card', { : '测试', : [] })
.('渲染结果:', )
.('图片数量:', .)
.('第一张图片类型:', [0]?.type)

await .reply()

输出应该类似:

渲染结果: [ { type: 'image', file: 'base64://iVBORw0KGgo...' } ]
图片数量: 1
第一张图片类型: image

3. 检查 base64 前缀

src/utils/render.ts
// ✅ 正确:base64:// 双斜杠
segment.image(`base64://${img}`)

// ❌ 错误:单斜杠
// segment.image(`base64:/${img}`)

4. 测试直接回复 base64

src/apps/template.ts
import { , segment } from 'node-karin'

// 测试 karin 的图片发送是否正常
export const  = .('^#测试图片$', async () => {
  // 1x1 透明 PNG 的 base64
  const  = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='
  await .([segment.(`base64://${}`)])
  return true
})

如果测试图片能发送,说明是 renderImage 的问题;如果测试图片也发不出,说明是 Karin 配置或网络问题。


相关资源

On this page