Skip to content

Head Management

τjs provides flexible document head management through a headContent function defined in your entry-server.tsx.

entry-server.tsx
import { createRenderer } from "@taujs/react";
import { App } from "./App";
export const { renderSSR, renderStream } = createRenderer({
appComponent: ({ location }) => <App location={location} />,
headContent: ({ data, meta }) => {
const title = data.title || meta.title || "My App";
const description = data.description || meta.description || "";
return `
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<meta name="description" content="${description}">
`;
},
});

Result from your route’s attr.data handler:

// Route config
{
path: '/products/:id',
attr: {
render: 'ssr',
data: async (params) => {
const product = await db.products.findById(params.id);
return {
title: product.name,
description: product.description,
image: product.imageUrl
};
}
}
}

From your route’s attr.meta configuration:

// Route config
{
path: '/about',
attr: {
render: 'ssr',
meta: {
title: 'About Us',
description: 'Learn about our company'
}
}
}

Data is always fully resolved before headContent runs:

headContent: ({ data }) => {
// data.product is guaranteed to exist
return `
<title>${data.product.name}</title>
<meta property="og:image" content="${data.product.imageUrl}">
`;
};

Data may not be ready when headContent runs. Use meta for reliable SEO:

headContent: ({ data, meta }) => {
// Use meta for guaranteed values
return `
<title>${meta.title}</title>
<meta name="description" content="${meta.description}">
${
data.ogImage ? `<meta property="og:image" content="${data.ogImage}">` : ""
}
`;
};
headContent: ({ data, meta }) => {
const title = data.title || meta.title;
const description = data.description || meta.description;
const image = data.ogImage || meta.ogImage;
return `
<title>${title}</title>
<meta property="og:title" content="${title}">
<meta property="og:description" content="${description}">
${image ? `<meta property="og:image" content="${image}">` : ""}
`;
};
headContent: ({ data, meta }) => {
const jsonLd = data.jsonLd || meta.jsonLd;
return `
<title>${meta.title}</title>
${
jsonLd
? `<script type="application/ld+json">${JSON.stringify(
jsonLd
)}</script>`
: ""
}
`;
};
headContent: ({ data, meta }) => {
const canonical = data.canonical || meta.canonical;
return `
<title>${meta.title}</title>
${canonical ? `<link rel="canonical" href="${canonical}">` : ""}
`;
};
// ✅ Good - reliable in streaming
headContent: ({ data, meta }) => {
return `
<title>${meta.title}</title>
${
data.ogImage ? `<meta property="og:image" content="${data.ogImage}">` : ""
}
`;
};
const title = data.title || meta.title || "Default Title";
function escapeHtml(str: string): string {
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}

If your head content critically depends on fetched data, use render: 'ssr'.