Your First Page
This walkthrough shows the complete path from a sitemap entry to Streak rendering a page — both live in the dev server and in a full build.
Step 1 — Add an Entry to streak.sitemap.json
[
{
"url": "/",
"renderConfig": {
"renderId": "homeRenderId",
"metadata": {},
"dataHandler": "HomeDataHandler",
"rootLayout": "MainLayout",
"widgets": [
{ "id": "PageHead", "type": "PageHead" },
{ "id": "HelloBanner", "type": "HelloBanner" }
],
"version": "1.0.0"
}
}
] renderId must be globally unique across the sitemap. The url and version fields determine where a full build writes this page's output: out/1.0.0/raw-content.json.
Step 2 — Create the Data Handler
src/handlers/HomeDataHandler.ts
const getHomeData = async (
metadata?: Record<string, unknown>,
{ common }: { common?: Record<string, unknown> } = {},
) => {
return {
status: 200,
PageHead: {
title: "Hello",
},
HelloBanner: {
heading: "Hello World",
},
};
};
export default getHomeData; - Must be the default export
- Must return
{ status: 200, ...widgetData } - Each key must match a widget
idin the sitemap - The value is passed to the matching widget as
props.data - Streak calls the handler as
(metadata, { common })— the sitemap'smetadatafield, and (ifCommonHandler.tsexists) its return value. Both are optional in practice; a handler with no parameters still works
Step 3 — Create the Layout
src/layouts/MainLayout.tsx
import { WidgetPlaceholder } from "streak-forge/components";
const MainLayout = () => {
console.info("Rendering Main Layout"); // runs on the server, never in the browser
return (
<html dir="ltr" lang="en">
<head>
<WidgetPlaceholder id="PageHead" type="PageHead" />
</head>
<body>
<WidgetPlaceholder id="HelloBanner" type="HelloBanner" />
</body>
</html>
);
};
export default MainLayout; Every widget in widgets[] needs a matching WidgetPlaceholder. Both id and type must match the sitemap entry exactly.
Step 4 — Create the Widget
src/widgets/HelloBanner.tsx
type HelloBannerProps = {
data?: {
heading?: string;
};
};
const HelloBanner = (props: HelloBannerProps) => {
const heading = props?.data?.heading ?? "Hello World";
return (
<section>
<h1>{heading}</h1>
</section>
);
};
export default HelloBanner; - Filename must exactly match the
typein the sitemap (case-sensitive) - Data arrives as
props.data— always use?.and??because Streak passes{ data: undefined }if the handler returned nothing for this widget - No
useState, nouseEffect— widgets are stateless
Step 5 — Render It
While developing, just run the dev server — no separate build step is needed:
bun run dev Open http://localhost:3690/ (or whatever url you used). streak-forge dev renders that page fresh for the request: Middleware.ts (if present) resolves which render config to use, then CommonHandler.ts (if present) supplies shared data, then your data handler runs, then the layout, then each widget — and the resulting HTML is returned.
Step 6 — Build
To render every page in the sitemap and write it to disk — for example, before publishing — run:
bunx streak-forge build This writes:
out/1.0.0/raw-content.json
raw-content.json is a JSON snapshot of the render: the page's renderId, metadata, dataHandler, rootLayout, version, and each widget's rendered HTML and collected styles. It's an intermediate build artifact, not a finished HTML page.
Optionally, run
bunx streak-forge pre-buildfirst to bundle your handlers, widgets, and layouts into.prebuild/— later builds then skip re-transpiling that source.
Publishing this output to a live site is handled by Nexus — see the Nexus documentation for publishing/hosting details.
What Happens on Each Render
For every page — whether serving a live dev request or writing it out during a build — Streak follows the same sequence:
Middleware.ts(if present) runs first and may override which render config is used for the URL.CommonHandler.ts(if present) supplies data shared across every page.- The page's own data handler runs as
(metadata, { common }) => {...}. - The layout renders, with a
WidgetPlaceholderfor each widget. - Each widget renders with its slice of the handler's data as
props.data. - Styles are collected.