Skip to content

调试技巧:c.getLayout()c.render() 与 SSR 集成

Hono 本身是一个极简的 Web 框架,默认不内置 c.getLayout()c.render() 这样的方法。这些功能通常来自于你集成的 模板引擎中间件SSR(服务端渲染)框架

因此,调试与 SSR 相关的问题,关键在于理解你所使用的集成方案的工作机制,并利用 Hono 的上下文(Context)进行日志输出和流程控制。

一、常见 SSR 集成方案

Hono 支持多种 SSR 方式,常见的有:

方案说明
Hono + hono/html内置轻量级 HTML 构建器,适合简单 SSR
Hono + hono/vite与 Vite 集成,支持 React、Preact、Vue 等框架的 SSR
Hono + eta / nunjucks / handlebars传统模板引擎
自定义 render() 函数手动集成任意模板引擎

二、c.render() 的典型实现与调试

假设你使用 eta 模板引擎:

ts
// middleware/template.ts
import { Eta } from 'eta'

const eta = new Eta()

export const render = (template: string, data: Record<string, any> = {}) => {
  return async (c: any, next: () => Promise<any>) => {
    // 将 render 方法挂载到 c 上
    c.render = (file: string, locals: Record<string, any> = {}) => {
      return eta.render(file, { ...data, ...locals })
    }
    await next()
  }
}
使用:
ts
app.use('*', render('./views', { siteName: 'My App' }))

app.get('/', async (c) => {
  const html = await c.render('index.eta', { user: { name: 'John' } })
  return c.html(html)
})
调试技巧 1:打印渲染上下文
ts
c.render = (file: string, locals: Record<string, any> = {}) => {
  console.log('[SSR Debug] Rendering:', file)
  console.log('[SSR Debug] Locals:', locals)
  return eta.render(file, { ...data, ...locals })
}
  • 检查 locals 是否包含预期数据;
  • 确认 file 路径是否正确(避免 ENOENT 错误)。

三、c.getLayout() 的实现与调试

getLayout 通常用于实现布局模板(Layouts),如 main.eta 包含 <header><footer>,内容区域由子页面填充。

实现 getLayout
ts
c.getLayout = () => {
  return async (content: string, layoutData: Record<string, any> = {}) => {
    return await c.render('layouts/main.eta', {
      content,
      ...layoutData
    })
  }
}
使用:
ts
app.get('/about', async (c) => {
  const content = await c.render('about.eta')
  const html = await c.getLayout()(content, { title: 'About Us' })
  return c.html(html)
})
调试技巧 2:分步验证渲染流程
ts
app.get('/debug', async (c) => {
  try {
    // 1. 检查模板文件是否存在
    const fs = require('fs')
    const path = require('path')
    const layoutPath = path.resolve('./views/layouts/main.eta')
    console.log('Layout file exists:', fs.existsSync(layoutPath))

    // 2. 单独渲染内容
    const content = await c.render('debug.eta', { test: 'Hello' })
    console.log('Content rendered:', content.substring(0, 100) + '...')

    // 3. 渲染布局
    const html = await c.getLayout()(content, { title: 'Debug' })
    console.log('Final HTML length:', html.length)

    return c.html(html)
  } catch (err: any) {
    console.error('[SSR Error]', err.message)
    return c.text(`SSR Error: ${err.message}`, 500)
  }
})

四、与 Vite 的 SSR 集成调试

使用 hono/vite 时,SSR 流程更复杂,涉及 Vite 的开发服务器和构建产物。

常见问题与调试
  1. ** ENOENT: no such file or directory**

    • 原因:Vite 找不到 .html 入口文件。
    • 调试:
      ts
      console.log('CWD:', process.cwd())
      console.log('HTML Path:', path.resolve('./index.html'))
  2. ** ReferenceError: React is not defined**

    • 原因:SSR 构建时未正确导出组件。
    • 调试:检查 entry-server.tsx 是否正确导出:
      ts
      export default function App() {}
  3. ** 样式未注入**

    • 原因:CSS 没有通过 cssMap 注入。
    • 调试:打印 result
      ts
      console.log('CSS:', result.css)
示例:Vite SSR 调试中间件
ts
import { serveStatic } from '@hono/node-server/serve-static'
import { dangerouslySetHTMLString, html } from 'hono/html'

app.use(
  '/static/*',
  serveStatic({ root: './dist/client', rewriteRequestPath: (path) => path.replace(/^\/static/, '') })
)

app.get('*', async (c) => {
  const manifest = await fs.promises.readFile('./dist/client/.vite/manifest.json', 'utf-8')
  const manifestJson = JSON.parse(manifest)

  const entry = manifestJson['src/entry-client.tsx']
  const cssFiles = entry.css || []

  try {
    const { render } = await import('../dist/server/entry-server.js')
    const appHtml = await render(c.req.url)
    
    console.log('[Vite SSR] Rendered HTML length:', appHtml.length)
    console.log('[Vite SSR] CSS files:', cssFiles)

    return c.html(
      html`<!DOCTYPE html>
        <html>
          <head>
            <meta charset="utf-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            ${cssFiles.map((file: string) => `<link rel="stylesheet" href="/static/${file}">`).join('')}
          </head>
          <body>
            <div id="root">${dangerouslySetHTMLString(appHtml)}</div>
            <script type="module" src="/src/entry-client.tsx"></script>
          </body>
        </html>`
    )
  } catch (err) {
    console.error('[Vite SSR Error]', err)
    return c.text('SSR Render Failed', 500)
  }
})

五、通用调试技巧总结

技巧说明
打印 c.req 信息console.log(c.req.path, c.req.query())
捕获并打印异常try/catch + console.error
检查文件路径使用 path.resolve() 确保路径正确
分步渲染验证先测 c.render(),再测 c.getLayout()
启用模板引擎调试模式eta.config.debug = true
使用 c.text() 输出中间结果快速查看变量值
检查 Vite 构建产物确认 dist/ 目录结构正确

六、最佳实践

  1. 封装调试中间件

    ts
    app.use('/debug/*', (c, next) => {
      console.log('Debug mode:', c.req.path)
      return next()
    })
  2. 环境区分

    ts
    if (process.env.NODE_ENV === 'development') {
      app.use('*', (c, next) => {
        c.startTime = Date.now()
        return next()
      })
      // 记录请求耗时
    }
  3. 使用 Source Maps:确保错误能定位到源码。

七、结论

虽然 Hono 没有原生的 c.getLayout()c.render(),但其灵活的上下文机制让你可以轻松集成任何 SSR 方案。

调试的核心是

  • 理解数据流(请求 → 上下文 → 模板 → HTML);
  • 利用 console.logtry/catch 暴露中间状态;
  • 分模块验证,避免问题扩散。

掌握这些技巧,你就能高效地构建和调试 Hono 的 SSR 应用。