CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vue--server-renderer

Server-side rendering library for Vue.js applications with support for streaming and multiple environments

Pending
Overview
Eval results
Files

web-streams.mddocs/

Web Streams Support

Modern Web Streams API support for serverless environments, edge computing platforms, and modern web standards. These functions enable Vue SSR in environments like CloudFlare Workers, Vercel Edge Functions, and other platforms that support Web Streams instead of Node.js streams.

Capabilities

renderToWebStream

Creates a Web ReadableStream that outputs the rendered HTML. This function provides native Web Streams API integration for modern web platforms and edge environments.

/**
 * Renders input as a Web ReadableStream
 * @param input - Vue application instance or VNode to render
 * @param context - Optional SSR context for teleports and additional data
 * @returns Web ReadableStream containing the rendered HTML
 * @throws Error if ReadableStream constructor is not available in global scope
 */
function renderToWebStream(
  input: App | VNode,
  context?: SSRContext
): ReadableStream;

Usage Examples:

import { createSSRApp } from "vue";
import { renderToWebStream } from "@vue/server-renderer";

const app = createSSRApp({
  template: `
    <div>
      <h1>Web Streams</h1>
      <p>Content streamed via Web ReadableStream</p>
    </div>
  `,
});

// Direct usage with Web Response
const stream = renderToWebStream(app);
const response = new Response(stream, {
  headers: { 'Content-Type': 'text/html' }
});

// CloudFlare Workers example
export default {
  fetch(request: Request): Response {
    const vueApp = createSSRApp({
      template: `<div>Hello from CloudFlare Workers!</div>`
    });
    
    const stream = renderToWebStream(vueApp);
    return new Response(stream, {
      headers: {
        'Content-Type': 'text/html',
        'Cache-Control': 'public, max-age=300'
      }
    });
  }
};

// Vercel Edge Function example
export const config = { runtime: 'edge' };

export default function handler(request: Request): Response {
  const vueApp = createSSRApp({
    template: `<div>Hello from Vercel Edge!</div>`
  });
  
  return new Response(renderToWebStream(vueApp), {
    headers: { 'Content-Type': 'text/html' }
  });
}

pipeToWebWritable

Pipes the rendered output directly to an existing Web WritableStream. This provides more control over the destination and allows for custom stream processing.

/**
 * Render and pipe to an existing Web WritableStream instance
 * @param input - Vue application instance or VNode to render
 * @param context - Optional SSR context for teleports and additional data
 * @param writable - Web WritableStream to pipe the output to
 */
function pipeToWebWritable(
  input: App | VNode,
  context?: SSRContext,
  writable: WritableStream
): void;

Usage Examples:

import { createSSRApp } from "vue";
import { pipeToWebWritable } from "@vue/server-renderer";

const app = createSSRApp({
  template: `
    <div>
      <h1>Piped Web Stream</h1>
      <p>This content is piped to a WritableStream</p>
    </div>
  `,
});

// Using TransformStream for processing
const { readable, writable } = new TransformStream();

// Pipe Vue content to the writable side
pipeToWebWritable(app, {}, writable);

// Process the readable side
const response = new Response(readable, {
  headers: { 'Content-Type': 'text/html' }
});

// Custom WritableStream implementation
class CustomWriter {
  constructor() {
    this.chunks = [];
  }

  write(chunk: Uint8Array): Promise<void> {
    this.chunks.push(chunk);
    return Promise.resolve();
  }

  close(): Promise<void> {
    const content = new TextDecoder().decode(
      new Uint8Array(this.chunks.flat())
    );
    console.log('Final content:', content);
    return Promise.resolve();
  }

  abort(reason: any): Promise<void> {
    console.error('Stream aborted:', reason);
    return Promise.resolve();
  }
}

const customWritable = new WritableStream(new CustomWriter());
pipeToWebWritable(app, {}, customWritable);

SSR Context with Web Streams

