• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
nbbaier

nbbaier

vt-ssg

Lightweight SSG: markdown + JSX layout → HTML string
Public
Like
vt-ssg
Home
Code
9
examples
3
README.md
defaults.tsx
frontmatter.ts
fs.ts
main.tsx
markdown.ts
test.tsx
types.ts
Connections
Environment variables
1
Branches
2
Pull requests
Remixes
History
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.
Sign up now
Code
/
README.md
Code
/
README.md
Search
…
Viewing readonly version of main branch: v184
View latest version
README.md

vt-ssg

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

Quick Start

/** @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>

Custom Layout

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" }, });

Batch Render + Write Files

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 }, }); // results: [{ path: "static/index.html", success: true }, ...]

Drop-in with Hono's toSSG

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" }); // Writes static/index.html and static/about.html into the val

API Reference

render(options: RenderOptions): Promise<string>

OptionTypeRequiredDescription
markdownstring✅Raw markdown string, optionally with YAML frontmatter
layoutLayoutComponent❌Hono JSX component. Defaults to a minimal HTML shell.
layoutPropsRecord<string, unknown>❌Extra props merged into the layout props
markedOptionsMarkedOptions❌Options passed to marked. GFM enabled by default.

renderToFiles(options: RenderToFilesOptions): Promise<PageResult[]>

OptionTypeRequiredDescription
pagesPageInput[]✅Array of pages to render (each has path, markdown, optional layout/layoutProps)
fsValTownFSOptions✅FS config: { callerUrl: import.meta.url } (or valId/token)
layoutLayoutComponent❌Shared layout for all pages (individual pages can override)
layoutPropsRecord<string, unknown>❌Shared extra layout props
markedOptionsMarkedOptions❌Options passed to marked
dirstring❌Output directory prefix. Defaults to "static".

createValTownFS(options: ValTownFSOptions): FileSystemModule

OptionTypeRequiredDescription
callerUrlstring✅Pass import.meta.url to target the calling val
valIdstring❌Target val ID. Takes precedence over callerUrl.
tokenstring❌API token. Defaults to VT_WRITE_TOKEN env var.
rootstring❌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.

Path Resolution Examples

Given a build script at examples/my-site/build.tsx:

rootwriteFile("static/index.html")Resolves to
(omitted)relative to callerexamples/my-site/static/index.html
"."relative to val rootstatic/index.html
"dist"relative to dist/dist/static/index.html

LayoutProps

PropTypeDescription
titlestringExtracted from frontmatter.title, or ""
contentstringRendered HTML from the markdown body
frontmatterRecord<string, unknown>Full parsed frontmatter object
...layoutPropsunknownAny extra props from layoutProps

Architecture

Rendering mermaid diagram...

File Structure

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)
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
AboutAlternativesPricingBlogNewsletterCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.