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
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.
| Name | Type | Description |
|---|---|---|
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) => |
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:
| Name | Type | Description |
|---|---|---|
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:
| Name | Type | Description |
|---|---|---|
'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.
| Name | Type | Description |
|---|---|---|
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.
| Name | Type | Description |
|---|---|---|
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 |
@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.