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
;
* }
* ```
*/
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 , , and 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 .
*
* @example
* ```tsx
* import { UnirendHead } from 'unirend/client';
*
* function HomePage() {
* return (
* <>
*
* 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 };