Introduction
Streak.js is a static site generator that renders React components to plain HTML using per-page data handlers. In development, the dev server renders full HTML for every request. In a full build, each page is rendered and written to disk as a JSON snapshot — nothing about how that snapshot becomes a hosted site is part of Streak.js itself.
What Streak.js Is NOT
Understanding what Streak does not do is as important as understanding what it does.
- No hydration — nothing re-renders in the browser after the page loads. The output is plain HTML.
- No virtual DOM in the browser — DOM mutations happen via plain JS in
Scriptfunctions. - No routing — Streak resolves one render config per URL (from the sitemap, or from
Middleware.ts). Anything beyond that — redirects, catch-all routes on a live server, etc. — is your host's job. - No state management — widgets are pure render functions. State lives in the DOM and in
Scriptclosures. - No CSS-in-JS — styling is compiled ahead of time (Tailwind, plain CSS, whatever you choose) and linked from the layout.
- No client-side framework runtime — the only browser-side surface is a small helper object (
gDom) that aScriptfunction can call to do things like load a third-party package or inject deferred content. It does not manage components or state.
Design Philosophy
TSX components are build-time (or request-time) templates, not client-side components. A data handler supplies plain data, a layout arranges WidgetPlaceholder slots, and widgets render into those slots — all on the server. The browser receives the result as-is. A small set of gDom helpers (loadPackage, loadDynamicComponent, addWidgetToBody, addResourceToBody) enables a few progressive-enhancement features — lazy-loaded widgets, deferred Dynamic content, third-party packages — but nothing re-renders components or re-runs your data handler in the browser.
Complete Mental Model
streak.sitemap.json
[{ url, renderConfig: { renderId, metadata, dataHandler,
rootLayout, widgets[], version } }]
│
▼
Middleware.ts (optional, auto-discovered)
Runs first for the requested/built url. Returning nothing means
"use the sitemap's own entry for this url (or 404 if there is
none)"; returning a render config overrides it — this is what lets
a url that isn't in the sitemap at all still resolve to a page.
│
▼
CommonHandler.ts (optional, auto-discovered)
Shared data fetched and passed into every page's data handler as
{ common }.
│
▼
<dataHandler>.ts
(metadata, { common }) => ({ status: 200, WidgetId: {...}, ... })
│
▼
<rootLayout>.tsx
Renders the full <html> document, with a WidgetPlaceholder for
each widget declared in widgets[].
│
▼
each widget in widgets[]
src/widgets/<type>.tsx renders with props.data taken from the
handler's output, replacing its WidgetPlaceholder in the layout.
│
▼
styles collected
│
├── dev ─────────► full HTML returned for that one request
│
└── build ───────► out/<url>/<version>/raw-content.json
written for every page in the sitemap
Key Files
| File | Purpose |
|---|---|
streak.sitemap.json | Declares every page: its URL, layout, data handler, and widgets |
src/handlers/Middleware.ts | Optional. Runs first on every page resolution; can override the render config used for a URL |
src/handlers/CommonHandler.ts | Optional. Shared data fetched once and passed to every data handler as { common } |
src/handlers/*.ts | Per-page data handlers — async functions returning widget data |
src/layouts/*.tsx | Full HTML document structure, laid out with WidgetPlaceholders |
src/widgets/*.tsx | Stateless TSX components rendered per page |
public/ | Static files served as-is (images, compiled CSS, packages for loadPackage) |
out/<url>/<version>/raw-content.json | Per-page JSON snapshot written by streak-forge build |