Esc

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 Script functions.
  • 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 Script closures.
  • 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 a Script function 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

FilePurpose
streak.sitemap.jsonDeclares every page: its URL, layout, data handler, and widgets
src/handlers/Middleware.tsOptional. Runs first on every page resolution; can override the render config used for a URL
src/handlers/CommonHandler.tsOptional. Shared data fetched once and passed to every data handler as { common }
src/handlers/*.tsPer-page data handlers — async functions returning widget data
src/layouts/*.tsxFull HTML document structure, laid out with WidgetPlaceholders
src/widgets/*.tsxStateless TSX components rendered per page
public/Static files served as-is (images, compiled CSS, packages for loadPackage)
out/<url>/<version>/raw-content.jsonPer-page JSON snapshot written by streak-forge build