Server-side rendering library for Vue.js applications with support for streaming and multiple environments
—
Progressive rendering capabilities that allow sending HTML to the client as it's generated, providing better perceived performance and user experience. Streaming is ideal for larger applications where you want to show content immediately rather than waiting for the complete page to render.
Core streaming function that renders to a simple readable interface. This is the foundation for all other streaming functions and allows custom stream implementations.
/**
* Renders input in streaming mode using a simple readable interface
* @param input - Vue application instance or VNode to render
* @param context - SSR context for teleports and additional data
* @param stream - Stream implementation with push and destroy methods
* @returns The same stream instance passed in
*/
function renderToSimpleStream<T extends SimpleReadable>(
input: App | VNode,
context: SSRContext,
stream: T
): T;
interface SimpleReadable {
/** Push a chunk of content to the stream, null indicates end */
push(chunk: string | null): void;
/** Handle errors and cleanup */
destroy(err: any): void;
}Usage Examples:
import { createSSRApp } from "vue";
import { renderToSimpleStream } from "@vue/server-renderer";
const app = createSSRApp({
template: `
<div>
<h1>Streaming Content</h1>
<p>This content is sent progressively</p>
</div>
`,
});
// Create a simple stream collector
let result = '';
const context = {};
const stream = renderToSimpleStream(app, context, {
push(chunk) {
if (chunk === null) {
// Rendering complete
console.log('Final result:', result);
console.log('Teleports:', context.teleports);
} else {
// Append chunk to result
result += chunk;
console.log('Received chunk:', chunk);
}
},
destroy(err) {
console.error('Stream error:', err);
},
});You can implement custom stream handling for specific use cases:
import { renderToSimpleStream } from "@vue/server-renderer";
class CustomResponseStream {
constructor(private response: Response) {}
push(chunk: string | null): void {
if (chunk === null) {
// Complete the response
this.response.end();
} else {
// Write chunk to response
this.response.write(chunk);
}
}
destroy(err: any): void {
console.error('Streaming error:', err);
this.response.status(500).end('Internal Server Error');
}
}
// Usage with custom stream
const app = createSSRApp(/* your app */);
const response = getHttpResponse(); // Your HTTP response object
const stream = new CustomResponseStream(response);
renderToSimpleStream(app, {}, stream);Streaming automatically handles async components and renders them as they resolve:
import { createSSRApp, defineAsyncComponent } from "vue";
import { renderToSimpleStream } from "@vue/server-renderer";
const AsyncComponent = defineAsyncComponent(() =>
new Promise(resolve => {
setTimeout(() => {
resolve({
template: '<div>Async content loaded!</div>'
});
}, 1000);
})
);
const app = createSSRApp({
components: { AsyncComponent },
template: `
<div>
<h1>Initial Content</h1>
<AsyncComponent />
<p>More content</p>
</div>
`,
});
const chunks: string[] = [];
renderToSimpleStream(app, {}, {
push(chunk) {
if (chunk === null) {
console.log('Streaming complete');
console.log('All chunks:', chunks);
} else {
console.log('Chunk received:', chunk);
chunks.push(chunk);
}
},
destroy(err) {
console.error('Error:', err);
},
});
// Output will show chunks being received as async components resolveStreaming includes built-in error handling and cleanup:
import { createSSRApp } from "vue";
import { renderToSimpleStream } from "@vue/server-renderer";
const app = createSSRApp({
setup() {
// Simulate an error during rendering
throw new Error("Component rendering failed");
},
template: `<div>This won't be reached</div>`,
});
renderToSimpleStream(app, {}, {
push(chunk) {
console.log('Received:', chunk);
},
destroy(err) {
// This will be called with the error
console.error('Streaming failed:', err.message);
// Handle error - send error page, log, etc.
},
});Streaming provides several performance advantages:
import { renderToSimpleStream } from "@vue/server-renderer";
// Always handle both success and error cases
renderToSimpleStream(app, context, {
push(chunk) {
if (chunk === null) {
// Always handle completion
this.finalize();
} else {
// Process chunk immediately
this.writeChunk(chunk);
}
},
destroy(err) {
// Always handle errors gracefully
this.handleError(err);
},
});
// Consider teleport handling
renderToSimpleStream(app, context, {
push(chunk) {
if (chunk === null) {
// Process teleports after main content
if (context.teleports) {
for (const [target, content] of Object.entries(context.teleports)) {
this.insertTeleport(target, content);
}
}
} else {
this.writeChunk(chunk);
}
},
destroy: this.handleError.bind(this),
});Streaming works well with most web frameworks:
// Express.js example pattern
app.get('/stream', (req, res) => {
const vueApp = createSSRApp(/* your app */);
renderToSimpleStream(vueApp, {}, {
push(chunk) {
if (chunk === null) {
res.end();
} else {
res.write(chunk);
}
},
destroy(err) {
res.status(500).end('Error occurred');
},
});
});When implementing caching with streaming, consider chunk-level caching:
const cache = new Map();
renderToSimpleStream(app, context, {
push(chunk) {
if (chunk !== null) {
// Cache chunks for potential reuse
cache.set(this.chunkId++, chunk);
}
},
destroy: this.handleError,
});Install with Tessl CLI
npx tessl i tessl/npm-vue--server-renderer