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

nodejs-integration.mddocs/

Node.js Integration

Native Node.js stream integration for traditional server environments and frameworks. These functions provide seamless integration with Node.js's built-in stream system, making it easy to integrate Vue SSR into existing Node.js applications and frameworks.

Capabilities

renderToNodeStream

Creates a Node.js Readable stream that outputs the rendered HTML. This function is only available in the CommonJS build and provides direct integration with Node.js stream ecosystem.

/**
 * Renders input as a Node.js Readable stream
 * @param input - Vue application instance or VNode to render
 * @param context - Optional SSR context for teleports and additional data
 * @returns Node.js Readable stream containing the rendered HTML
 * @throws Error if used in ESM build - use pipeToNodeWritable instead
 */
function renderToNodeStream(
  input: App | VNode,
  context?: SSRContext
): Readable;

Usage Examples:

import { createSSRApp } from "vue";
import { renderToNodeStream } from "@vue/server-renderer";
import { pipeline } from "stream";

const app = createSSRApp({
  template: `
    <div>
      <h1>Node.js Streaming</h1>
      <p>Content streamed via Node.js Readable</p>
    </div>
  `,
});

// Direct streaming to HTTP response
import express from "express";
const server = express();

server.get("/", (req, res) => {
  res.setHeader('Content-Type', 'text/html');
  
  const stream = renderToNodeStream(app);
  stream.pipe(res);
  
  // Handle errors
  stream.on('error', (err) => {
    console.error('Streaming error:', err);
    res.status(500).end('Internal Server Error');
  });
});

// Pipeline with transforms
import { Transform } from "stream";

const htmlWrapper = new Transform({
  objectMode: false,
  transform(chunk, encoding, callback) {
    // Wrap content in HTML document
    if (!this.headerSent) {
      this.push('<!DOCTYPE html><html><head><title>My App</title></head><body>');
      this.headerSent = true;
    }
    this.push(chunk);
    callback();
  },
  flush(callback) {
    this.push('</body></html>');
    callback();
  }
});

pipeline(
  renderToNodeStream(app),
  htmlWrapper,
  process.stdout,
  (err) => {
    if (err) console.error('Pipeline error:', err);
    else console.log('Pipeline complete');
  }
);

renderToStream (deprecated)

Legacy function that creates a Node.js Readable stream. This function is deprecated and should be replaced with renderToNodeStream.

/**
 * @deprecated Use renderToNodeStream instead
 * Renders input as a Node.js Readable stream  
 * @param input - Vue application instance or VNode to render
 * @param context - Optional SSR context for teleports and additional data
 * @returns Node.js Readable stream containing the rendered HTML
 */
function renderToStream(
  input: App | VNode,
  context?: SSRContext
): Readable;

Migration:

// Old (deprecated)
const stream = renderToStream(app, context);

// New (recommended)
const stream = renderToNodeStream(app, context);

pipeToNodeWritable

Pipes the rendered output directly to an existing Node.js Writable stream. This function works in both CommonJS and ESM builds and provides more control over the destination.

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

Usage Examples:

import { createSSRApp } from "vue";
import { pipeToNodeWritable } from "@vue/server-renderer";
import { createWriteStream } from "fs";
import { Transform } from "stream";

const app = createSSRApp({
  template: `
    <div>
      <h1>Piped Content</h1>
      <p>This is piped to a writable stream</p>
    </div>
  `,
});

// Pipe to file
const fileStream = createWriteStream('output.html');
pipeToNodeWritable(app, {}, fileStream);

// Pipe to HTTP response
import express from "express";
const server = express();

server.get("/", (req, res) => {
  res.setHeader('Content-Type', 'text/html');
  pipeToNodeWritable(app, {}, res);
});

// Pipe through transform streams
const compressionStream = new Transform({
  transform(chunk, encoding, callback) {
    // Add compression or other transformations
    const compressed = this.compress(chunk);
    callback(null, compressed);
  }
});

pipeToNodeWritable(app, {}, compressionStream);
compressionStream.pipe(process.stdout);

SSR Context with Node.js Streams

Both functions support SSR context for handling teleports and passing data:

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

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

const context = {
  userAgent: req.headers['user-agent'],
  requestId: generateRequestId(),
};

// Custom writable that handles teleports
class TeleportAwareWritable extends Writable {
  constructor(private response: Response) {
    super();
    this.chunks = [];
  }

  _write(chunk, encoding, callback) {
    this.chunks.push(chunk);
    callback();
  }

