RenderComponent
Use renderComponent() to server-render React components outside the page router — for emails, PDFs, API responses, scheduled jobs, or any context where you need HTML without a full HTTP request/response cycle.
Overview
renderComponent() is a server-only function that renders any React component to an HTML string with full SSR support — hooks, async components, layouts, and all. Unlike the page router (which ties rendering to HTTP routes), renderComponent()runs standalone: call it from API routes, cron jobs, CLI scripts, or anywhere you need server-rendered markup programmatically.
renderComponent is a server-only API. Import it from 'nukejs/server', not 'nukejs', to avoid pulling Node.js built-ins into client bundles.Basic usage
import { renderComponent } from 'nukejs/server'
import WelcomeEmail from '../app/emails/Welcome'
const html = await renderComponent(WelcomeEmail, {
name: 'Alice',
activationLink: 'https://example.com/activate?token=xyz'
})
console.log(html) // Full HTML document stringSignature
renderComponent(
Component: React.ComponentType<any>,
props?: Record<string, any>,
options?: RenderComponentOptions
): Promise<string>Options
| Option | Type | Description |
|---|---|---|
| layouts | React.ComponentType[] | Array of layout components to wrap the main component. Applied innermost-first. |
| url | string | URL pathname for the request context (default: '/'). Used by useRequest(). |
| params | Record<string, string | string[]> | Route params exposed via useRequest().params |
| query | Record<string, string | string[]> | Query string params exposed via useRequest().query |
| headers | Record<string, string> | Request headers exposed via useRequest().headers |
| title | string | Default document title (default: 'NukeJS') |
| isDev | boolean | Development mode flag. Auto-detected from NODE_ENV if not set. |
Example: Email templates
Render transactional email HTML using React components. Share styles and layout components across all your emails.
import { useHtml } from 'nukejs'
export default function WelcomeEmail({ name, activationLink }: {
name: string
activationLink: string
}) {
useHtml({ title: 'Welcome!' })
return (
<div style={{ fontFamily: 'sans-serif', padding: '20px' }}>
<h1>Welcome, {name}!</h1>
<p>Thank you for signing up. Please activate your account:</p>
<a
href={activationLink}
style={{
display: 'inline-block',
padding: '10px 20px',
background: '#0070f3',
color: 'white',
textDecoration: 'none',
borderRadius: '4px'
}}
>
Activate Account
</a>
<p style={{ marginTop: '20px', fontSize: '12px', color: '#666' }}>
If you didn't sign up, ignore this email.
</p>
</div>
)
}import { renderComponent } from 'nukejs/server'
import nodemailer from 'nodemailer'
import WelcomeEmail from '../app/emails/Welcome'
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
})
export async function sendWelcomeEmail(to: string, name: string, token: string) {
const activationLink = `https://example.com/activate?token=${token}`
const html = await renderComponent(WelcomeEmail, {
name,
activationLink
})
await transporter.sendMail({
from: 'noreply@example.com',
to,
subject: 'Welcome to Our App',
html,
})
}Example: PDF generation
Combine renderComponent() with a headless browser to generate PDFs from React components:
export default function InvoiceTemplate({ invoice }: { invoice: any }) {
return (
<div style={{ padding: '40px', fontFamily: 'Arial, sans-serif' }}>
<h1>Invoice #{invoice.number}</h1>
<p>Date: {new Date(invoice.date).toLocaleDateString()}</p>
<hr />
<h2>Items</h2>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ borderBottom: '2px solid #000' }}>
<th style={{ textAlign: 'left' }}>Description</th>
<th style={{ textAlign: 'right' }}>Amount</th>
</tr>
</thead>
<tbody>
{invoice.items.map((item: any, i: number) => (
<tr key={i}>
<td>{item.description}</td>
<td style={{ textAlign: 'right' }}>${item.amount.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
<hr />
<p style={{ textAlign: 'right', fontSize: '18px', fontWeight: 'bold' }}>
Total: ${invoice.total.toFixed(2)}
</p>
</div>
)
}import { renderComponent } from 'nukejs/server'
import puppeteer from 'puppeteer'
import InvoiceTemplate from '../app/templates/Invoice'
export async function generateInvoicePDF(invoice: any): Promise<Buffer> {
// Render component to HTML
const html = await renderComponent(InvoiceTemplate, { invoice })
// Launch headless browser
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setContent(html, { waitUntil: 'networkidle0' })
// Generate PDF
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
})
await browser.close()
return pdf
}Example: API route returning HTML
Render dynamic HTML responses in API routes without going through the page router:
import type { IncomingMessage, ServerResponse } from 'http'
import { renderComponent } from 'nukejs/server'
import BlogPost from '../../app/components/BlogPost'
import { getPostById } from '../../lib/db'
export async function GET(
req: IncomingMessage & { params: any },
res: ServerResponse
) {
const postId = parseInt(req.params.id as string)
const post = await getPostById(postId)
if (!post) {
res.writeHead(404, { 'Content-Type': 'text/plain' })
res.end('Not found')
return
}
// Render the component with custom headers
const html = await renderComponent(BlogPost,
{ post },
{
url: `/preview/${postId}`,
headers: { 'x-preview': 'true' },
title: post.title,
}
)
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(html)
}Using layouts
Wrap your component in one or more layout components to share structure, styles, or context providers:
export default function EmailLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<head>
<meta charSet="utf-8" />
<style>{`
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
`}</style>
</head>
<body>
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
{children}
</div>
</body>
</html>
)
}import { renderComponent } from 'nukejs/server'
import EmailLayout from '../app/layouts/EmailLayout'
import Newsletter from '../app/emails/Newsletter'
const html = await renderComponent(
Newsletter,
{ articles: latestArticles },
{
layouts: [EmailLayout], // Wraps Newsletter in EmailLayout
title: 'Weekly Newsletter'
}
)
// html is a complete <!DOCTYPE html> document with the layout appliedRequest context with useRequest()
Components rendered via renderComponent() can use useRequest()to access the synthetic request context you provide:
import { useRequest } from 'nukejs'
export default function Report() {
const { query, headers } = useRequest()
const format = query.format as string ?? 'summary' // From options.query
const locale = headers['accept-language'] ?? 'en' // From options.headers
return (
<div>
<p>Report format: {format}</p>
<p>Locale: {locale}</p>
</div>
)
}import { renderComponent } from 'nukejs/server'
import Report from '../app/components/Report'
const html = await renderComponent(Report, {}, {
url: '/reports/monthly',
query: { format: 'detailed' },
headers: { 'accept-language': 'fr-FR' },
})
// The Report component sees query.format === 'detailed'
// and headers['accept-language'] === 'fr-FR'When to use renderComponent()
| Use case | Why |
|---|---|
| Email templates | Reuse React components for transactional and marketing emails |
| PDF generation | Render invoices, reports, certificates as HTML → PDF |
| API routes returning HTML | Preview endpoints, embeds, or dynamic snippets |
| Static site generation | Pre-render pages in a build script |
| Scheduled jobs | Generate and send HTML summaries on a cron schedule |
| Testing | Render components in tests to verify output |
renderComponent() is pure SSR output. It does not include the hydration script or client bundle. If you need interactive components, use the page router instead.TypeScript
import { renderComponent } from 'nukejs/server'
import type { RenderComponentOptions } from 'nukejs/server'
// Type-safe options
const options: RenderComponentOptions = {
url: '/foo',
query: { bar: 'baz' },
headers: { 'x-custom': 'value' },
}
const html = await renderComponent(MyComponent, { prop: 'value' }, options)