: TEntry extends { readonly path: infer P extends string }
? ExtractParams
: Record;
/**
* Resolve params from Prerender's type parameter.
* Accepts named routes (global or .local) and explicit param objects.
*
* Resolution order:
* 1. ".local" string → look up in TRouteMap
* 2. Global route name string → look up in DefaultPrerenderRouteMap
* 3. Record object → use as-is (explicit params)
* 4. Fallback → {}
*/
type ResolvePrerenderParams<
T,
TRouteMap extends {} = DefaultPrerenderRouteMap,
> = T extends `.${infer Local}`
? Local extends keyof TRouteMap
? ExtractParamsFromEntry
: Record
: T extends keyof DefaultPrerenderRouteMap
? ExtractParamsFromEntry
: T extends Record
? T
: {};
// -- Types ------------------------------------------------------------------
export interface PrerenderOptions {
/**
* Maximum number of param sets to render in parallel (default: 1).
* Only applies to dynamic Prerender handlers with getParams().
* Set to higher values to speed up builds with many routes.
*
* @example
* ```typescript
* export const BlogPost = Prerender(
* async () => allPosts.map(p => ({ slug: p.slug })),
* async (ctx) => ,
* { concurrency: 4 },
* );
* ```
*/
concurrency?: number;
}
/**
* Context passed to Prerender() handlers at build time.
* Has a synthetic URL from getParams, params, pathname, and optionally env.
* No request, headers, cookies.
*/
export interface BuildContext {
/** Params extracted from the route pattern (populated from getParams). */
params: TParams;
/** True during build-time pre-rendering, false during passthrough live render. */
build: true;
/**
* True when running in Vite dev mode (on-demand prerender), false during
* production `vite build`. Use this to branch on runtime mode without
* changing build semantics.
*/
dev: boolean;
/**
* Build-time environment bindings (KV, D1, etc.) supplied by the Vite plugin.
* Only available when `buildEnv` is configured in rango() options.
* Throws with a clear error if not configured.
*
* This is NOT the live request env — it is shared across all prerender
* invocations for the build.
*/
env: DefaultEnv;
/** Read a variable set by getParams or a parent handler. */
get: {
(contextVar: ContextVar): T | undefined;
(key: string): any;
};
/** Set a variable readable by child layouts and parallels. */
set: {
(contextVar: ContextVar, value: T): void;
(key: string, value: any): void;
};
/** Push handle data (frozen into pre-rendered output at build time). */
use: (handle: Handle) => (data: T) => void;
/** Synthetic URL built from pattern + params (no real request). */
url: URL;
/** Pathname portion of the synthetic URL. */
pathname: string;
/** URLSearchParams from the synthetic URL (always empty for prerender). */
searchParams: URLSearchParams;
/** Typed search params — always {} for prerender (no real query string). */
search: {};
/** URL generation by route name. */
reverse: BuildReverseFunction;
/**
* Signal that this param set should not produce a local prerender artifact.
* At runtime the live handler runs instead. Only valid on routes wrapped
* with `Passthrough()`.
*/
passthrough: () => PrerenderPassthroughResult;
}
/**
* Context passed to Static() handlers at build time.
* No URL, no params, no pathname — just renders content.
*/
export interface StaticBuildContext {
/** Always true for Static handlers at build time. */
build: true;
/**
* True when running in Vite dev mode, false during production build.
*/
dev: boolean;
/**
* Build-time environment bindings supplied by the Vite plugin.
* Only available when `buildEnv` is configured in rango() options.
*/
env: DefaultEnv;
/** Read a variable (available for type consistency with BuildContext). */
get: {
(contextVar: ContextVar): T | undefined;
(key: string): any;
};
/** Set a variable (available for type consistency with BuildContext). */
set: {
(contextVar: ContextVar, value: T): void;
(key: string, value: any): void;
};
/** Push handle data (frozen into pre-rendered output at build time). */
use: (handle: Handle) => (data: T) => void;
/** URL generation by route name. */
reverse: BuildReverseFunction;
}
/**
* Context passed to getParams() at build time.
* Allows sharing data with handler invocations via set().
*/
export interface GetParamsContext {
/** Always true during build-time getParams execution. */
build: true;
/**
* True when running in Vite dev mode, false during production build.
*/
dev: boolean;
/**
* Build-time environment bindings supplied by the Vite plugin.
* Only available when `buildEnv` is configured in rango() options.
*/
env: DefaultEnv;
/** Set a variable that will be available to each handler invocation via ctx.get(). */
set: {
(contextVar: ContextVar, value: T): void;
(key: string, value: any): void;
};
/** URL generation by route name. */
reverse: BuildReverseFunction;
}
export interface PrerenderHandlerDefinition<
TParams extends Record = any,
> {
readonly __brand: "prerenderHandler";
/** Auto-generated unique ID (injected by Vite plugin). */
$$id: string;
/** In dev mode, the actual handler function that path() can call. */
handler: Handler;
/** Returns the list of param objects to pre-render (dynamic routes). */
getParams?: (ctx: GetParamsContext) => Promise | TParams[];
/** Pre-render options. */
options?: PrerenderOptions;
/** Composable default DSL items merged when the handler is mounted. */
use?: () => UseItems;
}
// -- Overloads --------------------------------------------------------------
//
// T accepts: named route string (global or .local) OR explicit param object.
// Named routes resolve params from GeneratedRouteMap, e.g.:
// Prerender<"locale.detail"> → params = { locale: string; slug: string }
// Explicit params work as before:
// Prerender<{ slug: string }> → params = { slug: string }
// Overload 1: Static handler (build-time only)
export function Prerender<
T extends
| keyof DefaultPrerenderRouteMap
| `.${keyof TRouteMap & string}`
| Record = {},
TRouteMap extends {} = DefaultPrerenderRouteMap,
>(
handler: (
ctx: BuildContext>,
) =>
| ReactNode
| PrerenderPassthroughResult
| Promise,
options?: PrerenderOptions,
__injectedId?: string,
): PrerenderHandlerDefinition>;
// Overload 2: Dynamic handler (build-time only)
export function Prerender<
T extends
| keyof DefaultPrerenderRouteMap
| `.${keyof TRouteMap & string}`
| Record,
TRouteMap extends {} = DefaultPrerenderRouteMap,
>(
getParams: (
ctx: GetParamsContext,
) =>
| Promise[]>
| ResolvePrerenderParams[],
handler: (
ctx: BuildContext>,
) =>
| ReactNode
| PrerenderPassthroughResult
| Promise,
options?: PrerenderOptions,
__injectedId?: string,
): PrerenderHandlerDefinition>;
// -- Implementation ---------------------------------------------------------
export function Prerender>(
handlerOrGetParams: Function,
handlerOrOptions?: Function | PrerenderOptions,
optionsOrId?: PrerenderOptions | string,
maybeId?: string,
): PrerenderHandlerDefinition {
// Resolve overloads:
// 1 fn arg: Prerender(handler, options?, __injectedId?)
// 2 fn args: Prerender(getParams, handler, options?, __injectedId?)
let handler: Handler;
let getParams: (() => Promise | TParams[]) | undefined;
let options: PrerenderOptions | undefined;
let id: string;
if (typeof handlerOrOptions === "function") {
// Two function args: getParams + handler
getParams = handlerOrGetParams as () => Promise | TParams[];
handler = handlerOrOptions as Handler;
if (typeof optionsOrId === "string") {
id = optionsOrId;
} else {
options = optionsOrId as PrerenderOptions | undefined;
id = maybeId ?? "";
}
} else {
// Single function arg: handler only
handler = handlerOrGetParams as Handler;
if (typeof handlerOrOptions === "object" && handlerOrOptions !== null) {
options = handlerOrOptions as PrerenderOptions;
}
if (typeof optionsOrId === "string") {
id = optionsOrId;
} else {
id = maybeId ?? "";
}
}
if (isCachedFunction(handler)) {
throw new Error(
'A "use cache" function cannot be used as a Prerender() handler. ' +
"Prerender handlers are rendered at build time. Remove the " +
'"use cache" directive — Prerender already provides caching.',
);
}
if (getParams && isCachedFunction(getParams)) {
throw new Error(
'A "use cache" function cannot be used as Prerender() getParams. ' +
"getParams runs at build time to enumerate params. Remove the " +
'"use cache" directive.',
);
}
if (!id) {
throw new Error(
"[rango] Prerender: missing $$id. " +
"Ensure the exposeInternalIds Vite plugin is configured.",
);
}
return {
__brand: "prerenderHandler" as const,
$$id: id,
handler,
...(getParams ? { getParams } : {}),
...(options ? { options } : {}),
};
}
// -- Passthrough sentinel ---------------------------------------------------
/**
* Sentinel returned by `ctx.passthrough()` to signal that a specific param set
* should not produce a local prerender artifact. The build skips writing the
* entry; at runtime the Passthrough live handler runs instead.
*/
export const PRERENDER_PASSTHROUGH: Readonly<{
__brand: "prerenderPassthrough";
}> = Object.freeze({
__brand: "prerenderPassthrough" as const,
});
export type PrerenderPassthroughResult = typeof PRERENDER_PASSTHROUGH;
/**
* Type guard to check if a value is the passthrough sentinel.
*/
export function isPrerenderPassthrough(
value: unknown,
): value is PrerenderPassthroughResult {
return (
typeof value === "object" &&
value !== null &&
"__brand" in value &&
(value as { __brand: unknown }).__brand === "prerenderPassthrough"
);
}
// -- Type guards ------------------------------------------------------------
/**
* Type guard to check if a value is a PrerenderHandlerDefinition.
*/
export function isPrerenderHandler(
value: unknown,
): value is PrerenderHandlerDefinition {
return (
typeof value === "object" &&
value !== null &&
"__brand" in value &&
(value as { __brand: unknown }).__brand === "prerenderHandler"
);
}
// -- Passthrough wrapper ----------------------------------------------------
/**
* A prerender route with a live fallback handler for unknown params at runtime.
*
* Wraps a `Prerender(...)` definition with a separate handler that runs at
* request time for params not covered by `getParams()`.
*
* - Build time: `prerenderDef` provides getParams + build handler.
* - Runtime: `liveHandler` runs for unknown params with full HandlerContext.
*
* @example
* ```ts
* const BlogPrerender = Prerender(
* async () => [{ slug: "getting-started" }, { slug: "api-reference" }],
* async (ctx) => ,
* );
*
* // In route definition:
* path("/blog/:slug", Passthrough(BlogPrerender, async (ctx) => {
* const post = await ctx.env.DB.get(ctx.params.slug);
* return ;
* }))
* ```
*/
export interface PassthroughHandlerDefinition<
TParams extends Record = any,
TEnv = DefaultEnv,
> {
readonly __brand: "passthroughHandler";
/** The underlying prerender definition (build-time rendering). */
prerenderDef: PrerenderHandlerDefinition;
/** Live handler for runtime fallback on unknown params. */
liveHandler: (
ctx: HandlerContext,
) => ReactNode | Promise | Response | Promise;
/** Composable default DSL items merged when the handler is mounted. */
use?: () => UseItems;
}
export function Passthrough<
TParams extends Record,
TEnv = DefaultEnv,
>(
prerenderDef: PrerenderHandlerDefinition,
liveHandler: (
ctx: HandlerContext,
) => ReactNode | Promise | Response | Promise,
): PassthroughHandlerDefinition;
// Implementation
export function Passthrough<
TParams extends Record,
TEnv = DefaultEnv,
>(
prerenderDef: PrerenderHandlerDefinition,
liveHandler: (
ctx: HandlerContext,
) => ReactNode | Promise | Response | Promise,
): PassthroughHandlerDefinition {
if (!isPrerenderHandler(prerenderDef)) {
throw new Error(
"[rango] Passthrough: first argument must be a Prerender() definition.",
);
}
return {
__brand: "passthroughHandler" as const,
prerenderDef,
liveHandler,
};
}
/**
* Type guard to check if a value is a PassthroughHandlerDefinition.
*/
export function isPassthroughHandler(
value: unknown,
): value is PassthroughHandlerDefinition {
return (
typeof value === "object" &&
value !== null &&
"__brand" in value &&
(value as { __brand: unknown }).__brand === "passthroughHandler"
);
}