Kita HTML

repository·next·Indexed 21 days ago

https://github.com/kitajs/html

A high-performance HTML templating and integration suite designed for web frameworks like Fastify, Express, and Hono. It adheres to the JSX standard to provide a developer-friendly API while aiming for output compatibility with ReactDOMServer.renderToStaticMarkup. The monorepo includes the core runtime, a TypeScript plugin for IntelliSense and XSS scanning, and dedicated plugins for Fastify and Express to enable .html() responses and Suspense streaming.

Tokens
41K
Snippets
152
Records
220
Agent score
68%

What's inside @kitajs/html

  1. Overview of Kita Html

    next

    Kita Html is a high-performance server-side JSX runtime designed to compile JSX directly into plain strings rather than a virtual DOM. This approach eliminates the overhead of diffing and serialization, making it significantly faster and more memory-efficient than React, Preact, or HonoJsx for HTML generation.

    Key capabilities include:

    • Zero Virtual DOM: Renders directly to strings, reducing memory allocation and CPU usage.
    • Compile-Time XSS Protection: Uses a TypeScript plugin and the xss-scan CLI to detect unsafe string interpolations.
    • Async Components & Streaming: Supports async children and Suspense for streaming HTML via chunked transfer encoding.
    • Full HTML Type Coverage: Provides complete JSX type definitions for HTML5 elements, including opt-in support for HTMX, Alpine.js, and Hotwire Turbo via triple-slash directives.
    • Universal Integration: Since the output is a plain string, it can be used with any server environment that returns strings (e.g., Fastify, Express, Hono, Bun).
  2. What is Kita Html

    next

    Kita Html is a JSX runtime designed for high-performance HTML string generation. Unlike React, which creates a virtual DOM that requires reconciliation and serialization, Kita Html evaluates JSX directly into HTML strings.

    This architecture eliminates the intermediate representation, diffing, and serialization steps, making it ideal for:

    • Server-side rendering (SSR)
    • Static site generation (SSG)
    • Email templates
    • HTMX applications
    • Any HTTP handler sending HTML over the wire.
  3. Install and use @kitajs/express-html-plugin

    next

    The @kitajs/express-html-plugin is an Express middleware that integrates the Kita Html JSX engine into your Express application.

    It provides the following features:

    • Adds res.html() to handle JSX.Element and Promise<string> responses.
    • Automatically sets the Content-Type header to text/html; charset=utf-8.
    • Prepends <!doctype html> to responses containing an <html> tag by default.
    • Supports streaming Suspense output by matching the request ID (req.id) with the Suspense rid={req.id} prop.
    • Manages request IDs: it preserves an existing req.id or generates new ones (e.g., req-1) if none exist.
  4. Access API documentation for Kita HTML packages

    next

    The public API documentation for the various Kita HTML packages is generated automatically from source code using TypeDoc. You can find the detailed API references for specific packages at the following locations:

    • @kitajs/html: Core HTML functionality.
    • @kitajs/fastify-html-plugin: Integration for Fastify.
    • @kitajs/express-html-plugin: Integration for Express.
  5. Use multiple Suspense boundaries for independent loading

    next

    Each Suspense boundary resolves independently. To prevent one slow data fetch from blocking other parts of the page, place separate Suspense boundaries around different sections. You can share the same rid across all boundaries in a single request. The stream will automatically close once the last boundary resolves.

    import { Suspense, renderToStream } from '@kitajs/html/suspense'
    
    const stream = renderToStream((rid) => (
      <html>
        <body>
          <Suspense rid={rid} fallback={<div>Loading user...</div>}>
            <UserProfile id="123" />
          </Suspense>
          <Suspense rid={rid} fallback={<div>Loading feed...</div>}>
            <ActivityFeed id="123" />
          </Suspense>
        </body>
      </html>
    ))
  6. Special cases in XSS scanning

    next

    The plugin follows specific rules for certain code patterns:

    1. <script> tags: Anything inside a <script> tag is permitted, as execution is intended.
      const html = <script>{content}</script>
    2. Ternary and Binary operations: Both sides of an operation are evaluated separately. If any side is unsafe, an error is thrown, even if that branch is not reached at runtime.
      // ❌ Error if content is not safe
      const html = <div>{true ? safeContent : content}</div>
  7. How Kita Html JSX compilation works

    next

    At build time, the TypeScript compiler rewrites JSX expressions into jsx() and jsxs() function calls. At runtime, these functions perform direct string concatenation of attributes and children. This avoids the overhead of object tree construction and diffing found in virtual DOM renderers.

    import { jsx as __jsx } from '@kitajs/html/jsx-runtime'
    const username: string = 'Username'
    
    // What you write
    const html = (
      <div class="card">
        <h1 safe>{username}</h1>
      </div>
    )
    
    // What TypeScript compiles to
    const compiledHtml = __jsx('div', {
      class: 'card',
      children: __jsx('h1', {
        safe: true,
        children: username
      })
    })
  8. Choosing between Kita Html and React

    next

    Kita Html and React serve different purposes despite both using JSX. The choice depends on whether your primary goal is generating HTML or managing a client-side interactive application.

    Use Kita Html when:

    • You need to generate Server-side rendered (SSR) pages.
    • You are building HTMX-style applications.
    • You are creating Email templates.
    • You are performing Static HTML generation.
    • You are writing HTTP handlers that return HTML directly.
    • You want low-bundling or no-bundling setups.

    Use React when:

    • You need complex client-side interactions.
    • You are building large stateful interfaces.
    • You require rich browser-only behavior.
    • You are building component-heavy frontend apps.
    • You need hydrated client-side applications.
    • You want to leverage a broad third-party component ecosystem.
  9. Understand the Kita Html performance architecture

    next

    Kita Html achieves high performance through a string-only architecture. Unlike Virtual DOM renderers, it avoids the overhead of:

    • Object tree construction
    • Diffing algorithms
    • Serialization steps

    Key performance optimizations include:

    • Character-by-character loops: Used for HTML escaping instead of expensive regex replacements.
    • Optimized checks: The runtime performs regex tests before attempting expensive operations and orders void element checks by frequency to minimize branching costs.
    • Runtime delegation: When running on the Bun runtime, Kita Html delegates HTML escaping to Bun's native escapeHTML implementation for maximum speed.
  10. Enable Suspense streaming in Fastify

    next

    When your JSX tree contains Suspense components, the plugin automatically switches from a buffered response to a chunked stream.

    Explicit Suspense

    By default, you should use the Suspense component from @kitajs/html/suspense and pass req.id as the rid prop. This ties the stream to the specific request.

    Automatic Suspense

    To avoid manually passing req.id through your component tree, you can enable autoSuspense: true during plugin registration. This allows you to use the AutoSuspense component. The plugin establishes the request-ID scope in Fastify's onRequest hook to ensure all async work (including parsing and validation) is covered.

    // Option 1: Explicit Suspense
    import { Suspense } from '@kitajs/html/suspense'
    
    app.get('/dashboard', (req, reply) => {
      reply.html(
        <Suspense rid={req.id} fallback={<div>Loading...</div>}>
          <AsyncDashboard />
        </Suspense>
      )
    })
    
    // Option 2: Automatic Suspense
    import { AutoSuspense } from '@kitajs/html/suspense'
    
    await app.register(fastifyKitaHtml, { autoSuspense: true })
    
    app.get('/dashboard', (req, reply) => {
      reply.html(
        <AutoSuspense fallback={<div>Loading...</div>}>
          <AsyncDashboard />
        </AutoSuspense>
      )
    })
  11. Understand JSX serialization behavior

    next

    Different JavaScript types are serialized differently when used as children. Unlike React, booleans render as their string representation, and arrays are concatenated without separators.

    | Expression     | Output  |
    | -------------- | ------- |
    | `{"abc"}`      | `abc`   |
    | `{42}`         | `42`    |
    | `{true}`       | `true`  |
    | `{false}`      | `false` |
    | `{null}`       | `''`    |
    | `{undefined}`  | `''`    |
    | `{[1, 2, 3]}`  | `123`   |
    | `{BigInt(42)}` | `42`    |
  12. Use Async Components in JSX

    next

    Async components are supported. If any child or sub-child in a component tree is an async function (returning a Promise), the entire parent tree will also resolve to a Promise instead of a string. If no async components are present, the result is a simple string.

    Note: Due to TypeScript limitations (issue #14729), you may need to manually check if a JSX.Element is a Promise or a string if you are unsure of the tree's contents.

    async function Async() {
      await callApi()
      return <div>Async!</div>
    }
    
    function Sync() {
      return <div>Sync!</div>
    }
    
    // This will be a Promise
    const asyncResult = (
      <div>
        <Async />
      </div>
    )
    
    // This will be a string
    const syncResult: string = (
      <div>
        <Sync />
      </div>
    )