Both functions support SSR context for teleports and custom data:

import { createSSRApp } from "vue";
import { renderToWebStream } from "@vue/server-renderer";

const app = createSSRApp({
  template: `
    <div>
      <h1>Main Content</h1>
      <Teleport to="#modal">
        <div class="modal">Modal content</div>
      </Teleport>
    </div>
  `,
});

// CloudFlare Workers with teleports
export default {
  async fetch(request: Request): Promise<Response> {
    const context = {
      url: request.url,
      userAgent: request.headers.get('User-Agent'),
    };

    const { readable, writable } = new TransformStream();
    
    // Start rendering (non-blocking)
    pipeToWebWritable(app, context, writable);
    
    // Create full HTML response with teleports
    const reader = readable.getReader();
    const encoder = new TextEncoder();
    
    return new Response(
      new ReadableStream({
        async start(controller) {
          // Send HTML document start
          controller.enqueue(encoder.encode('<!DOCTYPE html><html><body>'));
          
          // Stream main content
          try {
            while (true) {
              const { done, value } = await reader.read();
              if (done) break;
              controller.enqueue(value);
            }
            
            // Add teleported content
            if (context.teleports) {
              for (const [target, content] of Object.entries(context.teleports)) {
                const teleportHtml = `<div id="${target.slice(1)}">${content}</div>`;
                controller.enqueue(encoder.encode(teleportHtml));
              }
            }
            
            // Close HTML document
            controller.enqueue(encoder.encode('</body></html>'));
            controller.close();
          } catch (error) {
            controller.error(error);
          }
        }
      }),
      {
        headers: { 'Content-Type': 'text/html' }
      }
    );
  }
};

Platform Integration Examples

CloudFlare Workers

// worker.ts
import { createSSRApp } from "vue";
import { renderToWebStream } from "@vue/server-renderer";

export default {
  async fetch(request: Request, env: any, ctx: any): Promise<Response> {
    // Parse URL for routing
    const url = new URL(request.url);
    
    const app = createSSRApp({
      setup() {
        return { path: url.pathname };
      },
      template: `
        <div>
          <h1>CloudFlare Workers + Vue SSR</h1>
          <p>Current path: {{ path }}</p>
          <p>Rendered at: ${new Date().toISOString()}</p>
        </div>
      `
    });

    const stream = renderToWebStream(app, {
      requestUrl: request.url,
      timestamp: Date.now()
    });

    return new Response(stream, {
      headers: {
        'Content-Type': 'text/html',
        'X-Powered-By': 'CloudFlare Workers + Vue'
      }
    });
  }
};

Vercel Edge Functions

// pages/api/ssr.ts
import { createSSRApp } from "vue";
import { renderToWebStream } from "@vue/server-renderer";

export const config = {
  runtime: 'edge',
};

export default async function handler(request: Request): Promise<Response> {
  const { searchParams } = new URL(request.url);
  const name = searchParams.get('name') || 'World';

  const app = createSSRApp({
    setup() {
      return { name };
    },
    template: `
      <div>
        <h1>Hello {{ name }}!</h1>
        <p>Rendered with Vercel Edge Functions</p>
      </div>
    `
  });

  const stream = renderToWebStream(app);
  
  return new Response(stream, {
    headers: {
      'Content-Type': 'text/html',
      'Cache-Control': 'public, s-maxage=60'
    }
  });
}

Deno Deploy

// main.ts
import { createSSRApp } from "vue";
import { renderToWebStream } from "@vue/server-renderer";

Deno.serve(async (request: Request): Promise<Response> => {
  const app = createSSRApp({
    template: `
      <div>
        <h1>Deno Deploy + Vue SSR</h1>
        <p>Fast edge rendering with Deno</p>
      </div>
    `
  });

  const stream = renderToWebStream(app);
  
  return new Response(stream, {
    headers: {
      'Content-Type': 'text/html',
      'X-Powered-By': 'Deno Deploy'
    }
  });
});

