Create a Project
A Streak.js project requires four things:
streak.sitemap.json— declares pages- A data handler in
src/handlers/ - A layout in
src/layouts/ - At least one widget in
src/widgets/
This guide walks through creating each one from scratch.
Step 1 — Create streak.sitemap.json
The sitemap is the single entry point for the build. Place it at the project root.
[
{
"url": "/",
"renderConfig": {
"renderId": "homeRenderId",
"metadata": {},
"dataHandler": "HomeDataHandler",
"rootLayout": "MainLayout",
"widgets": [
{ "id": "PageHead", "type": "PageHead" },
{ "id": "HelloBanner", "type": "HelloBanner" },
{ "id": "HelloMessage", "type": "HelloMessage", "loadingStrategy": "lazy" }
],
"version": "1.0.0"
}
}
] renderId must be globally unique — it becomes the output directory name (out/homeRenderId/index.html).
Step 2 — Create the Data Handler
Create src/handlers/HomeDataHandler.ts. The filename must match dataHandler in the sitemap (without extension).
// src/handlers/HomeDataHandler.ts
const getHomeData = async () => {
return {
status: 200, // required — signals render success
PageHead: { // key matches widget id from sitemap
title: "Hello",
},
HelloBanner: {
heading: "Hello World",
},
};
};
export default getHomeData; Rules:
- Must be the default export
- Must return
{ status: 200, ...widgetData } - Each top-level key must match a widget
idin the sitemap - The value under each key is passed to the matching widget as
props.data - It is async — you can
awaitany database, CMS, or API call here
Step 3 — Create the Layout
Create src/layouts/MainLayout.tsx. The filename must match rootLayout in the sitemap (without extension).
// src/layouts/MainLayout.tsx
import { WidgetPlaceholder } from "streak/components";
const MainLayout = () => {
return (
<html dir="ltr" lang="en">
<head>
<WidgetPlaceholder id="PageHead" type="PageHead" />
</head>
<body>
<WidgetPlaceholder id="HelloBanner" type="HelloBanner" />
<WidgetPlaceholder id="HelloMessage" type="HelloMessage" />
</body>
</html>
);
};
export default MainLayout; Rules:
- Must be the default export
- Must return the full HTML document —
<html>,<head>,<body> - Every widget in the sitemap's
widgets[]needs a matchingWidgetPlaceholderhere idandtypeonWidgetPlaceholdermust match the sitemap entry exactly
Step 4 — Create Widgets
Create src/widgets/HelloBanner.tsx. The filename must exactly match the type field in the sitemap (case-sensitive).
// 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; Rules:
- Must be the default export
- Data arrives as
props.data— always use optional chaining (?.) and fallbacks (??) - Widgets are stateless — no
useState, nouseEffect
Step 5 — Run the Dev Server
bun run dev Step 6 — Build
bun run build Output is written to:
out/homeRenderId/index.html
The generated HTML contains the rendered layout, all widget HTML, a hidden <script id="w-m"> with widget metadata, and the app.js IIFE.