Esc

Components API

Streak ships four built-in components. All are imported from streak-forge/components:

import { WidgetPlaceholder, Script, Dynamic, Preload } from "streak-forge/components";

WidgetPlaceholder

Marks the position in a layout where a widget's rendered HTML will be injected at build time.

Props

PropTypeRequiredDescription
idstringYesMust equal the widget entry's id in the sitemap and the handler return key. Any string — not required to equal type. Unique within the page.
typestringYesMust equal the widget entry's type in the sitemap and the widget filename in src/widgets/ (case-sensitive)

WidgetPlaceholder throws at render time if either prop is missing.

Usage

WidgetPlaceholder is used only inside layout files:

const MainLayout = () => (
  <html 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;

Every widget in the sitemap widgets[] array must have a corresponding WidgetPlaceholder in the layout whose id and type equal that entry's id and type (case-sensitive). This is placeholder-to-entry matching — id is not required to equal type.

The same type may be used by multiple entries on one page as long as each has a distinct id (and its own placeholder). See WidgetPlaceholder → Reusing a Widget Type.

At Build Time

  • A widget with no loadingStrategy (the default) has its placeholder replaced directly with its rendered HTML — it ships inline in the page.
  • A widget with loadingStrategy: "lazy" has its placeholder replaced with a small placeholder <div component-placeholder component-type="w" component-id="<id>"> instead. Its HTML is stored separately and fetched by the client runtime after page load. See Lazy Widgets.

Script

Serializes a function body to a string at build time and emits it as an inline <script> tag that executes as an IIFE in the browser.

Props

PropTypeRequiredDescription
idstringYesMust be unique on the page
optionsobjectNoPlain JSON-serializable object passed as the second IIFE argument
noncestringNoCSP nonce forwarded to the rendered <script nonce> attribute
childrenfunctionYesFunction body to serialize — receives (gDom, options)

Usage

<Script id="banner-init" options={{ color: "#818cf8", delay: 800 }}>
  {(gDom: any, options: any) => {
    document.getElementById("banner-heading").style.color = options.color;
  }}
</Script>

The rendered output is an IIFE:

((function(gDom, options) {
  document.getElementById("banner-heading").style.color = options.color;
})(window, {"color":"#818cf8","delay":800}));

Serialization Rules

RuleDetail
No closuresThe function is converted with .toString(). Outer-scope variables are not captured.
No importsES module imports inside the function body will not work. Use gDom.loadPackage() instead.
Options must be JSON-serializableThe options object is passed through JSON.stringify; any literal </script sequence in it is escaped so it can't break out of the inline tag.
gDom is windowThe first argument is window extended with the Streak client runtime helpers (addResourceToBody, loadPackage, loadDynamicComponent, addWidgetToBody).

Dynamic

Strips its children from the initial HTML at build time. The content is injected on demand at runtime via gDom.loadDynamicComponent().

Props

PropTypeRequiredDescription
idstringYesIdentifier used by loadDynamicComponent() to find and inject this content
childrenJSXNoThe content to defer — removed from initial HTML, injected on demand

Usage

Dynamic is always paired with a Script that triggers the injection:

<Dynamic id="nav-submenu">
  <ul>
    <li>Item A</li>
    <li>Item B</li>
  </ul>
</Dynamic>

<Script id="nav-submenu-trigger">
  {(gDom: any) => {
    document.getElementById("nav-btn")
      ?.addEventListener("click", () => {
        gDom.loadDynamicComponent("nav-submenu", () => {
          console.info("submenu injected");
        });
      });
  }}
</Script>

How It Works

  1. Build time: children are stripped and stored for later retrieval. A placeholder <div> is emitted with component-placeholder, component-type="c", and component-id="<id>".
  2. Runtime:gDom.loadDynamicComponent(id, callback) fetches the stored HTML, replaces the placeholder's outerHTML with it, injects any associated <script> tags, and calls callback.

Dynamic vs Lazy Comparison

HTML in initial payloadJS loadedTriggered by
loadingStrategy: "lazy"No — placeholder onlyFetched and injected after page loadAutomatic
<Dynamic>NoOn demandgDom.loadDynamicComponent()

Use lazy when a widget can wait until just after the page loads, but should still load automatically without any user interaction. Use Dynamic when content should only load in response to your own code — e.g. on click.


Preload

Renders a <link rel="preload"> tag. Tells the browser to begin fetching a resource before the parser discovers it naturally.

Props

PropTypeRequiredDescription
hrefstringYesURL of the resource to preload
asstringYesResource type hint for the browser — commonly "image", "font", "style", "script", "video", but any value is passed through as-is
...restanyNoAny other prop (e.g. media, crossorigin, type) is spread directly onto the rendered <link> tag

Usage

Use Preload inside a widget that renders in <head>:

<Preload href="/images/hero.jpg" as="image" media="(min-width: 768px)" />
<Preload href="/styles/tailwind.css" as="style" />
<Preload href="/assets/fonts/inter.woff2" as="font" crossorigin="anonymous" />

Rendered Output

<link rel="preload" href="/images/hero.jpg" as="image" media="(min-width: 768px)">
<link rel="preload" href="/styles/tailwind.css" as="style">

When to Use

  • LCP images — preload the largest above-the-fold image to reduce Largest Contentful Paint time.
  • Critical fonts — preload web fonts to avoid flash of unstyled text.
  • Above-the-fold CSS — preload stylesheets not discovered early in the document.