ProvenPin Install

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

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.