Skip to main content
Alpha

Fresh meta-framework

The NetScript Fresh meta-framework is the application layer that turns a route into a typed, server-rendered page. You author a page with definePage() — binding a typed route contract, server resources and layers (each with its own loader, cache window, and partial-refresh endpoint), and forms — then build it into Fresh route wiring. The same contract object a page loader uses to call a service is the one the typed SDK client imports and the oRPC service implements, so the page → contract → SDK → service request model cannot drift.

alpha

Request flow: browser hits a Fresh route built by definePage; the server handler runs resource and layer loaders that call the typed SDK client, which calls an oRPC service backed by the database; the rendered HTML ships to the browser where an island hydrates against the same query key.
The Fresh page model: definePage binds a route, runs server loaders through the typed SDK to a service, renders HTML, and hydrates islands against a shared query cache — one contract end to end.

What it is

@netscript/fresh is a DSL / builder package over Fresh 2. Its public product is a small set of builders and typed contracts — definePage(), defineRouteContract(), definePartial(), defineFreshApp(), plus form, query, and defer factories — that translate page and route intent into Fresh runtime wiring. A page is composed from three cooperating pieces: a server layer (data resolved by a loader), a partial route that re-renders just that layer on demand, and a hydrated island that shares the same query cache on the client. Rendering is cache-first: a fresh cached payload renders immediately, a stale one renders then reloads in the background (SWR), and a miss falls back to the layer's partial. The contract type-flow that makes this safe end-to-end is explained in Contracts.

Learn → / Do →

Minimal example — an orders page

A page starts with a typed route contract, then definePage() binds it, attaches a metadata resolver, and builds the Fresh route wiring. The search schema below is parsed and type-checked, so ctx.useSearch() inside a loader is fully typed.

// routes/orders/index.tsx
import { definePage } from '@netscript/fresh/builders';
import { defineRouteContract, paginationSearchSchema } from '@netscript/fresh/route';
import { OrdersPanel } from '../../islands/OrdersPanel.tsx';

// 1. The route contract: typed, parsed search params (the single source of truth for this URL).
const ordersRoute = defineRouteContract({
  searchSchema: paginationSearchSchema({
    defaultLimit: 20,
    defaultSort: 'createdAt',
    defaultOrder: 'desc',
  }),
});

// 2. The page: bind the route, load a server resource, render a cache-first layer.
export const ordersPage = definePage()
  .withRoute(ordersRoute)
  .withResource('orders', async (ctx) => {
    const { limit, sort, order } = ctx.useSearch();
    return await ordersClient.list.query({ limit, sort, order });
  })
  .withLayer('list', OrdersPanel, {
    loader: (ctx) => ({ orders: ctx.useResource('orders') }),
    partialName: 'orders-list',
    staleTime: 30_000,
    staleReloadMode: 'background',
  })
  .withMeta(() => ({ title: 'Orders', description: 'Browse the current order queue.' }))
  .build();

The matching partial route re-renders only the orders-list layer when the island asks for fresh data — same loader, same query key, no full-page reload:

// routes/orders/_partials/orders-list.tsx
import { definePartial } from '@netscript/fresh/builders';
import { OrdersPanel } from '../../../islands/OrdersPanel.tsx';

export const ordersListPartial = definePartial({
  name: 'orders-list',
  loader: async (ctx) => ({ orders: await ordersClient.list.query(ctx.search) }),
  component: OrdersPanel,
});

Key types first — the page builder chain

definePage() returns a fluent builder. Each with* step is typed against the accumulated state, so a search-param key, a resource name, or a layer's props are all inferred forward into the loaders and components downstream. Call .build() last; with a bound route it returns a routed definition exposing route, nav, and hooks.

definePage() builder methods (PageBuilder)
NameTypeDescription
withRoute(route) method Bind the page to a typed route contract (from defineRouteContract). Makes path/search params type-safe in every loader and component.
withResource(key, factory) method Resolve one named server resource (e.g. an SDK query). Read it later with ctx.useResource(key). withResources(map) adds several at once.
withParams / withPathParams / withSearchParams method Attach path and/or search schemas directly (alternative to withRoute) when you do not need a shared route contract.
withPolicy(policy) method Set the page-wide defer policy — a named profile or an override object. See the policy table below.
withLayer(id, component, config) method Register a render slot with its own loader, cache window, and partial-refresh endpoint. config is a PageLayerConfig (or a bare loader). See the layer-config table.
withForm(id, component, config) method Register a route-bound, server-validated form as a typed layer. config is a PageFormConfig. See the form-config table.
withLayout(layout) method Compose the registered layer slots into the page shell: (slots) =>
{slots.list()}
.
withMeta(resolver) method Resolve metadata (title, description) per request.
withTelemetry(config) method Attach telemetry metadata (span naming) for the page's traces.
withHandler(method, handler) method Register a raw method handler. Do not combine GET here with withHeader()/withStatus().
build(options?) method Finalize. build() / build('/path') / build({ routePattern }) — with a bound route the result exposes route, nav, and hooks.

withLayer options — the cache-first slot

