For AI agents: fetch /llms.txt first for the curated documentation index, then use same-host Markdown pages when available.

Concepts

Routing

A route maps a URL path to a render function and optional metadata. Routes are plain objects, so humans and agents can read the whole map at a glance.

Defining a route

route takes a path and a definition with a render function and an optional meta function.

// site/pages/home.js
import { html, route } from "@nativefragments/core/server";

export const home = route("/", {
  meta: () => ({ title: "Home", description: "Welcome." }),
  render: () => html`<h1>Home</h1>`,
});

Path parameters

Use :name segments. Matched values arrive on ctx.params.

// site/pages/blog.js
export const post = route("/blog/:slug", {
  render: (ctx) => html`<h1>${ctx.params.slug}</h1>`,
});

Catch-all segments

A trailing :rest* captures zero or more remaining segments as a single slash-joined string on ctx.params.rest. It must be the final segment — declaring it anywhere else throws at definition time.

// /docs, /docs/guides, /docs/guides/routing all match
export const docs = route("/docs/:rest*", {
  render: (ctx) => html`<h1>${ctx.params.rest || "Docs home"}</h1>`,
});

Query parameters

ctx.query is the request's URLSearchParams. Read it directly, or use readSearch to pull typed string values with defaults for missing or empty params.

import { readSearch, route } from "@nativefragments/core/server";

export const list = route("/posts", {
  render: (ctx) => {
    const { filter, sort } = readSearch(ctx.query, {
      filter: "all",
      sort: "newest",
    });
    return html`<h1>${filter} · ${sort}</h1>`;
  },
});

The route context

Every render, meta, and action receives a RouteContext:

  • ctx.params — captured path parameters, including :rest*.
  • ctx.query — the request's URLSearchParams.
  • ctx.url — the parsed URL.
  • ctx.request — the original Request.
  • ctx.signal — an AbortSignal that fires on cancellation or a deferred timeout; pass it to fetch.
  • ctx.defer(fragment) — render a loading boundary now, stream the fragment when its data resolves.

Status and headers

Set status for a non-200 rendered page and headers for per-route response headers (an object or a function of the context). Route headers merge in after the adapter's defaults, so a route can set its own Cache-Control.

export const gone = route("/legacy", {
  status: 410,
  headers: () => ({ "Cache-Control": "public, max-age=3600" }),
  render: () => html`<h1>This page is gone</h1>`,
});

A route can also return or throw a native Response from meta or render; the adapter passes it through untouched.

Redirects

redirect(location, status = 302) builds a native redirect Response. Return it from render for a permanent move, or from an action for POST-redirect-GET.

import { redirect, route } from "@nativefragments/core/server";

export const old = route("/old", {
  render: () => redirect("/new", 301),
});

Mutations with actions

A route action handles POST for no-JavaScript forms. Actions never render — they must return a Response, usually a 303 redirect back to a GET URL. POST forms are never fragment-intercepted, so the browser follows the redirect normally.

import { redirect, route } from "@nativefragments/core/server";

export const todos = route("/todos", {
  action: async ({ request }) => {
    const form = await request.formData();
    await saveTodo(form.get("title"));
    return redirect("/todos", 303);
  },
  render: () => html`<form method="post">
    <input name="title" />
    <button>Add</button>
  </form>`,
});

Metadata

meta returns a RouteMeta object — title, description, canonical, and alternates. The shell renders it into the document head, and fragment responses carry it so the browser can update the head on navigation.

The route manifest

An app exports an array of routes. The Cloudflare adapter builds a manifest with createRoutes; exact paths match first, then parameterized routes in declaration order.

// site/routes.js
import { createRoutes } from "@nativefragments/core/server";

export const routes = [home, post];
// createCloudflareHandler calls createRoutes(routes) for you.

See also