Esc

Your First Page

This walkthrough shows the complete path from a sitemap entry to a generated HTML file.


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"
    }
  }
]

The renderId is globally unique and becomes the output folder name: out/homeRenderId/index.html.


Step 2 — Create the Data Handler

src/handlers/HomeDataHandler.ts

const getHomeData = async () => {
  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 id in the sitemap
  • The value is passed to the matching widget as props.data

Step 3 — Create the Layout

src/layouts/MainLayout.tsx

import { WidgetPlaceholder } from "streak/components";

const MainLayout = () => {
  console.info("Rendering Main Layout"); // runs at build time on the server
  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. The console.info call runs at build time, not in the browser.


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 type in 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, no useEffect — widgets are stateless

Step 5 — Build

bun run build

This runs streak-forge pre-build, which processes the sitemap through 7 stages and writes:

out/homeRenderId/index.html

What the Output Contains

The generated HTML file contains:

  • The fully rendered layout with all widget HTML injected in place of WidgetPlaceholder elements
  • A hidden <script id="w-m"> element with JSON metadata listing every widget's id and asset path
  • The app.js IIFE — the client-side coordinator
  • Inline <script> tags for any Script components used in widgets

The page is complete static HTML. No framework runtime is included.


What Happens in the Browser

When the page loads, app.js:

  1. Records the page load time
  2. Reads widget metadata from <script id="w-m">
  3. Checks the __vi session cookie to determine window.ftr (first-time render)
  4. Loads widget assets sequentially, chaining callbacks
  5. Makes gDom helpers available on window for Script functions to use