A layer is the unit of independent loading and refresh. Its loader produces props, its partial/partialName names the route that re-renders it in isolation, and the cache/policy keys decide when a stale slot reloads. These are the real keys on PageLayerConfig:

PageLayerConfig — withLayer(id, component, config)
NameTypeDescription
loader (ctx) => Props | Promise Async loader providing the layer component's props. Reads resources via ctx.useResource(key).
partial string | (ctx) => string Partial endpoint (or resolver) used to refresh this layer without a full-page reload.
partialName string | (ctx) => string Stable Fresh partial name rendered into the response — the handshake key the island uses to ask for fresh data.
fallback unknown Content shown while a deferred layer is still pending.
policy PageDeferPolicyInput | profile Per-layer defer policy override (see the policy table).
layerDeps (ctx) => unknown Dependency projection (over path + search) deciding when the layer should reload.
staleTime number (ms) Freshness window for the cached layer payload before it is considered stale.
gcTime number (ms) Cache retention window before the payload is evicted.
staleReloadMode 'blocking' | 'background' When stale, reload before rendering (blocking) or render now and revalidate after (background SWR).
shouldReload boolean | (ctx) => boolean Explicit reload guard, overriding the freshness heuristics.
delivery PageLayerDelivery Delivery mode for the layer (e.g. streamed).

withPolicy — the defer policy enum

withPolicy() (and a layer's policy) accepts a named profile or an override object. The full set of named profiles is:

PageDeferPolicyProfile + PageDeferPolicyInput overrides
NameTypeDescription
'balanced' profile (default) Cache-first with sensible SWR — the default trade-off between first paint and freshness.
'aggressive-first-paint' profile Render cached/fallback content as early as possible, revalidate after.
'background-refresh' profile Prefer serving cache and always refresh in the background.
'low-bandwidth' profile Minimize prewarm and client refresh traffic for constrained clients.
{ profile, staleTimeMs } override Start from a named profile, then override the freshness window in ms.
{ prewarmOnMiss, prewarmOnStale } override Prewarm the partial when the cache is missing and/or stale.
{ clientRefreshOnFreshCache, skipClientWhenServerPrewarm } override Fine-tune whether the client refreshes when the server cache is already fresh or prewarming.

withForm — server-validated forms as a layer

withForm(id, component, config) registers a form as a typed layer: it wires the method handler, CSRF headers, validation, and form metadata in one step. TOutput is inferred from mutate's return type, so redirectTo/onSuccess are typed against your result.

PageFormConfig — withForm(id, component, config)
NameTypeDescription
schema Schema (required) Validation + constraint schema. The inference site for the form's input type.
mutate (input, ctx) => TOutput (required) Runs the mutation with validated input. Its return type is the sole inference site for TOutput.
initial (ctx) => Partial Resolves initial values on GET, merged with schema defaults.
onIntent (intent, values, ctx) => FormIntentResult Handles non-submit intents (validate, reset) — short-circuits before validation.
redirectTo (output, ctx) => string | Response Redirect target after a successful mutation. Takes precedence over onSuccess.
onSuccess (output, ctx) => { message?, nextValues? } Success metadata when staying on the same page.
invalidate (output, ctx) => void Cache invalidation after mutation, before the response is sent.
csrf boolean (default true) CSRF protection toggle.
method 'POST' | 'PUT' | 'PATCH' (default POST) HTTP method for the form submission.
spanName string (default form.{id}) Telemetry span prefix for the form handler.

Route contracts, partials, defer & islands

The route contract is the typed boundary of a URL. defineRouteContract({ pathSchema, searchSchema }) produces a contract whose parsed params flow into the page; the @netscript/fresh/route subpath ships the schema builders and link helpers, and @netscript/fresh/defer + @netscript/fresh/query cover deferral and island hydration.

Surrounding surface (confirmed exports)
NameTypeDescription
defineRouteContract({ pathSchema, searchSchema }) @netscript/fresh/route Typed route contract. Parsed path/search params become type-safe page inputs.
paginationSearchSchema(opts) @netscript/fresh/route Ready-made search schema for limit/sort/order pagination (defaultLimit/defaultSort/defaultOrder).
enumPathParamSchema(name, values) @netscript/fresh/route Typed enum path-param schema, e.g. a status segment constrained to a fixed set.
InferRouteContractSearch / InferRouteContractPath @netscript/fresh/route Extract the parsed search/path types from a route contract for reuse.
createRouteReference / bindRoutePattern @netscript/fresh/route Typed href and link-prop helpers bound to a route pattern.
definePartial({ name, loader, component }) @netscript/fresh/builders A framework-owned partial route — the isolated re-render target for a layer.
DeferPage / DeferComponent / Deferred @netscript/fresh/defer Suspense-style deferred rendering: stream a fallback now, swap real content when the promise resolves.
QueryIsland / useIslandQuery / useIslandMutation / useLiveQuery @netscript/fresh/query TanStack Query for islands, imported through one centralized subpath so the dependency never forks.
defineFreshApp(options) @netscript/fresh/server Bootstrap the Fresh app runtime that serves the built pages.

Production notes

Reference →

This hub is intentionally thin — the full generated API for every subpath (server, builders, route, defer, form, query, interactive, streams, vite) lives in the reference.

fresh