Sections

page()

page() defines a server-rendered route for mini(). A page render function returns HTML and receives request context: the original request, parsed URL, matched route parameters, current route pattern, and HTMX state.

Basic Page

import { page } from "@calvinbonner/minifw/core";
import { html } from "@calvinbonner/minifw/helpers";

const product = page(({ params }) => html`<h1>${params["id"]}</h1>`, {
  head: { title: "Product" },
});

Register it under a route pattern:

mini({
  routes: {
    "/products/:id": product,
  },
});

The route pattern becomes context.route, and Bun's matched :id parameter is available as context.params["id"].

Render Context

FieldDescription
requestOriginal incoming Bun.BunRequest, including cookies
urlParsed request URL, including search parameters
routeMatched route pattern, when the page is served by mini()
paramsRoute parameters as a string record
isHtmxWhether the request includes HX-Request: true

Use url.searchParams for query values and params for values captured by a route pattern. For native Bun handlers outside a page, use isHtmx() to detect the same header.

Head Metadata

The optional head field provides document metadata for the initial response and applicable <title>/<meta> updates for HTMX responses.

const profile = page(renderProfile, {
  head: {
    title: "Profile",
    description: "Manage your account profile.",
    canonical: "https://example.com/profile",
    robots: "noindex",
  },
});

mini() creates the initial document and renders this metadata in its head. Do not include <html>, <head>, or <body> in page markup.

Scoped Styles

Use the style-function overload to attach CSS to a page. MiniFW scopes element selectors to that page's markup, preventing collisions with other pages and partials.

import { css, html } from "@calvinbonner/minifw/helpers";

const profile = page(
  ({ params }) => html`<article class="profile">${params["id"]}</article>`,
  () => css`
    .profile {
      max-width: 60ch;
    }
  `,
  { head: { title: "Profile" } },
);

For an initial response, scoped styles move into the document head. For a later HTMX response, the markup includes its styles and MiniFW's runtime promotes any new style block into the existing head. See Style Encapsulation for details.

Caching

Set cache: true to cache a page indefinitely, or use a millisecond ttl.

const catalog = page(renderCatalog, { cache: { ttl: 60_000 } });

Page cache keys include the route, pathname, search string, parameters, and HTMX state. Omit cache, or set it to false, when each request must render fresh content. See Cache Management.

Normal And HTMX Responses

For a normal request, mini() renders the page inside all matching layouts, returning a full document. For a standard HTMX request, MiniFW returns page markup and targets the innermost layout's pageTarget. For boosted navigation that shares an outer layout prefix, it swaps only the changed inner fragment; unrelated layout chains use HX-Redirect for a full navigation.

Redirects And Errors

Call redirectTo() when render-time logic should stop and redirect the client:

const account = page(({ params }) => {
  if (!params["userId"]) redirectTo("/login", 303);

  return "<h1>Account</h1>";
});

Call error() for an expected HTTP failure such as a 404. It reaches Bun's error handler, where isMiniError() narrows the error and exposes its status.

Use fragment() to compose reusable context-free markup inside a page. Use a partial() when that markup needs its own request endpoint.