vt-ssg
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Viewing readonly version of main branch: v337View latest version
A lightweight static site generator for Val Town. Takes a markdown string and a JSX layout template, returns an HTML string. Includes a Hono-compatible FileSystemModule for writing output files directly into a val via the API.
Stack: marked · gray-matter · Hono JSX
/** @jsxImportSource npm:hono@4/jsx */
import { render } from "https://esm.town/v/nbbaier/vt-ssg/main.tsx";
const html = await render({
markdown: `---
title: Hello World
date: 2026-04-04
---
# Hello
Some **markdown** content.`,
});
// html is a complete HTML string with <!DOCTYPE html>
Pass a Hono JSX component as the layout option. It receives title, content (rendered HTML), and the full frontmatter object as props.
/** @jsxImportSource npm:hono@4/jsx */
import { render } from "https://esm.town/v/nbbaier/vt-ssg/main.tsx";
import type { LayoutComponent } from "https://esm.town/v/nbbaier/vt-ssg/types.ts";
const BlogLayout: LayoutComponent = ({ title, content, frontmatter }) => (
<html lang="en">
<head>
<title>{title} | My Blog</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<header><nav>Home | About</nav></header>
<article>
<h1>{title}</h1>
<time>{String(frontmatter.date)}</time>
<div dangerouslySetInnerHTML={{ __html: content }} />
</article>
<footer>© 2026</footer>
</body>
</html>
);
const html = await render({
markdown: source,
layout: BlogLayout,
layoutProps: { theme: "dark" },
});
Use renderToFiles to render multiple pages and write them as static HTML files into a val via the API. Requires a VT_WRITE_TOKEN env var with write permissions.
/** @jsxImportSource npm:hono@4/jsx */
import { renderToFiles } from "https://esm.town/v/nbbaier/vt-ssg/main.tsx";
const results = await renderToFiles({
pages: [
{ path: "index.html", markdown: indexMd },
{ path: "about.html", markdown: aboutMd, layout: BlogLayout },
{ path: "blog/post-1.html", markdown: post1Md },
],
layout: DefaultBlogLayout, // shared layout (pages can override)
dir: "static", // output directory (default: "static")
fs: { callerUrl: import.meta.url },
});
// Output is relative to the calling file's directory by default.
// Use fs: { callerUrl: import.meta.url, root: "." } to write from the val root.
The createValTownFS function returns a Hono-compatible FileSystemModule, so you can use it directly with Hono's SSG helper:
import { Hono } from "npm:hono@4";
import { toSSG } from "npm:hono@4/ssg";
import { createValTownFS } from "https://esm.town/v/nbbaier/vt-ssg/fs.ts";
const app = new Hono();
app.get("/", (c) => c.html("<h1>Home</h1>"));
app.get("/about", (c) => c.html("<h1>About</h1>"));
const fs = createValTownFS({ callerUrl: import.meta.url });
const result = await toSSG(app, fs, { dir: "./static" });
// Output is relative to the calling file by default.
// Writes static/index.html and static/about.html next to this file.
| Option | Type | Required | Description |
|---|---|---|---|
markdown | string | ✅ | Raw markdown string, optionally with YAML frontmatter |
layout | LayoutComponent | ❌ | Hono JSX component. Defaults to a minimal HTML shell. |
layoutProps | Record<string, unknown> | ❌ | Extra props merged into the layout props |
markedOptions | MarkedOptions | ❌ | Options passed to marked. GFM enabled by default. |
| Option | Type | Required | Description |
|---|---|---|---|
pages | PageInput[] | ✅ | Array of pages to render (each has path, markdown, optional layout/layoutProps) |
fs | ValTownFSOptions | ✅ | FS config: { callerUrl: import.meta.url } (or valId/token) |
layout | LayoutComponent | ❌ | Shared layout for all pages (individual pages can override) |
layoutProps | Record<string, unknown> | ❌ | Shared extra layout props |
markedOptions | MarkedOptions | ❌ | Options passed to marked |
dir | string | ❌ | Output directory prefix. Defaults to "static". |
| Option | Type | Required | Description |
|---|---|---|---|
callerUrl | string | ✅ | Pass import.meta.url to target the calling val |
valId | string | ❌ | Target val ID. Takes precedence over callerUrl. |
token | string | ❌ | API token. Defaults to VT_WRITE_TOKEN env var. |
root | string | ❌ | Base directory for resolving output paths. Omitted: relative to the calling file's directory. ".": relative to the val root. Any other string: used as-is from the val root. |
Given a build script at examples/my-site/build.tsx:
root | writeFile("static/index.html") | Resolves to |
|---|---|---|
| (omitted) | relative to caller | examples/my-site/static/index.html |
"." | relative to val root | static/index.html |
"dist" | relative to dist/ | dist/static/index.html |
| Prop | Type | Description |
|---|---|---|
title | string | Extracted from frontmatter.title, or "" |
content | string | Rendered HTML from the markdown body |
frontmatter | Record<string, unknown> | Full parsed frontmatter object |
...layoutProps | unknown | Any extra props from layoutProps |
Rendering mermaid diagram...
main.tsx — Public API: render() + renderToFiles()
types.ts — TypeScript type definitions
fs.ts — Hono-compatible FileSystemModule for Val Town
frontmatter.ts — gray-matter wrapper
markdown.ts — marked wrapper
defaults.tsx — Default HTML layout component
test.tsx — Test script
serve.tsx — Static file server (for serving generated output)