Skip to content

Streaming Template Rendering

WARNING

This feature is under development and not yet officially released.

Streaming template rendering allows you to send HTML, reducing time-to-first-byte (TTFB) and improving user experience for large templates.

Basic Concept

Instead of waiting for template to render, streaming sends HTML chunk by chunk:

typescript
// Regular render (blocking) - wait for everything to complete
const html = await view.render('large-template', data)
return ctx.send.html(html)

// Streaming render (progressive) - send chunk by chunk
const stream = view.streamRender('large-template', data)
return ctx.send.stream(stream, undefined, 'text/html; charset=utf-8')

Basic Usage

1. In Context Handler

Use ctx.streamRender() for streaming HTML response:

typescript
// routes/dashboard.ts
import type { Context } from '@neabyte/deserve'

export function GET(ctx: Context): Response {
  // Streaming render complex dashboard
  return ctx.streamRender('dashboard', {
    user: ctx.state.user,
    analytics: ctx.state.analytics
  })
}

2. Custom Response Headers

Use ctx.streamRender() with custom headers:

typescript
import type { Context } from '@neabyte/deserve'

export function GET(ctx: Context): Response {
  // Access view engine from context state
  const stream = ctx.state.view.streamRender('report', reportData)
  return new Response(stream, {
    headers: {
      'Content-Type': 'text/html; charset=utf-8',
      'Cache-Control': 'no-cache'
    }
  })
}

Template Support

All DVE template features work with streaming:

dve
<!-- views/streaming-demo.dve -->
<!DOCTYPE html>
<html>
  <head>
    <title>{{title}}</title>
  </head>
  <body>
    <header>{{header}}</header>

    <!-- Each loop streams item by item -->
    {{#each items as item}}
    <div class="item">
      <h3>{{item.name}}</h3>
      <p>{{item.description}}</p>
    </div>
    {{/each}}

    <!-- Conditional rendering -->
    {{#if showFooter}}
    <footer>{{footer}}</footer>
    {{/if}}
  </body>
</html>

Best Use Cases

1. Large Templates

typescript
// Report with thousands of data rows
export function GET(ctx: Context): Response {
  return ctx.streamRender('financial-report', {
    transactions: await getTransactions(), // 10,000+ items
    summary: calculateSummary()
  })
}

2. Real-time Data

typescript
// Dashboard with live data
export function GET(ctx: Context): Response {
  return ctx.streamRender('live-dashboard', {
    metrics: getLatestMetrics(),
    alerts: getActiveAlerts()
  })
}

3. Progressive Enhancement

typescript
// Send skeleton first, data streams
export function GET(ctx: Context): Response {
  return ctx.streamRender('progressive-app', {
    layout: getLayoutData(), // Fast
    content: await getContent(), // Slow
    analytics: getAnalytics() // Very slow
  })
}

Migration from Regular Render

typescript
// Before (blocking) - wait for everything to complete
export async function GET(ctx: Context): Promise<Response> {
  const html = await ctx.render('large-template', data)
  return html
}

// After (streaming) - send progressively
export function GET(ctx: Context): Response {
  return ctx.streamRender('large-template', data)
}

Streaming rendering significantly improves performance for large templates and real-time applications. Simple API and easy to use.

Released under the MIT License.