Skip to content
HomePagesHomePages template kit
Menu

template-kit/typecheck

The template-kit/typecheck authoring rule — what it enforces, why, and how to fix it.

The template must compile. Every type error is one violation.

The template’s TypeScript must compile cleanly. The check resolves typescript from your workspace’s node_modules — the compiler that runs is the one you installed, at the version you pinned, against your own tsconfig.json.

If typescript is not installed, that is itself the diagnostic (with the fix), not a crash.

The schema system is the section contract, and it is enforced through the type system. SectionProps<typeof schema> is not a convenience — it is the mechanism that keeps schema.ts and Renderer.tsx in agreement: a slot you add to the schema and forget to render, a slot you render under the wrong name, an optional slot you treat as always present, a slot whose type you assume wrong. Each of those is a compile error rather than a surprise on a stranger’s published page.

A template that does not typecheck has opted out of the only thing checking that the two files still describe the same section.

Fix the type error. If the compiler itself is missing, install it and point your tsconfig.json at the kit’s base config, which sets the module resolution and JSX settings the kit’s types expect:

tsconfig.json
{ "extends": "@homepages/template-kit/tsconfig", "include": ["sections", "theme.ts"] }
Terminal window
npm install --save-dev typescript

year_built is declared required: false, so SectionProps resolves it to string | null — and the Renderer treats it as a string:

sections/facts/Renderer.tsx
import { Section, Slot } from "@homepages/template-kit";
import type { Props } from "./schema.js";
export function Renderer({ slots }: Props) {
return (
<Section>
{/* error TS18047: 'slots.year_built' is possibly 'null'. */}
<Slot id="year_built" textLeaf>
<p>Built {slots.year_built.slice(0, 4)}</p>
</Slot>
</Section>
);
}

That is not a pedantic complaint. required: false means a thin listing really does arrive with no year, and this line really does throw on it.

sections/facts/Renderer.tsx
import { Section, Slot } from "@homepages/template-kit";
import type { Props } from "./schema.js";
export function Renderer({ slots }: Props) {
return (
<Section>
{slots.year_built ? (
<Slot id="year_built" textLeaf>
<p>Built {slots.year_built.slice(0, 4)}</p>
</Slot>
) : null}
</Section>
);
}

If the slot should never be absent, the other correct fix is in the schema — declare it required: true, and the type narrows to string everywhere.

  • The schema system — how SectionProps resolves each slot to its runtime type.
  • props-from-schema — the rule that stops a hand-written Props from disabling this inference in the first place.
  • render-invariant — the same class of bug caught at render time, for the cases the types cannot see.