import type { ComponentType } from "react"; import { type ReactNode } from "react"; import type { SegmentCacheStore } from "./cache/types.js"; import { type SerializedManifest } from "./debug.js"; import { type ReverseFunction, type PrefixRoutePatterns } from "./reverse.js"; import { type RouteHandlers } from "./route-definition.js"; import type { AllUseItems } from "./route-types.js"; import type { UrlPatterns } from "./urls.js"; import type { ErrorBoundaryHandler, ErrorInfo, HandlerContext, MatchResult, NotFoundBoundaryHandler, OnErrorCallback, RouteDefinition } from "./types"; import type { NonceProvider } from "./rsc/types.js"; import { type ExecutionContext } from "./server/request-context.js"; import type { SerializedSegmentData, SegmentHandleData } from "./cache/types.js"; import { type MiddlewareEntry, type MiddlewareFn } from "./router/middleware.js"; /** * Props passed to the root layout component */ export interface RootLayoutProps { children: ReactNode; } /** * Router configuration options */ /** * Brand marker for identifying router instances at build time. * Used by the Vite plugin to auto-discover routers from module exports. */ export declare const RSC_ROUTER_BRAND: "__rsc_router__"; /** * Global registry of all router instances created via createRouter(). * Each router is keyed by its id (auto-generated or user-provided). * Used by the Vite plugin at build time to discover routers and extract * manifests, prefix trees, and pre-render candidates. */ export declare const RouterRegistry: Map>; export interface RSCRouterOptions { /** * Unique identifier for this router instance. * Used to namespace static output files and route maps. * Auto-generated if not provided. */ id?: string; /** * Injected by the Vite transform at compile time. * Hash of filename + line number for stable cross-environment ID. * @internal */ $$id?: string; /** * Enable performance metrics collection * When enabled, metrics are output to console and available via Server-Timing header */ debugPerformance?: boolean; /** * Allow the `?__debug_manifest` query parameter to return route manifest data as JSON. * In development mode this is always enabled regardless of this setting. * Defaults to true. Set to false to disable in production. * @internal */ allowDebugManifest?: boolean; /** * Document component that wraps the entire application. * * This component provides the HTML structure for your app and wraps * both normal route content AND error states, preventing the app shell * from unmounting during errors (avoids FOUC). * * Must be a client component ("use client") that accepts { children }. * * If not provided, a default document with basic HTML structure is used: * `{children}` * * @example * ```typescript * // components/Document.tsx * "use client"; * export function Document({ children }: { children: ReactNode }) { * return ( * * * * * * * {children} * * * ); * } * * // router.tsx * const router = createRouter({ * document: Document, * }); * ``` */ document?: ComponentType; /** * Default error boundary fallback used when no error boundary is defined in the route tree * If not provided, errors will propagate and crash the request */ defaultErrorBoundary?: ReactNode | ErrorBoundaryHandler; /** * Default not-found boundary fallback used when no notFoundBoundary is defined in the route tree * If not provided, DataNotFoundError will be treated as a regular error */ defaultNotFoundBoundary?: ReactNode | NotFoundBoundaryHandler; /** * Component to render when no route matches the requested URL. * * This is rendered within your document/app shell with a 404 status code. * Use this for a custom 404 page that maintains your app's look and feel. * * If not provided, a default "Page not found" component is rendered. * * Can be a static ReactNode or a function receiving the pathname. * * @example * ```typescript * // Simple static component * const router = createRouter({ * document: Document, * notFound: , * }); * * // Dynamic component with pathname * const router = createRouter({ * document: Document, * notFound: ({ pathname }) => ( *
*

404 - Not Found

*

No page exists at {pathname}

* Go home *
* ), * }); * ``` */ notFound?: ReactNode | ((props: { pathname: string; }) => ReactNode); /** * Callback invoked when an error occurs during request handling. * * This callback is for notification/logging purposes - it cannot modify * the error handling flow. Use errorBoundary() in route definitions to * customize error UI. * * The callback receives comprehensive context about the error including: * - The error itself * - Phase where it occurred (routing, middleware, loader, handler, etc.) * - Request info (URL, method, params) * - Route info (routeKey, segmentId) * - Environment/bindings * - Duration from request start * * @example * ```typescript * const router = createRouter({ * onError: (context) => { * // Send to error tracking service * Sentry.captureException(context.error, { * tags: { * phase: context.phase, * route: context.routeKey, * }, * extra: { * url: context.url.toString(), * params: context.params, * duration: context.duration, * }, * }); * }, * }); * ``` */ onError?: OnErrorCallback; /** * Cache store for segment caching. * * When provided, enables route-level caching via cache() boundaries. * The store handles persistence (memory, KV, Redis, etc.). * * Can be a static config or a function receiving env for runtime bindings. * * @example Static config * ```typescript * import { MemorySegmentCacheStore } from "rsc-router/rsc"; * * const router = createRouter({ * cache: { * store: new MemorySegmentCacheStore({ defaults: { ttl: 60 } }), * }, * }); * ``` * * @example Dynamic config with env (e.g., Cloudflare Workers with ExecutionContext) * ```typescript * const router = createRouter({ * cache: (env) => ({ * store: new CFCacheStore({ * defaults: { ttl: 60 }, * ctx: env.ctx, // ExecutionContext for non-blocking writes * }), * }), * }); * ``` */ cache?: { store: SegmentCacheStore; enabled?: boolean; } | ((env: TEnv & { ctx?: ExecutionContext; }) => { store: SegmentCacheStore; enabled?: boolean; }); /** * Theme configuration for automatic theme management. * * When provided, enables: * - ctx.theme and ctx.setTheme() in route handlers * - useTheme() hook for client components * - FOUC prevention via inline script in MetaTags * - Automatic ThemeProvider wrapping in NavigationProvider * * @example * ```typescript * const router = createRouter({ * theme: { * defaultTheme: "system", * themes: ["light", "dark"], * } * }); * * // In route handler: * route("settings", (ctx) => { * const theme = ctx.theme; // "light" | "dark" | "system" * ctx.setTheme("dark"); // Sets cookie * return ; * }); * * // In client component: * import { useTheme } from "@rangojs/router/theme"; * * function ThemeToggle() { * const { theme, setTheme, themes } = useTheme(); * return ; * } * ``` * * Use `theme: true` to enable with all defaults. */ theme?: import("./theme/types.js").ThemeConfig | true; /** * URL patterns to register with the router. * * Alternative to calling `.routes()` method - allows passing patterns * directly in the config for a more concise setup. * * @example * ```typescript * import { urls } from "@rangojs/router/server"; * * const urlpatterns = urls(({ path, layout }) => [ * path("/", HomePage, { name: "home" }), * path("/about", AboutPage, { name: "about" }), * ]); * * const router = createRouter({ * document: Document, * urls: urlpatterns, * }); * ``` */ urls?: UrlPatterns; /** * Injected by the Vite transform at compile time. * Static import of NamedRoutes from the generated named-routes file. * Provides O(1) reverse() fallback when lazy includes haven't resolved. * @internal */ $$routeNames?: Record; /** * Nonce provider for Content Security Policy (CSP). * * Can be: * - A function that returns a nonce string * - A function that returns `true` to auto-generate a nonce * - Undefined to disable nonce (default) * * The nonce will be applied to inline scripts injected by the RSC payload. * It's also available to middleware via `ctx.get('nonce')`. * * @example Auto-generate nonce * ```tsx * createRouter({ * nonce: () => true, * }); * ``` * * @example Custom nonce from request context * ```tsx * createRouter({ * nonce: (request, env) => env.nonce, * }); * ``` */ nonce?: NonceProvider; /** * RSC version string included in metadata. * The browser sends this back on partial requests to detect version mismatches. * * Defaults to the auto-generated VERSION from `@rangojs/router:version` virtual module. * Only set this if you need a custom versioning strategy. * * @default VERSION from @rangojs/router:version */ version?: string; /** * Enable connection warmup to keep TCP+TLS alive after idle periods. * * When enabled, the client sends a HEAD request after the user returns * from an idle period (60s+), prewarming the TLS connection before * the next navigation. * * @default true */ warmup?: boolean; } /** * Merge route patterns with response types into a single route map. * Routes with response types get { path, response } objects; others stay as strings. * Handles both plain string routes and { path, search } object routes. */ type MergeRoutesWithResponses, TResponses> = { [K in keyof TRoutes]: K extends keyof NonNullable ? unknown extends NonNullable[K] ? TRoutes[K] : TRoutes[K] extends { readonly path: infer P extends string; } ? TRoutes[K] & { readonly response: NonNullable[K]; } : { readonly path: TRoutes[K] & string; readonly response: NonNullable[K]; } : TRoutes[K]; }; /** * Extract the URL pattern from a route entry (string or { path, response } object) */ type PatternOfEntry = V extends string ? V : V extends { readonly path: infer P extends string; } ? P : never; /** * Type-level detection of conflicting route keys. * Extracts keys that exist in both TExisting and TNew but with different URL patterns. * Returns `never` if no conflicts exist. * Compares patterns (not full entries) to handle both string and { path, response } values. * * @example * ```typescript * ConflictingKeys<{ a: "/a" }, { a: "/b" }> // "a" (conflict - same key, different URLs) * ConflictingKeys<{ a: "/a" }, { a: "/a" }> // never (no conflict - same key and URL) * ConflictingKeys<{ a: "/a" }, { b: "/b" }> // never (no conflict - different keys) * ``` */ type ConflictingKeys, TNew extends Record> = { [K in keyof TExisting & keyof TNew]: PatternOfEntry extends PatternOfEntry ? PatternOfEntry extends PatternOfEntry ? never : K : K; }[keyof TExisting & keyof TNew]; /** * Error type returned when route keys conflict. * Methods require an impossible `never` parameter so TypeScript errors at the call site. */ type RouteConflictError = { __error: `Route key conflict! Key "${TConflicts}" already exists with a different URL pattern.`; hint: "Route keys must be globally unique. Use prefixed names like 'blog.index' instead of 'index'."; conflictingKeys: TConflicts; routes: (__conflict: `Fix route key conflict: "${TConflicts}" is already defined with a different URL pattern`) => never; map: (__conflict: `Fix route key conflict: "${TConflicts}" is already defined with a different URL pattern`) => never; }; /** * Simplified route helpers for inline route definitions. * Uses TRoutes (Record) instead of RouteDefinition. * * Note: Some helpers use `any` for context types as a trade-off for simpler usage. * The main type safety is in the `route` helper which enforces valid route names. * For full type safety, use the standard map() API with separate handler files. */ type InlineRouteHelpers, TEnv> = { /** * Define a route handler for a specific route pattern */ route: (name: K, handler: ((ctx: HandlerContext<{}, TEnv>) => ReactNode | Promise) | ReactNode) => AllUseItems; /** * Define a layout that wraps child routes */ layout: (component: ReactNode | ((ctx: HandlerContext) => ReactNode | Promise), use?: () => AllUseItems[]) => AllUseItems; /** * Define parallel routes */ parallel: (slots: Record<`@${string}`, ReactNode | ((ctx: HandlerContext) => ReactNode | Promise)>, use?: () => AllUseItems[]) => AllUseItems; /** * Define route middleware */ middleware: (fn: (ctx: any, next: () => Promise) => Promise) => AllUseItems; /** * Define revalidation handlers */ revalidate: (fn: (ctx: any) => boolean | Promise) => AllUseItems; /** * Define data loaders */ loader: (loader: any, use?: () => AllUseItems[]) => AllUseItems; /** * Define loading states */ loading: (component: ReactNode) => AllUseItems; /** * Define error boundaries */ errorBoundary: (handler: ReactNode | ((props: { error: Error; }) => ReactNode)) => AllUseItems; /** * Define not found boundaries */ notFoundBoundary: (handler: ReactNode | ((props: { pathname: string; }) => ReactNode)) => AllUseItems; /** * Define intercept routes */ intercept: (name: string, handler: ReactNode | ((ctx: HandlerContext) => ReactNode | Promise), use?: () => AllUseItems[]) => AllUseItems; /** * Define when conditions for intercepts */ when: (condition: (ctx: any) => boolean | Promise) => AllUseItems; /** * Define cache configuration */ cache: (config: { ttl?: number; swr?: number; } | false, use?: () => AllUseItems[]) => AllUseItems; }; /** * Router builder for chaining .use() and .map() * TRoutes accumulates all registered route types through the chain * TLocalRoutes contains the routes for the current .routes() call (for inline handler typing) */ interface RouteBuilder, TLocalRoutes extends Record = Record> { /** * Add middleware scoped to this mount * Called between .routes() and .map() * * @example * ```typescript * .routes("/admin", adminRoutes) * .use(authMiddleware) // All of /admin/* * .use("/danger/*", superAuth) // Only /admin/danger/* * .map(() => import("./admin")) * ``` */ use(patternOrMiddleware: string | MiddlewareFn, middleware?: MiddlewareFn): RouteBuilder; /** * Map routes to handlers * * Supports two patterns: * * 1. Lazy loading (code-split): * ```typescript * .routes(homeRoutes) * .map(() => import("./handlers/home")) * ``` * * 2. Inline definition: * ```typescript * .routes({ index: "/", about: "/about" }) * .map(({ route }) => [ * route("index", () => ), * route("about", () => ), * ]) * ``` */ map) => Array>(handler: H): RSCRouter; map(handler: () => Array | Promise<{ default: RouteHandlers; }> | Promise>): RSCRouter; /** * Accumulated route map for typeof extraction * Used for module augmentation: `type AppRoutes = typeof _router.routeMap` */ readonly routeMap: TRoutes; } /** * RSC Router interface * TRoutes accumulates all registered route types through the builder chain */ export interface RSCRouter = Record> { /** * Brand marker for build-time discovery. * The Vite plugin uses this to identify router instances in module exports. */ readonly __brand: typeof RSC_ROUTER_BRAND; /** * Unique identifier for this router instance. * Used to namespace static output and isolate route maps between routers. */ readonly id: string; /** * Register routes with a prefix * Route keys stay unchanged, only URL patterns get the prefix applied. * This enables composable route modules that work regardless of mount point. * * @throws Compile-time error if route keys conflict with previously registered routes */ routes>(prefix: TPrefix, routes: T): ConflictingKeys> extends never ? RouteBuilder, T> : RouteConflictError> & string>; /** * Register routes without a prefix * Route types are accumulated through the chain * * @throws Compile-time error if route keys conflict with previously registered routes */ routes>(routes: T): ConflictingKeys extends never ? RouteBuilder : RouteConflictError & string>; /** * Register routes using Django-style URL patterns * This is the new API for @rangojs/router - call once with urls() result * * @example * ```typescript * createRouter({}) * .routes(urlpatterns) // Single call with urls() result * ``` */ routes>(patterns: T): RSCRouter extends Record ? MergeRoutesWithResponses, T["_responses"]> : Record)>; /** * Add global middleware that runs on all routes * Position matters: middleware before any .routes() is global * * @example * ```typescript * createRouter({ document: RootLayout }) * .use(loggerMiddleware) // All routes * .use("/api/*", rateLimiter) // Pattern match * .routes(homeRoutes) * .map(() => import("./home")) * ``` */ use(patternOrMiddleware: string | MiddlewareFn, middleware?: MiddlewareFn): RSCRouter; /** * Type-safe URL builder for registered routes * Types are inferred from the accumulated route registrations * Route keys stay unchanged regardless of mount prefix. * * @example * ```typescript * // Given: .routes("/shop", { cart: "/cart", detail: "/product/:slug" }) * router.reverse("cart"); // "/shop/cart" * router.reverse("detail", { slug: "widget" }); // "/shop/product/widget" * ``` */ reverse: ReverseFunction; /** * Accumulated route map for typeof extraction * Used for module augmentation: `type AppRoutes = typeof _router.routeMap` * * @example * ```typescript * const _router = createRouter() * .routes(homeRoutes).map(() => import('./home')) * .routes('/shop', shopRoutes).map(() => import('./shop')); * * type AppRoutes = typeof _router.routeMap; * * declare global { * namespace RSCRouter { * interface RegisteredRoutes extends AppRoutes {} * } * } * ``` */ readonly routeMap: TRoutes; /** * Root layout component that wraps the entire application * Access this to pass to renderSegments */ readonly rootLayout?: ComponentType; /** * Error callback for monitoring/alerting * Called when errors occur in loaders, actions, or routes */ readonly onError?: RSCRouterOptions["onError"]; /** * Cache configuration (for internal use by RSC handler) */ readonly cache?: RSCRouterOptions["cache"]; /** * Not found component to render when no route matches (for internal use by RSC handler) */ readonly notFound?: RSCRouterOptions["notFound"]; /** * Resolved theme configuration (null if theme not enabled) * Used by NavigationProvider to include ThemeProvider and by MetaTags to render theme script */ readonly themeConfig: import("./theme/types.js").ResolvedThemeConfig | null; /** * Whether connection warmup is enabled. * When true, the client sends HEAD /?_rsc_warmup after idle periods * and the server responds with 204 No Content. */ readonly warmupEnabled: boolean; /** * Whether ?__debug_manifest is allowed in production. * Always enabled in development. * @internal */ readonly allowDebugManifest: boolean; /** * App-level middleware entries (for internal use by RSC handler) * These wrap the entire request/response cycle */ readonly middleware: MiddlewareEntry[]; /** * Nonce provider for CSP (for internal use by createHandler) */ readonly nonce?: NonceProvider; /** * RSC version string (for internal use by createHandler) */ readonly version?: string; /** * URL patterns reference for build-time manifest generation * @internal */ readonly urlpatterns?: UrlPatterns; /** * Source file path where createRouter() was called. * Set via Error.stack parsing at construction time. * Used by the Vite plugin to write per-router named-routes.gen.ts files. * @internal */ readonly __sourceFile?: string; match(request: Request, context: TEnv): Promise; /** * Build-time pre-render match. Resolves segments with a BuildContext * (no request/env/headers/cookies), skipping middleware and loaders. * Used by the Vite plugin to collect pre-render data at build time. * @internal */ matchForPrerender(pathname: string, params: Record): Promise<{ segments: SerializedSegmentData[]; handles: Record; routeName: string; params: Record; } | null>; /** * Preview match - returns route middleware without segment resolution. * Also returns responseType and handler for response routes (non-RSC short-circuit). */ previewMatch(request: Request, context: TEnv): Promise<{ routeMiddleware?: Array<{ handler: import("./router/middleware.js").MiddlewareFn; params: Record; }>; responseType?: string; handler?: Function; params?: Record; negotiated?: boolean; } | null>; matchPartial(request: Request, context: TEnv, actionContext?: { actionId?: string; actionUrl?: URL; actionResult?: any; formData?: FormData; }): Promise; /** * Match an error to the nearest error boundary and return error segments * * Used when an action or other operation fails and we need to render * the error boundary UI. Finds the nearest errorBoundary in the route tree * for the current URL and renders it with the error info. * * @param request - The current request (used to match the route) * @param context - Environment context * @param error - The error that occurred * @param segmentType - Type of segment where error occurred (default: "route") * @returns MatchResult with error segment, or null if no error boundary found */ matchError(request: Request, context: TEnv, error: unknown, segmentType?: ErrorInfo["segmentType"]): Promise; /** * @internal * Debug utility to serialize the manifest for inspection * Returns a JSON-friendly representation of all routes and layouts */ debugManifest(): Promise; /** * Handle an RSC request. * * Uses the router's configuration (nonce, version, cache) automatically. * The handler is lazily created on first call. * * @example Cloudflare Workers * ```tsx * import { router } from "./router"; * * export default { fetch: router.fetch }; * ``` * * @example Direct export * ```tsx * const router = createRouter({ * document: Document, * urls: urlpatterns, * nonce: () => true, * }); * * export const fetch = router.fetch; * ``` */ fetch(request: Request, env: TEnv & { ctx?: ExecutionContext; }): Promise; } /** * Create an RSC router with generic context type * Route types are accumulated automatically through the builder chain * * @example * ```typescript * interface AppContext { * db: Database; * user?: User; * } * * const router = createRouter({ * debugPerformance: true // Enable metrics * }); * * // Route types accumulate through the chain - no module augmentation needed! * // Keys stay unchanged, only URL patterns get the prefix * router * .routes(homeRoutes) // accumulates homeRoutes * .map(() => import('./home')) * .routes('/shop', shopRoutes) // accumulates shopRoutes with prefixed URLs * .map(() => import('./shop')); * * // router.reverse now has type-safe autocomplete for all registered routes * // Given shopRoutes = { cart: "/cart" }, reverse uses original key: * router.reverse("cart"); // "/shop/cart" * ``` */ export declare function createRouter(options?: RSCRouterOptions): RSCRouter; export {}; //# sourceMappingURL=router.d.ts.map