Post-rendering
Post-rendering (.post.ts files) lets you transform an HTML document after the build has already inlined every component, deduplicated styles, and compiled TypeScript. It runs as the last DOM step before the file is written to web/.
Use .post.ts when you need the final, fully-resolved DOM — for example to inject metadata derived from the rendered content, normalize attributes, or perform any cleanup that depends on components being expanded.
Quick Start
Create a .post.ts file with the same base name as your .html file:
// index.post.ts
const head = document.querySelector("head");
if (head) {
const meta = document.createElement("meta");
meta.setAttribute("name", "generator");
meta.setAttribute("content", "tkeron");
head.appendChild(meta);
}
The corresponding index.html is loaded after components have been resolved, and the result is written back to the same file.
How It Works
File Pairing
Post-rendering files are paired with HTML files by name:
| Post-render File | Processes |
|---|---|
index.post.ts | index.html |
about.post.ts | about.html |
blog/post.post.ts | blog/post.html |
The document Variable
Every .post.ts file has access to a document variable — the fully-resolved DOM of the corresponding HTML:
document.querySelector("title");
document.getElementById("content");
document.querySelectorAll("article");
document.body;
All <my-component> tags have already been replaced, and duplicated component <style> blocks have already been deduplicated.
Build Process Order
1. Run .pre.ts → Modify HTML before components run
2. Iterative components → .com.ts / .com.html / .com.md (up to 10 iterations)
3. Style deduplication → One <style data-tk-com> per component
4. Run .post.ts → Final DOM transformation
5. Compile TypeScript → .ts → .js
6. Copy to output → web/
.pre.ts vs .post.ts
.pre.ts | .post.ts | |
|---|---|---|
| Runs | Before components | After components & style dedup |
document contains | Raw page + component tags | Fully-inlined HTML |
| Can inject components? | ✅ Yes — they'll be processed next | ❌ No — would not be expanded |
| Typical use | API fetching, dynamic markup | Final metadata, cleanup, analysis |
Examples
Inject Final Build Metadata
// index.post.ts
const meta = document.createElement("meta");
meta.setAttribute("name", "build-time");
meta.setAttribute("content", new Date().toISOString());
document.querySelector("head")?.appendChild(meta);
Auto-link Headings
Because components are already expanded, you can safely scan the entire final document:
// docs.post.ts
document.querySelectorAll("h2, h3").forEach((heading) => {
const id = heading.id;
if (!id) return;
const link = document.createElement("a");
link.setAttribute("href", `#${id}`);
link.textContent = "#";
link.className = "heading-anchor";
heading.appendChild(link);
});
Generate a Table of Contents
// docs.post.ts
const toc = document.getElementById("toc");
if (toc) {
const items: string[] = [];
document.querySelectorAll("h2[id]").forEach((h) => {
items.push(`<li><a href="#${h.id}">${h.textContent}</a></li>`);
});
toc.innerHTML = `<ul>${items.join("")}</ul>`;
}
Limitations
- No browser APIs —
window,localStorage,navigatordon't exist - No event listeners — Won't persist to the browser
- Don't inject component tags — They won't be resolved at this point
For element-level dynamic generation use TypeScript Components. For build-time fetching before components run, use Pre-rendering.
Next Steps
- Pre-rendering — Transform the page before components run
- TypeScript Components — Element-level logic
- Best Practices — Patterns and tips
- CLI Reference — Build options