Skip to content

JSX 렌더러 미들웨어

JSX 렌더러 미들웨어를 사용하면 c.setRenderer()를 사용하지 않고도 c.render() 함수로 JSX를 렌더링할 때 레이아웃을 설정할 수 있다. 또한 useRequestContext()를 통해 컴포넌트 내에서 Context 인스턴스에 접근할 수 있다.

임포트

ts
import { Hono } from 'hono'
import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer'

사용법

jsx
const app = new Hono()

app.get(
  '/page/*',
  jsxRenderer(({ children }) => {
    return (
      <html>
        <body>
          <header>Menu</header>
          <div>{children}</div>
        </body>
      </html>
    )
  })
)

app.get('/page/about', (c) => {
  return c.render(<h1>About me!</h1>)
})

옵션

optional docType: boolean | string

HTML의 시작 부분에 DOCTYPE을 추가하지 않으려면 docType 옵션을 false로 설정한다.

tsx
app.use(
  '*',
  jsxRenderer(
    ({ children }) => {
      return (
        <html>
          <body>{children}</body>
        </html>
      )
    },
    { docType: false }
  )
)

또한 사용자 정의 DOCTYPE을 지정할 수도 있다.

tsx
app.use(
  '*',
  jsxRenderer(
    ({ children }) => {
      return (
        <html>
          <body>{children}</body>
        </html>
      )
    },
    {
      docType:
        '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
    }
  )
)

optional stream: boolean | Record<string, string>

true로 설정하거나 Record 값을 제공하면 스트리밍 응답으로 렌더링된다.

tsx
const AsyncComponent = async () => {
  await new Promise((r) => setTimeout(r, 1000)) // 1초 대기
  return <div>Hi!</div>
}

app.get(
  '*',
  jsxRenderer(
    ({ children }) => {
      return (
        <html>
          <body>
            <h1>SSR 스트리밍</h1>
            {children}
          </body>
        </html>
      )
    },
    { stream: true }
  )
)

app.get('/', (c) => {
  return c.render(
    <Suspense fallback={<div>loading...</div>}>
      <AsyncComponent />
    </Suspense>
  )
})

true로 설정하면 다음과 같은 헤더가 추가된다:

ts
{
  'Transfer-Encoding': 'chunked',
  'Content-Type': 'text/html; charset=UTF-8',
  'Content-Encoding': 'Identity'
}

Record 값을 지정해 헤더 값을 커스터마이징할 수 있다.

중첩 레이아웃

Layout 컴포넌트를 사용하면 레이아웃을 중첩할 수 있다.

tsx
app.use(
  jsxRenderer(({ children }) => {
    return (
      <html>
        <body>{children}</body>
      </html>
    )
  })
)

const blog = new Hono()
blog.use(
  jsxRenderer(({ children, Layout }) => {
    return (
      <Layout>
        <nav>Blog Menu</nav>
        <div>{children}</div>
      </Layout>
    )
  })
)

app.route('/blog', blog)

useRequestContext()

useRequestContext()는 Context 인스턴스를 반환한다.

tsx
import { useRequestContext, jsxRenderer } from 'hono/jsx-renderer'

const app = new Hono()
app.use(jsxRenderer())

const RequestUrlBadge: FC = () => {
  const c = useRequestContext()
  return <b>{c.req.url}</b>
}

app.get('/page/info', (c) => {
  return c.render(
    <div>
      현재 접속 중인 URL: <RequestUrlBadge />
    </div>
  )
})

WARNING

Deno의 precompile JSX 옵션과 함께 useRequestContext()를 사용할 수 없다. 대신 react-jsx를 사용한다:

json
   "compilerOptions": {
     "jsx": "precompile", 
     "jsx": "react-jsx", 
     "jsxImportSource": "hono/jsx"
   }
 }

ContextRenderer 확장하기

아래와 같이 ContextRenderer를 정의하면 렌더러에 추가적인 내용을 전달할 수 있다. 예를 들어, 페이지에 따라 head 태그의 내용을 변경하고 싶을 때 유용하다.

tsx
declare module 'hono' {
  interface ContextRenderer {
    (
      content: string | Promise<string>,
      props: { title: string }
    ): Response
  }
}

const app = new Hono()

app.get(
  '/page/*',
  jsxRenderer(({ children, title }) => {
    return (
      <html>
        <head>
          <title>{title}</title>
        </head>
        <body>
          <header>Menu</header>
          <div>{children}</div>
        </body>
      </html>
    )
  })
)

app.get('/page/favorites', (c) => {
  return c.render(
    <div>
      <ul>
        <li>Eating sushi</li>
        <li>Watching baseball games</li>
      </ul>
    </div>,
    {
      title: 'My favorites',
    }
  )
})

Released under the MIT License.