Server-Side Rendering (SSR)
Blaze-ng can render templates to HTML strings on the server for fast initial page loads, SEO, and email generation.
Basic SSR
Every template and view can be rendered to an HTML string:
ts
import { Blaze } from '@blaze-ng/core';
import { Template } from '@blaze-ng/templating-runtime';
// Render a template to HTML
const html = Blaze.toHTML(Template.myTemplate);
// Render with data
const html = Blaze.toHTMLWithData(Template.userCard, {
name: 'Alice',
email: 'alice@example.com',
avatar: '/avatars/alice.jpg',
});Rendering Compiled Templates
ts
import { SpacebarsCompiler } from '@blaze-ng/spacebars-compiler';
// Compile a template
const renderFn = SpacebarsCompiler.compile('<h1>Hello, {{name}}!</h1>');
// Create a template
const tmpl = new Template('greeting', renderFn);
// Render to HTML
const html = Blaze.toHTMLWithData(tmpl, { name: 'World' });
// => '<h1>Hello, World!</h1>'SSR Patterns
Page Shell
ts
function renderPage(templateName, data) {
const content = Blaze.toHTMLWithData(Template[templateName], data);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${data.title || 'My App'}</title>
<meta name="description" content="${data.description || ''}">
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div id="app">${content}</div>
<script src="/client.js"></script>
</body>
</html>`;
}Express Integration
ts
import express from 'express';
import { Blaze } from '@blaze-ng/core';
import { Template } from '@blaze-ng/templating-runtime';
const app = express();
app.get('/', (req, res) => {
const html = Blaze.toHTMLWithData(Template.homePage, {
title: 'Welcome',
features: getFeatures(),
});
res.send(renderShell(html, { title: 'Home' }));
});
app.get('/users/:id', async (req, res) => {
const user = await db.users.findOne(req.params.id);
if (!user) {
const html = Blaze.toHTML(Template.notFound);
return res.status(404).send(renderShell(html, { title: 'Not Found' }));
}
const html = Blaze.toHTMLWithData(Template.userProfile, { user });
res.send(
renderShell(html, {
title: `${user.name} — Profile`,
description: user.bio,
}),
);
});Meteor SSR
ts
import { WebApp } from 'meteor/webapp';
import { Blaze } from '@blaze-ng/core';
import { Template } from '@blaze-ng/templating-runtime';
WebApp.connectHandlers.use('/ssr', (req, res) => {
const html = Blaze.toHTMLWithData(Template.ssrPage, {
url: req.url,
user: req.user,
});
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
});Email Templates
SSR is perfect for generating HTML emails:
ts
function sendWelcomeEmail(user) {
const html = Blaze.toHTMLWithData(Template.welcomeEmail, {
appName: 'MyApp',
tagline: 'Build amazing things',
user,
dashboardUrl: `https://myapp.com/dashboard`,
unsubscribeUrl: `https://myapp.com/unsubscribe?token=${user.unsubToken}`,
steps: [
{ title: 'Complete your profile', description: 'Add a photo and bio' },
{ title: 'Create a project', description: 'Start building something cool' },
{ title: 'Invite your team', description: 'Collaborate in real-time' },
],
});
Email.send({
to: user.email,
from: 'hello@myapp.com',
subject: 'Welcome to MyApp!',
html,
});
}PDF Generation
Combine SSR with a PDF library:
ts
import puppeteer from 'puppeteer';
async function generateInvoicePdf(invoice) {
const html = Blaze.toHTMLWithData(Template.invoice, {
invoice,
company: getCompanyInfo(),
formatCurrency: (amount) => `$${amount.toFixed(2)}`,
});
const fullHtml = `
<!DOCTYPE html>
<html>
<head><style>${getInvoiceStyles()}</style></head>
<body>${html}</body>
</html>
`;
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(fullHtml);
const pdf = await page.pdf({ format: 'A4' });
await browser.close();
return pdf;
}Static Site Generation
Pre-render pages at build time:
ts
import { writeFileSync, mkdirSync } from 'fs';
import { Blaze } from '@blaze-ng/core';
import { Template } from '@blaze-ng/templating-runtime';
const pages = [
{ path: '/', template: 'homePage', data: { title: 'Home' } },
{ path: '/about', template: 'aboutPage', data: { title: 'About' } },
{ path: '/pricing', template: 'pricingPage', data: { title: 'Pricing', plans: getPlans() } },
];
// Generate static HTML files
for (const page of pages) {
const content = Blaze.toHTMLWithData(Template[page.template], page.data);
const html = renderShell(content, page.data);
const dir = `dist${page.path}`;
mkdirSync(dir, { recursive: true });
writeFileSync(`${dir}/index.html`, html);
}
console.log(`Generated ${pages.length} static pages`);Component Snapshots for Testing
Use SSR to test component output:
ts
import { describe, it, expect } from 'vitest';
import { Blaze } from '@blaze-ng/core';
import { Template } from '@blaze-ng/templating-runtime';
describe('UserCard', () => {
it('renders user information', () => {
const html = Blaze.toHTMLWithData(Template.userCard, {
name: 'Alice',
email: 'alice@example.com',
role: 'Admin',
});
expect(html).toContain('Alice');
expect(html).toContain('alice@example.com');
expect(html).toContain('Admin');
});
it('shows placeholder for missing avatar', () => {
const html = Blaze.toHTMLWithData(Template.userCard, {
name: 'Bob',
avatar: null,
});
expect(html).toContain('avatar-placeholder');
expect(html).not.toContain('<img');
});
it('matches snapshot', () => {
const html = Blaze.toHTMLWithData(Template.userCard, {
name: 'Test User',
email: 'test@example.com',
});
expect(html).toMatchSnapshot();
});
});Performance Tips
- Cache compiled templates — compile once, render many times
- Minimize helpers — complex helper logic runs on every render
- Use
toHTMLoverrender— no DOM needed on the server - Stream large pages — for very large pages, consider streaming chunks
- Pre-render static parts — cache parts that don't change per-request