Advanced Usage

Stream Processing with TransformStream

import { renderToWebStream } from "@vue/server-renderer";

class HTMLWrapperTransform {
  constructor() {
    this.headerSent = false;
  }

  transform(chunk: Uint8Array, controller: TransformStreamDefaultController) {
    if (!this.headerSent) {
      // Add HTML document wrapper
      const header = new TextEncoder().encode(
        '<!DOCTYPE html><html><head><title>My App</title></head><body>'
      );
      controller.enqueue(header);
      this.headerSent = true;
    }
    
    controller.enqueue(chunk);
  }

  flush(controller: TransformStreamDefaultController) {
    // Close HTML document
    const footer = new TextEncoder().encode('</body></html>');
    controller.enqueue(footer);
  }
}

const app = createSSRApp(/* your app */);
const vueStream = renderToWebStream(app);

const { readable, writable } = new TransformStream(new HTMLWrapperTransform());

// Pipe Vue stream through transformer
vueStream.pipeTo(writable);

// Return processed stream
return new Response(readable, {
  headers: { 'Content-Type': 'text/html' }
});

Error Handling

import { renderToWebStream } from "@vue/server-renderer";

export default {
  async fetch(request: Request): Promise<Response> {
    try {
      const app = createSSRApp({
        setup() {
          // Simulate potential error
          const shouldError = new URL(request.url).searchParams.has('error');
          if (shouldError) {
            throw new Error('Component error');
          }
          return {};
        },
        template: '<div>Success!</div>'
      });

      const stream = renderToWebStream(app);
      return new Response(stream, {
        headers: { 'Content-Type': 'text/html' }
      });
    } catch (error) {
      // Return error page
      const errorStream = renderToWebStream(createSSRApp({
        template: `<div>Error: ${error.message}</div>`
      }));
      
      return new Response(errorStream, {
        status: 500,
        headers: { 'Content-Type': 'text/html' }
      });
    }
  }
};

Performance Monitoring

import { renderToWebStream } from "@vue/server-renderer";

class PerformanceMonitoringTransform {
  constructor() {
    this.startTime = Date.now();
    this.chunkCount = 0;
  }

  transform(chunk: Uint8Array, controller: TransformStreamDefaultController) {
    this.chunkCount++;
    
    if (this.chunkCount === 1) {
      const ttfb = Date.now() - this.startTime;
      console.log(`Time to first byte: ${ttfb}ms`);
    }
    
    controller.enqueue(chunk);
  }

  flush(controller: TransformStreamDefaultController) {
    const totalTime = Date.now() - this.startTime;
    console.log(`Total render time: ${totalTime}ms, chunks: ${this.chunkCount}`);
  }
}

const app = createSSRApp(/* your app */);
const vueStream = renderToWebStream(app);
const { readable, writable } = new TransformStream(new PerformanceMonitoringTransform());

vueStream.pipeTo(writable);
return new Response(readable);

Environment Considerations

Browser Compatibility

Web Streams are supported in:

  • Modern browsers (Chrome 78+, Firefox 102+, Safari 14.1+)
  • Edge computing platforms (CloudFlare Workers, Vercel Edge, Deno Deploy)
  • Node.js 18+ (with --experimental-web-streams flag)

Memory Efficiency

Web Streams provide excellent memory characteristics:

  • Backpressure handling prevents memory bloat
  • Automatic garbage collection of processed chunks
  • Suitable for large responses and constrained environments

Error Recovery

Web Streams include robust error handling:

  • Stream-level error propagation
  • Graceful degradation options
  • Timeout and cancellation support

Install with Tessl CLI

npx tessl i tessl/npm-vue--server-renderer

docs

index.md

internal-helpers.md

nodejs-integration.md

streaming-rendering.md

string-rendering.md

web-streams.md

tile.json