import React, { ReactNode } from 'react'; import { RouteObject } from 'react-router'; import * as react_jsx_runtime from 'react/jsx-runtime'; /** * Result type indicating how the app was mounted * - "hydrated": App was hydrated over existing SSR/SSG content * - "rendered": App was rendered fresh (SPA mode) * - "not_found": Container element was not found in the DOM */ type MountAppResult = 'hydrated' | 'rendered' | 'not_found'; /** * Options for mounting the app */ type MountAppOptions = { /** * Whether to wrap the app element with React.StrictMode * @default true */ strictMode?: boolean; /** * Optional custom wrapper component for additional providers * Applied after UnirendHeadProvider but before StrictMode (StrictMode is always outermost) * Must be a React component that accepts children */ rootProviders?: React.ComponentType<{ children: React.ReactNode; }>; }; /** * Intelligently mounts a React Router-based app by detecting whether to hydrate or render. * * This is the primary function for client-side mounting in unirend. It provides a unified, * opinionated API that works seamlessly across different rendering contexts: * - SSR/SSG: Hydrates pre-rendered HTML content * - SPA: Creates a fresh root and renders the app * * The detection is based on whether the container already has child elements, * which indicates pre-rendered content that should be hydrated rather than replaced. * * @param containerID - The ID of the root DOM element (e.g., "root", "app") * @param routes - Your React Router routes configuration * @param options - Optional configuration for mounting behavior * @returns MountAppResult indicating the mounting strategy used or if it failed * * @example * ```typescript * import { mountApp } from 'unirend/client'; * import { routes } from './Routes'; * * const result = mountApp('root', routes); * * // With custom providers * const customWrapper = (node) => ( * * * {node} * * * ); * * const result = mountApp('root', routes, { wrapApp: customWrapper }); * * if (result === 'hydrated') { * console.log('Hydrated SSR content'); * } else if (result === 'rendered') { * console.log('Rendered as SPA'); * } else { * console.error('Failed to mount app'); * } * ``` */ declare function mountApp(containerID: string, routes: RouteObject[], options?: MountAppOptions): MountAppResult; /** * Parsed domain information derived from a request hostname. * * Used both on the Fastify request object (`request.domainInfo`) and in the * Unirend React context (`useDomainInfo()`). */ interface DomainInfo { /** Bare hostname with port stripped (IPv6-safe, e.g. `'app.example.com'` or `'::1'`). */ hostname: string; /** * Apex domain without a leading dot (e.g. `'example.com'`). * Empty string for localhost and raw IP addresses where no root domain can be resolved. * * When empty, omit the `domain` attribute entirely — `domain=.localhost` is invalid * per RFC 6265 and browsers reject it. A cookie without a `domain` attribute becomes * a host-only cookie scoped to the exact hostname, which is correct for localhost and IPs. * * Prepend `.` when using as a cookie `domain` attribute to span subdomains: * ```ts * document.cookie = [ * 'theme=dark', * 'path=/', * 'max-age=31536000', * domainInfo.rootDomain ? `domain=.${domainInfo.rootDomain}` : null, * ].filter(Boolean).join('; '); * ``` */ rootDomain: string; } /** * Render mode type - SSR, SSG, or Client * - "ssr": Server-Side Rendering (runtime server rendering) * - "ssg": Static Site Generation (build-time server rendering) * - "client": Client-side execution (SPA or after a SSG build/SSR page hydration occurs) */ type UnirendRenderMode = 'ssr' | 'ssg' | 'client'; /** * Unirend context value type */ interface UnirendContextValue { /** * The render mode: * - 'ssr': Server-Side Rendering * - 'ssg': Static Site Generation * - 'client': Client-side execution (SPA or after a SSG build/SSR page hydration occurs) */ renderMode: UnirendRenderMode; /** * Whether the app is running in development mode */ isDevelopment: boolean; /** * The Fetch API Request object (available during SSR/SSG rendering) * Undefined on client-side after hydration */ fetchRequest?: Request; /** * Public application configuration * This is a frozen (immutable) copy of the config passed to the server * Available on both server and client (injected into HTML during SSR/SSG) */ publicAppConfig?: Record; /** * CDN base URL for asset serving (e.g. 'https://cdn.example.com') * Available on both server (from app config or per-request override) and client * (read from window.__CDN_BASE_URL__ injected into HTML by the server) * Empty string when no CDN is configured */ cdnBaseURL?: string; /** * Domain information computed server-side from the request hostname. * Available during SSR (computed per-request) and SSG when a `hostname` option is * provided at build time. `null` when hostname is not known (SSG without hostname * configured, or pure SPA — no server to compute it via the public suffix list). */ domainInfo?: DomainInfo | null; /** * Request context revision counter for reactivity * Format: `${timestamp}-${counter}` (e.g., "1729123456789-0", "1729123456789-1") * Increments whenever request context is modified to trigger re-renders * @internal */ requestContextRevision?: string; } /** * Request context management interface */ interface RequestContextManager { /** * Get a value from the request context * @param key - The key to retrieve * @returns The value associated with the key, or undefined if not found */ get: (key: string) => unknown; /** * Set a value in the request context * @param key - The key to set * @param value - The value to associate with the key */ set: (key: string, value: unknown) => void; /** * Check if a key exists in the request context * @param key - The key to check * @returns true if the key exists, false otherwise */ has: (key: string) => boolean; /** * Delete a value from the request context * @param key - The key to delete * @returns true if the key existed and was deleted, false if it didn't exist */ delete: (key: string) => boolean; /** * Clear all values from the request context * @returns The number of keys that were cleared */ clear: () => number; /** * Get all keys from the request context * @returns An array of all keys */ keys: () => string[]; /** * Get the number of entries in the request context * @returns The number of key-value pairs */ size: () => number; } /** * Hook to check if the app is rendering in SSR mode * * @returns true if rendering mode is 'ssr', false if 'ssg' * * @example * ```tsx * function MyComponent() { * const isSSR = useIsSSR(); * * return
{isSSR ? 'Server-Side Rendered' : 'Static Generated'}
; * } * ``` */ declare function useIsSSR(): boolean; /** * Hook to check if the app is rendering in SSG mode * * @returns true if rendering mode is 'ssg', false otherwise * * @example * ```tsx * function MyComponent() { * const isSSG = useIsSSG(); * * return
{isSSG ? 'Static Generated' : 'Not SSG'}
; * } * ``` */ declare function useIsSSG(): boolean; /** * Hook to check if the app is in client mode * Returns true for SPAs or after SSG build/SSR page hydration occurs * * @returns true if rendering mode is 'client', false otherwise * * @example * ```tsx * function MyComponent() { * const isClient = useIsClient(); * * return
{isClient ? 'Client Mode' : 'Server Rendering'}
; * } * ``` */ declare function useIsClient(): boolean; /** * Hook to get the render mode * * @returns The current render mode ('ssr', 'ssg', or 'client') * * @example * ```tsx * function MyComponent() { * const renderMode = useRenderMode(); * * return
Render Mode: {renderMode}
; * } * ``` */ declare function useRenderMode(): UnirendRenderMode; /** * Hook to check if the app is running in development mode * * @returns true if in development mode, false if in production * * @example * ```tsx * function MyComponent() { * const isDev = useIsDevelopment(); * * return ( *
* {isDev &&
Development Mode - Debug Info
} *
* ); * } * ``` */ declare function useIsDevelopment(): boolean; /** * Hook to check if the code is running on the server (SSR) * This checks if fetchRequest has the SSRHelper property attached * * @returns true if on SSR server (has SSRHelper), false if on client or SSG * * @example * ```tsx * function MyComponent() { * const isServer = useIsServer(); * * return ( *
* {isServer ? 'Running on SSR server' : 'Running on client or SSG'} *
* ); * } * ``` */ declare function useIsServer(): boolean; /** * Hook to access public application configuration * This is a frozen (immutable) copy of the config passed to the server * Available on both server and client * * @returns The public app config object, or undefined if not provided * * @example * ```tsx * function MyComponent() { * const config = usePublicAppConfig(); * * if (!config) { * return
No config available
; * } * * return ( *
*

API URL: {config.api_endpoint}

*

App Name: {config.appName}

*
* ); * } * ``` */ declare function usePublicAppConfig(): Record | undefined; /** * Hook to get the CDN base URL configured for asset serving. * * Returns the effective CDN base URL for the current request — either a per-request * override (set in middleware via `request.CDNBaseURL`) or the app-level default * (`CDNBaseURL` in `serveSSRProd`/`registerProdApp` options). Returns an empty string * when no CDN is configured or when running Vite directly without the unirend server. * * Available on both server (during SSR rendering) and client (read from * `window.__CDN_BASE_URL__` injected into the page). * * @example * ```tsx * function AssetImage({ path }: { path: string }) { * const cdnBase = useCDNBaseURL(); * * return ; * } * ``` */ declare function useCDNBaseURL(): string; /** * Returns domain information computed server-side from the request hostname. * * - `hostname`: the bare requested hostname (port stripped), e.g. `'app.example.com'` * - `rootDomain`: the apex domain without a leading dot, e.g. `'example.com'`. * Empty string for localhost / IP addresses. * Prepend `.` when using as a cookie `domain` attribute to span subdomains: * ```ts * document.cookie = [ * 'theme=dark', * 'path=/', * 'max-age=31536000', * domainInfo?.rootDomain ? `domain=.${domainInfo.rootDomain}` : null, * ].filter(Boolean).join('; '); * ``` * * Returns `null` when hostname is not known — SSG without a `hostname` option * configured, or pure SPA (no server to compute it via the public suffix list). * * @example * ```tsx * function ThemeProvider({ children }) { * const domainInfo = useDomainInfo(); * // domainInfo?.hostname → 'app.example.com' * // domainInfo?.rootDomain → 'example.com' * } * ``` */ declare function useDomainInfo(): DomainInfo | null; /** * Hook to get the raw request context object for debugging purposes. * Returns a cloned, immutable copy of the entire request context. * * **Note:** This is primarily for debugging. Use `useRequestContextValue()` * or `useRequestContext()` for production code. * * @returns A cloned copy of the request context object, or undefined if not populated * * @example * ```tsx * function DebugPanel() { * const rawContext = useRequestContextObjectRaw(); * * if (!rawContext) { * return
Request context not populated
; * } * * return ( *
{JSON.stringify(rawContext, null, 2)}
* ); * } * ``` */ declare function useRequestContextObjectRaw(): Record | undefined; /** * Hook to access and manage the request context * * Returns an object with methods to get, set, check, delete, and inspect * the request context. The returned methods can be safely called in callbacks, * effects, or event handlers. * * @returns RequestContextManager object with context management methods * * @example * ```tsx * function MyComponent() { * const requestContext = useRequestContext(); * * const handleThemeChange = (theme: string) => { * requestContext.set('theme', theme); * }; * * const userID = requestContext.get('userID'); * const hasTheme = requestContext.has('theme'); * const allKeys = requestContext.keys(); * * return ( *
*

User ID: {userID}

*

Has theme: {hasTheme ? 'Yes' : 'No'}

*

Total entries: {requestContext.size()}

* * *
* ); * } * ``` */ declare function useRequestContext(): RequestContextManager; /** * Hook to access and reactively update a single request context value * * Similar to useState, this hook returns a tuple of [value, setValue] and will * cause the component to re-render when the value changes. * * @param key - The key to track in the request context * @returns A tuple of [value, setValue] similar to useState * * @example * ```tsx * function ThemeToggle() { * const [theme, setTheme] = useRequestContextValue('theme'); * * return ( *
*

Current theme: {theme || 'default'}

* * *
* ); * } * ``` */ declare function useRequestContextValue(key: string): [T | undefined, (value: T) => void]; /** * Framework-native document head manager. * * Place , <meta>, and <link> tags as direct children. * Works identically in SSR, SSG, and SPA modes. * * Server: collects tags via context for injection into the HTML template. * Client: renders tags directly; React 19 hoists them to <head>. * * @example * ```tsx * import { UnirendHead } from 'unirend/client'; * * function HomePage() { * return ( * <> * <UnirendHead> * <title>Home - My App * * * *
...
* * ); * } * ``` */ declare function UnirendHead({ children }: { children?: ReactNode; }): react_jsx_runtime.JSX.Element | null; /** * Returns the current URL's query parameters parsed with qs, supporting nested * objects and arrays (e.g. `?filters[status]=active&ids[]=1&ids[]=2`). * * The returned object matches the shape of `params.queryParams` in page data * loader handlers — so what you read in a component is consistent with what * the server handler receives. * * Re-parses only when the search string changes. * * @example * ```tsx * import { useQueryParams } from 'unirend/client'; * * interface ProductsQueryParams { * filters?: { status?: string; tags?: string[] }; * } * * function ProductsPage() { * const { filters } = useQueryParams(); * return
Status: {filters?.status}
; * } * ``` */ declare function useQueryParams>(): T; /** * Serializes a params object into a query string using qs, supporting nested * objects and arrays. Returns the string without a leading `?` — prepend one * yourself when passing to `navigate()` or building a ``. * * @example * ```ts * import { stringifyQueryParams } from 'unirend/router-utils'; * import { useNavigate } from 'react-router'; * * const navigate = useNavigate(); * navigate(`?${stringifyQueryParams({ filters: { status: 'active' }, ids: [1, 2] })}`); * // → ?filters%5Bstatus%5D=active&ids%5B0%5D=1&ids%5B1%5D=2 * ``` */ declare function stringifyQueryParams(params: Record): string; export { type DomainInfo, type MountAppOptions, type MountAppResult, type RequestContextManager, type UnirendContextValue, UnirendHead, type UnirendRenderMode, mountApp, stringifyQueryParams, useCDNBaseURL, useDomainInfo, useIsClient, useIsDevelopment, useIsSSG, useIsSSR, useIsServer, usePublicAppConfig, useQueryParams, useRenderMode, useRequestContext, useRequestContextObjectRaw, useRequestContextValue };