  _final(callback) {
    // Combine main content with teleports
    const mainContent = Buffer.concat(this.chunks).toString();
    
    let fullHtml = '<!DOCTYPE html><html><body>';
    fullHtml += mainContent;
    
    // Add teleported content
    if (context.teleports) {
      for (const [target, content] of Object.entries(context.teleports)) {
        fullHtml += `<div id="${target.slice(1)}">${content}</div>`;
      }
    }
    
    fullHtml += '</body></html>';
    
    this.response.end(fullHtml);
    callback();
  }
}

const writable = new TeleportAwareWritable(res);
pipeToNodeWritable(app, context, writable);

Framework Integration Examples

Express.js Integration

import express from "express";
import { createSSRApp } from "vue";
import { renderToNodeStream, pipeToNodeWritable } from "@vue/server-renderer";

const app = express();

// Using renderToNodeStream
app.get("/stream", (req, res) => {
  const vueApp = createSSRApp({
    template: `<div>Hello from Express + Vue SSR!</div>`
  });
  
  res.setHeader('Content-Type', 'text/html');
  const stream = renderToNodeStream(vueApp);
  
  stream.on('error', (err) => {
    console.error('SSR Error:', err);
    res.status(500).end('Server Error');
  });
  
  stream.pipe(res);
});

// Using pipeToNodeWritable (preferred for ESM)
app.get("/pipe", (req, res) => {
  const vueApp = createSSRApp({
    template: `<div>Hello from Express + Vue SSR (piped)!</div>`
  });
  
  res.setHeader('Content-Type', 'text/html');
  pipeToNodeWritable(vueApp, {}, res);
});

Fastify Integration

import Fastify from "fastify";
import { createSSRApp } from "vue";
import { pipeToNodeWritable } from "@vue/server-renderer";

const fastify = Fastify({ logger: true });

fastify.get("/", async (request, reply) => {
  const vueApp = createSSRApp({
    template: `<div>Hello from Fastify + Vue SSR!</div>`
  });
  
  reply.type('text/html');
  pipeToNodeWritable(vueApp, {}, reply.raw);
});

Koa Integration

import Koa from "koa";
import { createSSRApp } from "vue";
import { pipeToNodeWritable } from "@vue/server-renderer";

const app = new Koa();

app.use(async (ctx) => {
  const vueApp = createSSRApp({
    template: `<div>Hello from Koa + Vue SSR!</div>`
  });
  
  ctx.type = 'text/html';
  pipeToNodeWritable(vueApp, {}, ctx.res);
});

Advanced Usage

Custom Error Handling

import { pipeToNodeWritable } from "@vue/server-renderer";
import { Writable } from "stream";

class ErrorHandlingWritable extends Writable {
  constructor(private response: Response) {
    super();
  }

  _write(chunk, encoding, callback) {
    try {
      this.response.write(chunk);
      callback();
    } catch (err) {
      callback(err);
    }
  }

  _final(callback) {
    this.response.end();
    callback();
  }
}

const writable = new ErrorHandlingWritable(res);
writable.on('error', (err) => {
  console.error('Streaming error:', err);
  if (!res.headersSent) {
    res.status(500).end('Internal Server Error');
  }
});

pipeToNodeWritable(app, {}, writable);

Performance Monitoring

import { performance } from "perf_hooks";
import { pipeToNodeWritable } from "@vue/server-renderer";

const startTime = performance.now();
let firstChunkTime: number;
let chunkCount = 0;

const monitoringWritable = new Writable({
  write(chunk, encoding, callback) {
    if (chunkCount === 0) {
      firstChunkTime = performance.now();
      console.log(`Time to first chunk: ${firstChunkTime - startTime}ms`);
    }
    
    chunkCount++;
    this.push(chunk);
    callback();
  },
  final(callback) {
    const endTime = performance.now();
    console.log(`Total render time: ${endTime - startTime}ms`);
    console.log(`Total chunks: ${chunkCount}`);
    callback();
  }
});

pipeToNodeWritable(app, {}, monitoringWritable);

Environment Considerations

CommonJS vs ESM

  • renderToNodeStream: Only available in CommonJS build
  • pipeToNodeWritable: Available in both CommonJS and ESM builds
  • Recommendation: Use pipeToNodeWritable for maximum compatibility

Memory Usage

Node.js streaming provides excellent memory efficiency:

  • Content is processed and sent immediately
  • No buffering of complete HTML in memory
  • Suitable for large applications with complex component trees

Error Recovery

Node.js streams provide robust error handling mechanisms:

  • Use stream error events for graceful degradation
  • Implement timeout handling for slow-rendering components
  • Consider circuit breaker patterns for problematic components

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