Vue 3 SSR with renderToString: working versions (tested 2026-10-17)
Server-render a Vue 3 app built with defineComponent and h(), covering props, computed, list rendering and provide/inject, then assert the HTML output offline.
passed3 passed · 0 failed2026-10-17 19:19 UTC- Status
- passed
- Last passed
- 2026-10-17 19:19 UTC
- Tests
- 3 / 3
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions4
vue@3.5.44@vue/server-renderer@3.5.44@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 39061946720
The code
Exactly the files that passed. Your agent can pull them with get_recipe("vue-server-renderer-ssr") (install), or see them on GitHub.
package.json
{
"name": "vue-server-renderer-ssr",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"vue": "3.5.44",
"@vue/server-renderer": "3.5.44"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
src/app.ts
import {
computed,
createSSRApp,
defineComponent,
h,
inject,
provide,
type InjectionKey,
type PropType,
} from "vue";
import { renderToString } from "@vue/server-renderer";
export type Task = { id: string; label: string; done: boolean };
export const PrefixKey: InjectionKey<string> = Symbol("prefix");
export const ItemRow = defineComponent({
name: "ItemRow",
props: {
label: { type: String, required: true },
done: { type: Boolean, default: false },
},
setup(props) {
const prefix = inject(PrefixKey, "");
const text = computed(() => `${prefix}${props.label}`);
return () => {
const children = [h("span", { class: "label" }, text.value)];
if (props.done) {
children.push(h("span", { class: "check" }, " done"));
}
return h("li", { class: props.done ? "done" : "todo" }, children);
};
},
});
export const TaskList = defineComponent({
name: "TaskList",
props: {
title: { type: String, default: "Tasks" },
tasks: {
type: Array as PropType<Task[]>,
default: () => [] as Task[],
},
},
setup(props) {
provide(PrefixKey, "- ");
const remaining = computed(() => props.tasks.filter((task) => !task.done).length);
return () =>
h("section", { class: "task-list" }, [
h("h1", null, props.title),
h(
"ul",
null,
props.tasks.map((task) =>
h(ItemRow, { key: task.id, label: task.label, done: task.done })
)
),
h("p", { class: "remaining" }, `${remaining.value} remaining`),
]);
},
});
export function renderTaskList(options: { title?: string; tasks: Task[] }): Promise<string> {
const app = createSSRApp({
render: () => h(TaskList, { title: options.title, tasks: options.tasks }),
});
return renderToString(app);
}
test/app.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { renderTaskList } from "../src/app.ts";
test("renders title and v-for style list via h()", async () => {
const html = await renderTaskList({
title: "Groceries",
tasks: [
{ id: "1", label: "Milk", done: false },
{ id: "2", label: "Bread", done: true },
],
});
assert.ok(html.includes('<section class="task-list">'));
assert.ok(html.includes("<h1>Groceries</h1>"));
assert.ok(html.includes('<li class="todo"><span class="label">- Milk</span></li>'));
assert.ok(
html.includes(
'<li class="done"><span class="label">- Bread</span><span class="check"> done</span></li>'
)
);
});
test("provide/inject prefix reaches child components", async () => {
const html = await renderTaskList({
title: "Misc",
tasks: [{ id: "9", label: "Water", done: false }],
});
assert.ok(html.includes('<span class="label">- Water</span>'));
});
test("computed remaining count reflects props", async () => {
const html = await renderTaskList({
title: "Work",
tasks: [
{ id: "a", label: "Spec", done: true },
{ id: "b", label: "Code", done: true },
],
});
assert.ok(html.includes('<p class="remaining">0 remaining</p>'));
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.