import type { ReactNode } from "react"; import type { PartialCacheOptions, DefaultEnv, ErrorBoundaryHandler, ExtractRouteParams, Handler, LoaderDefinition, MiddlewareFn, NotFoundBoundaryHandler, ResolvedRouteMap, RouteDefinition, RouteDefinitionOptions, ShouldRevalidateFn, TrailingSlashMode } from "./types.js"; import { type InterceptWhenFn } from "./server/context"; import type { AllUseItems, LayoutItem, RouteItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, LoadingItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ParallelUseItem, InterceptUseItem, LoaderUseItem, WhenItem, CacheItem } from "./route-types.js"; /** * Result of route() function with paths and trailing slash config */ export interface RouteDefinitionResult { routes: ResolvedRouteMap; trailingSlash: Record; } /** * Define routes with optional trailing slash configuration * * @example * ```typescript * // Simple string paths * const routes = route({ * blog: "/blog", * post: "/blog/:id", * }); * * // With trailing slash config * const routes = route({ * blog: "/blog", * api: { path: "/api", trailingSlash: "ignore" }, * }, { trailingSlash: "never" }); // global default * ``` */ export declare function route(input: T, options?: RouteDefinitionOptions): ResolvedRouteMap & { __trailingSlash?: Record; }; export type { AllUseItems, LayoutItem, RouteItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ParallelUseItem, InterceptUseItem, WhenItem, CacheItem, } from "./route-types.js"; export type { InterceptSelectorContext, InterceptSegmentsState, InterceptWhenFn, } from "./server/context"; /** * Route helpers provided by map() * These are the only typed helpers users interact with */ export type RouteHelpers = { /** * Define a route handler for a specific route pattern * ```typescript * route("products.detail", async (ctx) => { * const product = await getProduct(ctx.params.slug); * return ; * }) * * // With nested use() for middleware, loaders, etc. * route("products.detail", ProductHandler, () => [ * loader(ProductLoader), * loading(), * ]) * ``` * @param name - Route name matching a key from route definitions * @param handler - Async function that returns JSX for the route * @param use - Optional callback returning middleware, loaders, loading, etc. */ route: & string>(name: K, handler: Handler, {}, TEnv>, use?: () => RouteUseItem[]) => RouteItem; /** * Define a layout that wraps child routes * ```typescript * layout(, () => [ * route("home", HomePage), * route("about", AboutPage), * ]) * * // With dynamic layout handler * layout(async (ctx) => { * const user = ctx.get("user"); * return ; * }, () => [ * middleware(authMiddleware), * route("dashboard", DashboardPage), * ]) * ``` * @param component - Static JSX or async handler for the layout * @param use - Callback returning child routes, middleware, loaders, etc. */ layout: (component: ReactNode | Handler, use?: () => LayoutUseItem[]) => LayoutItem; /** * Define parallel routes that render simultaneously in named slots * ```typescript * parallel({ * "@sidebar": , * "@main": async (ctx) => , * }) * * // With loaders and loading states * parallel({ * "@analytics": AnalyticsPanel, * "@metrics": MetricsPanel, * }, () => [ * loader(DashboardLoader), * loading(), * ]) * ``` * @param slots - Object with slot names (prefixed with @) mapped to handlers * @param use - Optional callback for loaders, loading, revalidate, etc. */ parallel: | ReactNode>>(slots: TSlots, use?: () => ParallelUseItem[]) => ParallelItem; /** * Define an intercepting route for soft navigation * * When soft-navigating to the target route from within the current layout, * the intercept handler renders in the named slot instead of the route's * default handler. Direct navigation uses the route's handler. * * ```typescript * // In a layout - intercept "card" route as modal * layout(, () => [ * intercept("@modal", "card", () => ), * ]) * * // With loaders and revalidation * intercept("@modal", "card", () => , () => [ * loader(CardModalLoader), * revalidate(() => false), * ]) * ``` * @param slotName - Named slot (prefixed with @) where intercept renders * @param routeName - Route name to intercept * @param handler - Component or handler for intercepted render * @param use - Optional callback for loaders, middleware, revalidate, etc. */ intercept: & string>(slotName: `@${string}`, routeName: K, handler: ReactNode | Handler, {}, TEnv>, use?: () => InterceptUseItem[]) => InterceptItem; /** * Attach middleware to the current route/layout * ```typescript * middleware(async (ctx, next) => { * const session = await getSession(ctx.request); * if (!session) return redirect("/login"); * ctx.set("user", session.user); * next(); * }) * * // Chain multiple middleware * middleware(authMiddleware, loggingMiddleware, rateLimitMiddleware) * ``` * @param fns - One or more middleware functions to execute in order */ middleware: (...fns: MiddlewareFn[]) => MiddlewareItem; /** * Control when a segment should revalidate during navigation * ```typescript * // Revalidate when params change * revalidate(({ currentParams, nextParams }) => * currentParams.slug !== nextParams.slug * ) * * // Revalidate after specific actions (actionId format: "path/to/file.ts#exportName") * revalidate(({ actionId }) => * actionId?.includes("Cart") ?? false * ) * * // Soft decision (suggest but allow override) * revalidate(({ defaultShouldRevalidate }) => * ({ defaultShouldRevalidate: true }) * ) * ``` * @param fn - Function that returns boolean (hard) or { defaultShouldRevalidate } (soft) */ revalidate: (fn: ShouldRevalidateFn) => RevalidateItem; /** * Attach a data loader to the current route/layout * ```typescript * loader(ProductLoader) * * // With loader-specific revalidation (match by file or export name) * loader(CartLoader, () => [ * revalidate(({ actionId }) => actionId?.includes("Cart") ?? false), * ]) * * // Access loader data in handlers via ctx.use() * route("products.detail", async (ctx) => { * const product = await ctx.use(ProductLoader); * return ; * }) * ``` * @param loaderDef - Loader created with createLoader() * @param use - Optional callback for loader-specific revalidation rules */ loader: (loaderDef: LoaderDefinition, use?: () => LoaderUseItem[]) => LoaderItem; /** * Attach a loading component to the current route/layout * ```typescript * // Show loading on all requests (including SSR) * loading() * * // Skip loading on SSR, only show on client navigation * loading(, { ssr: false }) * ``` * @param component - The loading UI to show during navigation * @param options - Configuration options * @param options.ssr - If false, skip showing loading on document requests (SSR) */ loading: (component: ReactNode, options?: { ssr?: boolean; }) => LoadingItem; /** * Attach an error boundary to catch errors in this segment and children * ```typescript * errorBoundary() * * // With dynamic error handler * errorBoundary(({ error, reset }) => ( *
*

Something went wrong

*

{error.message}

* *
* )) * ``` * @param fallback - Static JSX or handler receiving error info and reset function */ errorBoundary: (fallback: ReactNode | ErrorBoundaryHandler) => ErrorBoundaryItem; /** * Attach a not-found boundary to handle notFound() calls in this segment * ```typescript * notFoundBoundary() * * // With dynamic handler * notFoundBoundary(({ notFound }) => ( *
*

{notFound.message}

* Browse all products *
* )) * ``` * @param fallback - Static JSX or handler receiving not-found info */ notFoundBoundary: (fallback: ReactNode | NotFoundBoundaryHandler) => NotFoundBoundaryItem; /** * Define a condition for when an intercept should activate * * Only valid inside intercept() use() callback. When multiple when() calls * are present, ALL must return true for the intercept to activate. * If no when() is defined, the intercept always activates on soft navigation. * * Context properties: * - `from` - Source URL (where user is navigating from) * - `to` - Destination URL (where user is navigating to) * - `params` - Matched route params * - `segments` - Client's current segments with `path` and `ids` * * ```typescript * // Only intercept when coming from the board page * intercept("@modal", "card", , () => [ * when(({ from }) => from.pathname.startsWith("/board")), * loader(CardDetailLoader), * ]) * * // Use segments to check current route context * intercept("@modal", "card", , () => [ * when(({ segments }) => segments.path[0] === "kanban"), * ]) * * // Multiple conditions (AND logic) * intercept("@modal", "card", , () => [ * when(({ from }) => from.pathname.startsWith("/board")), * when(({ segments }) => segments.ids.includes("kanban-layout")), * ]) * ``` * @param fn - Selector function receiving navigation context, returns boolean */ when: (fn: InterceptWhenFn) => WhenItem; /** * Define cache configuration for segments * * Creates a cache boundary that applies to all children unless overridden. * Cache config inherits down the route tree like middleware wrapping. * * When ttl is not specified, uses store defaults (explicit store first, * then app-level store). When store is not specified, uses app-level store. * * Note: Loaders are NOT cached by default. Use cache() inside loader() * to explicitly opt-in to loader caching. * * ```typescript * // Using app-level defaults (ttl inherited from store.defaults) * cache(() => [ * layout(), // cached with default TTL * route("post/:slug"), // cached with default TTL * ]) * * // Cache all segments with explicit 60s TTL * cache({ ttl: 60 }, () => [ * layout(), // cached * route("post/:slug"), // cached * ]) * * // With stale-while-revalidate * cache({ ttl: 60, swr: 300 }, () => [ * route("product/:id"), * ]) * * // Override for specific section * cache({ ttl: 60 }, () => [ * layout(), * cache({ ttl: 300 }, () => [ * route("static-page"), // longer TTL * ]), * cache(false, () => [ * route("admin"), // not cached * ]), * ]) * * // Use different store for specific routes * cache({ store: kvStore, ttl: 3600 }, () => [ * route("archive/:year"), // uses KV store * ]) * * // Opt-in loader caching * route("product/:id", ProductHandler, () => [ * loader(ProductLoader), // NOT cached (default) * loader(StaticMetadata, () => [ * cache({ ttl: 3600 }), // cached for 1 hour * ]), * ]) * ``` * @param optionsOrChildren - Cache options, false to disable, or children callback * @param children - Optional callback returning child segments (when first arg is options) */ cache: { (): CacheItem; (children: () => AllUseItems[]): CacheItem; (options: PartialCacheOptions | false, use?: () => AllUseItems[]): CacheItem; }; }; /** * Branded type for route handlers that carries the route type info. * This enables type-safe verification that imported handlers match route definitions. */ export interface RouteHandlers { (): Array; /** Brand to carry route type info for type checking */ readonly __routes: T; } /** * Type-safe handler definition helper * */ export declare function map(builder: (helpers: RouteHelpers) => Array): RouteHandlers; /** * Create RouteHelpers for inline route definitions * Used internally by router.map() for inline handler syntax */ export declare function createRouteHelpers(): RouteHelpers; /** * Create a loader definition * * Loaders are RSC-compatible data fetchers that: * - Run after middleware, before handlers * - Are scoped to where attached (layout/route subtree) * - Revalidate independently from UI segments * - Are memoized per request (multiple ctx.use() calls return same value) * * Use the `"use server"` directive inside the loader function to ensure * the function is stripped from client bundles. * * Return type is automatically inferred from the callback. * * @param fn - Async function that fetches data (should contain "use server" directive) * @param fetchable - Optional flag to make the loader fetchable via useFetchLoader * * @example * ```typescript * // loaders/cart.ts - return type inferred from callback * export const CartLoader = createLoader(async (ctx) => { * "use server"; * const user = ctx.get("user"); * return await db.cart.get(user.id); // Return type inferred! * }); * * // loaders/product.ts - return type inferred * export const ProductLoader = createLoader(async (ctx) => { * "use server"; * const { slug } = ctx.params; * return await db.products.findBySlug(slug); // Return type inferred! * }); * * // Usage in handlers * layout(, () => [ * loader(CartLoader), * loader(CartLoader, () => [ * revalidate(({ actionId }) => actionId?.includes("Cart") ?? false), * ]), * ]) * * // Server-side access * route("cart", (ctx) => { * const cart = ctx.use(CartLoader); * return ; * }); * * // Client-side access * const cart = useLoader(CartLoader); * ``` */ export { createLoader } from "./loader.rsc.js"; /** * Create a soft redirect Response for middleware short-circuit * * Returns a Response that signals a client-side navigation to the target URL. * Unlike Response.redirect() which causes a full page reload, this redirect * is handled by the router for SPA-style navigation. * * @param url - The URL to redirect to * @param status - HTTP status code (default: 302) * * @example * ```typescript * middleware((ctx, next) => { * if (!ctx.get('user')) { * return redirect('/login'); * } * next(); * }) * ``` */ export declare function redirect(url: string, status?: number): Response; //# sourceMappingURL=route-definition.d.ts.map