react-edge

0.1.233 • Public • Published

React Edge Framework 🚀

A revolutionary React framework for building blazing-fast applications on the edge, powered by Cloudflare Workers.

TypeScript Vitest MIT License

🚀 Motivation

Read the full story on DEV.to

Index

Installation

npm install react-edge

Features

  • Edge-First Architecture: Built from the ground up for Cloudflare Workers, enabling server-rendering and API execution at the edge.
  • File-Based Routing: Simple and intuitive routing system using app/index.ts.
  • Integrated RPC: Type-safe client-server communication via RPC, eliminating the need for traditional API routes.
  • Powerful Data Fetching: Declarative data fetching hooks (fetchRpc, fetchHttp) via factories (useFetchRpc, useFetchHttp) with SSR, hydration, dependency tracking, debouncing, batching, and polling. Automatic key generation for fetchRpc hydration.
  • SSR & Streaming: Server-renders React components and streams HTML for fast TTI, with automatic hydration on the client.
  • Context API: Provides access to request details (URL, path params, headers, cf properties) and shared state (store) on both server and client.
  • Component-Level Caching: Fine-grained caching control for pages, API responses, and redirects using tags and TTLs via EdgeCache.
  • Middleware Support: Apply middleware functions to routes or route groups for cross-cutting concerns (auth, logging, etc.).
  • Built-in Utilities: Includes helpers for cookies, JWT, crypto, headers, bot detection, state management (useUrlState, useStorageState), i18n, and more, often leveraging use-request-utils.
  • Developer Experience: Fast HMR with Vite, simple configuration, strong TypeScript integration.
  • Performance: Optimized for edge environments, minimizing bundle size and latency.

Core Concepts

Worker Entry (WorkerEntry)

(/app/worker-entry.tsx)

The main class responsible for handling incoming requests in the Cloudflare Worker environment. It orchestrates routing, middleware execution, RPC handling, server-side rendering, and response generation.

Constructor

new WorkerEntry(options: WorkerEntry.Options)

Parameters:

  • options (WorkerEntry.Options):
    • config (Config): Application configuration (versions, dev server info, etc.).
    • router (App.Router): The application's route definitions created using createRoute, createRouteGroup, etc.
    • request (Request): The incoming HTTP request object.
    • cache? (EdgeCache, optional): An instance of EdgeCache for Cloudflare Cache API interaction.
    • cors? (boolean | WorkerEntry.Cors, optional): Enables or configures CORS headers. true enables default permissive CORS.
    • i18n? (I18n, optional): An object containing translations keyed by language code (e.g., { en: {...}, es: {...} }).
    • lang? (string, optional, default: 'en'): The default language for the request if not otherwise determined.
    • rpc? (Rpc, optional): An instance of your root RPC service class (extending Rpc). Required if using RPC.
    • url? (URL, optional): The URL object for the request (defaults to parsing request.url).

fetch(): Promise<Response>

The primary method that processes the incoming request based on its method and path, determines the appropriate handler (route, RPC, or fallback), executes it (including middlewares, SSR, data fetching), and returns the final Response. It automatically handles CORS preflight (OPTIONS) requests.

CORS Handling

If options.cors is enabled, WorkerEntry automatically handles OPTIONS requests and adds appropriate Access-Control-* headers to responses based on the configuration.

Caching Integration

If an EdgeCache instance is provided via options.cache, WorkerEntry interacts with it:

  • Checks the cache for matching requests (GET, HEAD) before executing route handlers.
  • Uses the cache options defined in route handlers (page, response, redirect) to store responses in the cache with specified TTLs and tags.

Client Entry (renderClient)

(/app/client-entry.tsx)

The function responsible for initializing and rendering/hydrating the React application in the browser.

renderClient(input: { errorBoundary?: ComponentType; render?: (component: any, node?: HTMLElement) => void; router: App.Router; }, waitRenderFlag?: boolean): Promise<void>

Parameters:

  • input (object):
    • router (App.Router): The same router configuration object used in the worker. Required to find the correct page component.
    • errorBoundary? (ComponentType, optional): A custom React Error Boundary component. Defaults to DefaultErrorBoundary.
    • render? ((component: any, node?: HTMLElement) => void, optional): A custom rendering function. If not provided, uses hydrateRoot (if #root has content) or createRoot().render.
  • waitRenderFlag? (boolean, optional, default: true): If true, waits for the window.$RENDER flag (set by the server stream) before rendering to ensure all initial data is available. Set to false for immediate rendering (less common).

Process:

  1. Waits for the DOM and necessary window flags ($ROUTER_STATE, $RENDER).
  2. Retrieves initial state (router state, i18n data, preloaded store data, preloaded fetch data) from window flags set during SSR.
  3. Identifies the correct page component using the router configuration and the hydrated router state.
  4. Initializes i18n.
  5. Creates the initial App.Context value.
  6. Renders the application inside necessary providers (StrictMode, ErrorBoundary, AppContext, HelmetProvider).
  7. Uses hydrateRoot if the #root element already contains server-rendered HTML, otherwise uses createRoot().render.

Routing

(app/router-builder.ts, use-request-utils/router)

React Edge uses a declarative approach to define routes, middlewares, and handlers. Routes are typically defined in a central file (e.g., src/app/router.ts) and passed to WorkerEntry.

createRoute

Defines a single route handler.

createRoute<R>(route: {
  // Handler definition (Page, Redirect, or API Response)
  handler: App.RouteHandler<R>;
  // Optional HTTP method (defaults to 'GET')
  method?: App.RouteMethod;
  // Path pattern(s) - string or array of strings
  path: string | string[];
}): App.Route<R>
  • handler: Specifies what happens when this route matches. Can be one of:
    • page: Renders a React component.
      • value: The React Component (ComponentType or FC).
      • cache?: Optional EdgeCache settings (boolean or { tags?: string[], ttlSeconds?: number }).
      • headers?: Optional Headers or an async function returning Headers to merge with the response.
      • static?: If true, hints that the page content is static (optimizations might apply, hydration behavior differs).
    • redirect: Performs an HTTP redirect.
      • value: A Redirect object ({ url: string; status?: number; headers?: Headers }) or an async function returning { cache?: App.CacheOptions; value: Redirect }.
      • cache?: Optional EdgeCache settings (only if value is an object, not a function).
    • response: Returns a raw Response object (e.g., for API endpoints).
      • value: A Response object or an async function returning { cache?: App.CacheOptions; value: Response }.
      • cache?: Optional EdgeCache settings (only if value is an object, not a function).
    • middlewares?: An array of RouteMiddleware functions to execute before the main handler.
  • method?: The HTTP method (e.g., 'POST', 'PUT'). Defaults to 'GET'.
  • path: A path string (e.g., '/users/:id') or an array of path strings that map to this handler. See use-request-utils/router for path syntax.

createRouteGroup

Groups multiple routes under a common path prefix and/or shared middlewares.

createRouteGroup<R>(group: {
  // Base path for all routes in this group
  path: string;
  // Optional middleware(s) applied to all routes in the group *before* route-specific middleware
  middlewares?: App.RouteMiddleware<R> | App.RouteMiddleware<R>[];
  // Array of Route definitions created with `createRoute`
  routes: App.Route<R>[];
}): App.RouteGroup<R>

createRouteFallback

Defines the handler used when no other route matches the request path and method.

createRouteFallback<R>(fallback: {
  // Handler definition (Page, Redirect, or Response)
  // Cannot have middlewares directly here. Apply globally if needed.
  handler: App.RouteHandler<R>;
}): App.RouteHandler<R>

Router Instance

The routerBuilder.router(config) function processes the configuration and returns a function that performs the matching, along with the underlying use-request-utils/router instance.

const routerInstance = routerBuilder.router<MyRpcType>(myRouterConfig);

// Usage (typically internal within WorkerEntry):
const { handler, pathParams, rawPath } = routerInstance({ method: 'GET', path: '/some/path' });

Path Matching

Path matching uses the underlying use-request-utils/router. See its documentation for details on:

  • Static paths
  • Parameters (:id)
  • Optional parameters (:id?)
  • Regex-constrained parameters (:id{\\d+})
  • Wildcards (*)
  • Parameter type inference

Usage Example

// src/app/router.ts
import app from 'react-edge/app';
import { PageHome, PageAbout, PageUser, PageAdminUsers } from '@/app/pages';
import { checkAuth, logRequest } from '@/app/middlewares';
import type { MyRpc } from '@/worker/rpc'; // Your defined RPC type

const router: App.Router<MyRpc> = {
	// Fallback for 404s (can be a page, response, or redirect)
	fallback: app.createRouteFallback({
		handler: { response: { value: new Response('Not Found', { status: 404 }) } }
	}),

	routes: [
		// Simple page route
		app.createRoute({ path: '/', handler: { page: { value: PageHome } } }),

		// Page with cache settings
		app.createRoute({ path: '/about', handler: { page: { value: PageAbout, cache: { ttlSeconds: 3600 } } } }),

		// Page with path parameter and middleware
		app.createRoute({
			path: '/users/:userId',
			handler: {
				middlewares: [logRequest], // Executes first
				page: { value: PageUser }
			}
		}),

		// API-like response route
		app.createRoute({
			path: '/api/health',
			method: 'GET',
			handler: { response: { value: Response.json({ status: 'ok' }) } }
		}),

		// Redirect route
		app.createRoute({
			path: '/old-path',
			handler: { redirect: { value: { url: '/new-path', status: 301 } } }
		}),

		// Route group with shared middleware
		app.createRouteGroup({
			path: '/admin',
			middlewares: [checkAuth], // Applied to all routes below
			routes: [
				app.createRoute({ path: '/users', handler: { page: { value: PageAdminUsers } } })
				// ... other admin routes
			]
		})
	]
};

export default router;

Context (useContext)

(/app/use-context.ts)

Provides access to request-specific data and shared state within React components. It behaves differently on the server (worker) and the client (browser).

useContext<C extends App.Context>() => C

  • Server/Worker: Returns the current App.WorkerContext associated with the request being processed. This context is populated by WorkerEntry and includes the server-side RPC client proxy (if an RPC service is configured).
  • Client/Browser: Returns an App.Context object. Initial path, pathParams, rawPath, searchParams, and store values are hydrated from data embedded in the initial HTML (window.$ROUTER_STATE, window.$STORE). The rpc property is initialized with a client-side RPC proxy that sends requests to the /api/rpc endpoint. The url property reflects the current window.location.

App.Context Type

type App.Context<R = DefaultRpc> = {
  path: string;          // The matched path segment (e.g., '/users/123')
  pathParams: Dict;      // Object of extracted path parameters (e.g., { userId: '123' })
  rawPath: string;       // The original path pattern from the route definition (e.g., '/users/:userId')
  rpc: RpcProxy.Proxy<R, true>; // Client-side RPC proxy (or server-side proxy in worker)
  searchParams: Dict;    // Object of query string parameters
  store: MapStore;       // A MapStore instance for request-scoped state sharing
  url: URL;              // The current URL object
};

Usage Example

import app from 'react-edge/app';
import type { App } from 'react-edge/types';
import type { MyRpc } from '@/worker/rpc'; // Your RPC service type

function UserInfo() {
  // Specify your RPC type for full type safety
  const { pathParams, searchParams, store, url, rpc } = app.useContext<App.Context<MyRpc>>();

  // Get the fetchRpc hook instance
  const { fetchRpc } = app.useFetchRpc<MyRpc>();

  // Access route parameters
  const userId = pathParams.userId;

  // Access query parameters
  const source = searchParams.ref;

  // Access shared state (e.g., set by middleware)
  const theme = store.get('theme', 'light');

  // Access URL info
  const hostname = url.hostname;

  // Use fetchRpc hook
  const { data: user } = fetchRpc(
    async ctx => (userId ? ctx.rpc.users.getProfile(Number(userId)) : null),
    { triggerDeps: [userId] } // Key is auto-generated
  );

  return (
    <div>
      User ID: {userId} <br />
      Source: {source} <br />
      Theme: {theme} <br />
      Hostname: {hostname} <br />
      Profile Name: {user?.name}
    </div>
  );
}

RPC (Remote Procedure Call)

(use-request-utils/rpc, use-request-utils/rpc-proxy, /app/use-context.ts)

React Edge includes a type-safe RPC system built on use-request-utils for client-server communication.

Defining RPC Services (Rpc)

Extend the Rpc class from use-request-utils/rpc to define your server-side methods.

// src/worker/rpc/users.ts
import Rpc from 'use-request-utils/rpc';
import HttpError from 'use-http-error';

interface User {
	id: number;
	name: string;
}

// Assume db is available (e.g., via constructor or env binding)
declare const db: { users: { findById: (id: number) => Promise<User | null> } };

export class UserRpc extends Rpc {
	async getProfile(id: number): Promise<User> {
		if (id <= 0) {
			throw new HttpError(400, 'Invalid ID');
		}
		const user = await db.users.findById(id);
		if (!user) {
			throw new HttpError(404, 'User not found');
		}
		// Caching is now typically handled by EdgeCache in WorkerEntry based on route handler config
		return user; // Return data directly
	}

	// Private method, not exposed via RPC
	private _validate(data: any) {
		/* ... */
	}
}

// src/worker/rpc/index.ts
import Rpc from 'use-request-utils/rpc';
import { UserRpc } from './users';
// Import other RPC modules...

export class RootRpc extends Rpc {
	// Nest RPC services
	public users = new UserRpc();
	// public products = new ProductRpc(...);

	// Method on the root
	async healthCheck(): Promise<{ status: string }> {
		return { status: 'ok' };
	}
}

Refer to the RPC Base Class section for more details on Rpc, this.context, createResponse, hooks ($onBeforeRequest, $onAfterResponse).

Client RPC Proxy (rpcProxy)

(use-request-utils/rpc-proxy)

On the client-side (and server-side within WorkerEntry), an RPC proxy is used to call the server methods. The useContext hook provides this proxy automatically on the client.

import app from 'react-edge/app';
import type { App } from 'react-edge/types';
import type { RootRpc } from '@/worker/rpc';

function MyComponent() {
	const { rpc } = app.useContext<App.Context<RootRpc>>();

	const handleLoadUser = async () => {
		try {
			// Call the RPC method via the proxy
			const user = await rpc.users.getProfile(123);
			console.log('User:', user);

			// Call with different response types
			const userResponse = await rpc.users.getProfile.asResponse(456);
			const userObject = await rpc.users.getProfile.asObject(789);

			// Batch multiple calls
			const [health, user2] = await rpc.batch([rpc.healthCheck(), rpc.users.getProfile(2)]);
			console.log('Health:', health, 'User 2:', user2);
		} catch (error) {
			console.error('RPC Error:', error);
		}
	};
	// ...
}

Refer to the RPC Proxy section for details on create, createTestCaller, response types (asObject, asResponse), and batching.

Server-Side RPC Handling

The WorkerEntry automatically handles incoming POST requests to /api/rpc. It extracts the RPC payload from the FormData, validates it, and uses the configured Rpc instance's fetch method to execute the call(s).

Here's a simplified view of the internal logic in WorkerEntry:

// Simplified logic within WorkerEntry.fetch for POST /api/rpc
import _ from 'lodash';
import { Request as StandardRequest } from '@cloudflare/workers-types'; // Use standard Request type
import HttpError from 'use-http-error';
import Rpc from 'use-request-utils/rpc';

async function handleRpcRequest(req: StandardRequest /* Incoming Request */, rpcService: Rpc /* Instantiated RootRpc */) {
	try {
		if (!rpcService) throw new HttpError(400, 'RPC is not available');
		if (!req.headers.get('content-type')?.includes('multipart/form-data')) {
			throw new HttpError(400, 'Invalid content type, must be multipart/form-data');
		}

		const form = await req.formData();
		const formBody = form.get('body'); // Optional Blob/File part
		const formRpc = form.get('rpc'); // RPC payload JSON string

		if (typeof formRpc !== 'string') {
			throw new HttpError(400, 'Missing RPC payload');
		}

		const rpc = Rpc.parseString(formRpc);

		if (!_.isPlainObject(rpc)) {
			// Basic validation
			throw new HttpError(400, 'Invalid RPC payload structure');
		}

		// Reconstruct a Request-like object for the Rpc.fetch method,
		// preserving essential details like headers, cf props, and the optional body part.
		const internalReq = new Request(req.url, {
			body: formBody instanceof Blob ? formBody : null, // Handle potential file body
			// @ts-ignore - Cloudflare specific properties might need casting
			cf: req.cf,
			headers: req.headers,
			method: req.method
		});

		// Dispatch to the main Rpc handler
		return await rpcService.fetch(rpc, internalReq);
	} catch (err) {
		return HttpError.response(err as Error | HttpError);
	}
}

Data Fetching Hooks

React Edge provides specialized hooks for data fetching, built upon a common factory (/app/use-fetch-hook-factory.ts). These hooks streamline data loading, manage state, handle SSR/hydration, and offer advanced features like debouncing and polling.

Hook Factories (useFetchRpc, useFetchHttp)

Instead of being the hooks themselves, useFetchRpc and useFetchHttp are factory functions that you call once (typically at the top level of your component) to get an object containing the actual fetch hooks.

import app from 'react-edge/app';
import type { App } from 'react-edge/types';
import type { MyRpc } from '@/worker/rpc'; // Your RPC type

function MyComponent() {
	// Get the hook functions from the factories
	const { fetchRpc, lazyFetchRpc } = app.useFetchRpc<MyRpc>();
	const { fetchHttp, lazyFetchHttp } = app.useFetchHttp();

	// Now use fetchRpc, lazyFetchHttp, etc.
	// ...
}

fetchRpc

(Returned by useFetchRpc)

The primary hook for calling RPC methods from React components. Fetches data automatically on component mount (unless shouldFetch prevents it).

fetchRpc<T, Mapped = T>(
  fetchFn: (context: App.Context, ...args: any[]) => Promise<T> | null,
  options?: Omit<UseFetchClientOptions<T, Mapped>, 'key'> // Key is auto-generated
): UseFetchResponse<Mapped>
  • fetchFn: An async function receiving App.Context (with rpc proxy) and arguments from the returned fetch function. Return the promise from your RPC call or null to skip.
  • options?: Configuration object (see Hook Options & Return Value). The key for SSR hydration is automatically generated.

lazyFetchRpc

(Returned by useFetchRpc)

A variation of fetchRpc that does not fetch data automatically on component mount. Fetching must be triggered manually via the returned fetch method or by triggerDeps.

lazyFetchRpc<T, Mapped = T>(
  fetchFn: (context: App.Context, ...args: any[]) => Promise<T> | null,
  options?: Pick<UseFetchClientOptions<T, Mapped>, 'effect' | 'ignoreAbort' | 'mapper'>
): UseFetchResponse<Mapped>

fetchHttp

(Returned by useFetchHttp)

Similar to fetchRpc, but uses the enhanced fetch.http client instead of the RPC proxy. Useful for calling external APIs or non-RPC endpoints. Fetches automatically on mount.

fetchHttp<T, Mapped = T>(
  fetchFn: (fetch: Fetch.Http, ...args: any[]) => Promise<T> | null,
  options: UseFetchClientOptions<T, Mapped> // Options including a required 'key'
): UseFetchResponse<Mapped>
  • fetchFn: Receives the fetch.http client from use-request-utils/fetch.
  • options: Configuration object (see below).

lazyFetchHttp

(Returned by useFetchHttp)

Lazy version of fetchHttp. Does not fetch on mount.

lazyFetchHttp<T, Mapped = T>(
  fetchFn: (fetch: Fetch.Http, ...args: any[]) => Promise<T> | null,
  options?: Pick<UseFetchClientOptions<T, Mapped>, 'effect' | 'ignoreAbort' | 'mapper'>
): UseFetchResponse<Mapped>

Hook Options & Return Value

The hooks returned by the factories (fetchRpc, lazyFetchRpc, fetchHttp, lazyFetchHttp) accept the fetchFn and options described below and return the same state structure.

Options (UseFetchClientOptions<T, Mapped>):

  • effect? (({ client: Fetch.Http, data: T}) => void): Function to run after fetching. Receives the the fetch.http client and fetched data.
  • ignoreAbort? (boolean): If true, don't abort previous pending requests on re-fetch.
  • mapper? (({ client: Fetch.Http, data: T }) => Mapped): Function to transform fetched data (T) into a different shape (Mapped).
  • shouldFetch? (boolean | (args) => boolean): Control whether fetching occurs. Function receives { initial, loaded, loadedTimes, loading, worker }. Default is true.
  • triggerDeps? (any[]): Trigger re-fetch when these dependencies change, without necessarily changing function arguments..
  • triggerDepsDebounce? (number): Debounce time (ms) for triggerDeps changes (min 50ms).
  • triggerInterval? (number): Fetch repeatedly at this interval (ms, min 500).

Return Value (UseFetchResponse<Mapped>):

  • abort(): Function to abort the current request and stop intervals.
  • data (Mapped | null): The latest successfully fetched (and mapped) data.
  • error (HttpError | null): Error object if the last fetch failed.
  • fetch(...args: any[]): Function to manually trigger a fetch, passing arguments to the hook's fetchFn.
  • fetchTimes (number): Counter for fetches.
  • lastFetchDuration (number): Duration of the last fetch in ms.
  • loaded (boolean): True if data has loaded successfully at least once.
  • loadedTimes (number): Counter for successful loads.
  • loading (boolean): True if a fetch is currently in progress.
  • reset(): Function to reset the hook's state to initial values, aborting requests and stopping intervals.
  • resetted (boolean): True immediately after reset() is called.
  • runningInterval (number): The active polling interval in ms (0 if none).
  • startInterval(interval?: number): Function to start interval polling.
  • stopInterval(): Function to stop interval polling.

Server-Side Rendering (SSR) & Hydration

The data fetching hooks (fetchRpc, fetchHttp) are designed to work seamlessly with SSR and client-side hydration:

  1. Worker (SSR):
    • When a component using fetchRpc or fetchHttp renders on the server, the framework detects these hooks.
    • If shouldFetch allows (considering { worker: true }), the hook's fetchFn is executed synchronously (made possible by build-time transforms).
    • Important: For this synchronous execution during SSR, the component function itself is transformed to async, and any non-async React hooks (useState, useMemo, useRef, useCallback, etc.) are replaced with worker-safe stubs. Hooks called after a data-fetching hook like fetchRpc are considered "unhandled" for SSR of that specific fetch call, as their state cannot be captured after an await. A warning will be logged in development, and preloading for that hook might be skipped. Place non-fetching hooks before fetching hooks in your component.
    • The fetched data (or error) is captured.
    • The result (data or error JSON, plus loaded status) is obfuscated and embedded into the HTML response within a <script> tag using the pattern: window.$USE_FETCH_PERSISTED_STATE(key) = "obfuscated_data";. The key is auto-generated.
  2. Client (Hydration):
    • When the component mounts in the browser, the hook runs again.
    • It checks if the corresponding window.$USE_FETCH_PERSISTED_STATE(key) script tag exists and contains data.
    • If found, it deobfuscates the data and uses it as the initial state, setting loading to false and loaded appropriately. This prevents an unnecessary fetch on initial load.
    • If not found (or if shouldFetch dictates), it proceeds with the normal client-side fetching logic.

Key Considerations for SSR Preloading:

  • Place non-fetching hooks (useState, useMemo, useCallback, useRef, useContext, etc.) before data-fetching hooks (fetchRpc, fetchHttp) in the component body.
  • Lazy hooks (lazyFetchRpc, lazyFetchHttp) do not participate in SSR preloading by default.

UI Components & Helpers

Link

(/app/link.tsx)

A wrapper around the standard <a> tag that optionally prefetches the linked page's data on hover.

<Link href="/about" prefetch={true} className="my-link">About Us</Link>

Props:

  • href (string, required): The target URL.
  • prefetch? (boolean, optional, default: true): If true, attempts to fetch the href when the user hovers over the link, potentially warming up caches or preloading resources. Fetch errors are suppressed. Prefetching only occurs once per href per session.
  • Other standard <a> tag attributes (className, target, etc.) are passed through.

Helmet / HelmetProvider

(react-helmet-async, re-exported)

Used for managing document head elements (<title>, <meta>, <link>, etc.) within components. Wrap your application root with <HelmetProvider>.

import app from 'react-edge/app'; // Provides Helmet and HelmetProvider

// In your root component or layout
<app.HelmetProvider>
	<App />
</app.HelmetProvider>;

// In a page component
function MyPage() {
	return (
		<div>
			<app.Helmet>
				<title>My Page Title</title>
				<meta
					name='description'
					content='This is my page'
				/>
			</app.Helmet>
			{/* Page content */}
		</div>
	);
}

defaultErrorBoundary

(/app/default-error-boundary.tsx)

A basic React Error Boundary component used by default if no custom boundary is provided to renderClient. Catches rendering errors within the application and displays a user-friendly message.


State Management Hooks (Re-exports)

React Edge re-exports several useful hooks from use-good-hooks for convenience.

useUrlState

Synchronizes component state with URL query parameters.

import app from 'react-edge/app';

const [filters, setFilters] = app.useUrlState({ category: 'all', sort: 'price' });
// State changes update URL, URL changes update state.

useStorageState

Synchronizes component state with localStorage or sessionStorage.

import app from 'react-edge/app';

const [theme, setTheme] = app.useStorageState('ui-theme', 'light', { storage: 'local' });
// State persists across sessions.

Other Re-exported Hooks

  • useDebounce: Debounce a value.
  • useThrottle: Throttle a value.
  • useDistinct: Get distinct values from an array, with optional debouncing (used internally by fetch hooks).
  • usePrev: Get the previous value of a state or prop.

Utilities

React Edge bundles several utilities, some from use-request-utils and some specific to the framework.

General Utilities (util)

(/libs/util.ts)

A collection of helper functions:

  • cleanString(str): Removes accents and converts to lowercase.
  • indexOfUint8Array(a, b): Finds index of byte array b within a.
  • pathJoin(...args): Joins path segments, trimming slashes.
  • readStream(stream, onRead?): Reads a ReadableStream to a string.
  • searchParamsToObject(params): Converts URLSearchParams to an object with inferred types.
  • serializable(obj): Checks if a value is JSON-serializable.
  • stringHash(str): Generates a non-cryptographic hash string.
  • stringToStream(...str): Creates a ReadableStream from strings.
  • stringToStreamWithDelay(delay, ...str): Creates a stream with delay between chunks.
  • toArray(input): Ensures the output is an array.
  • toNumber(str, defaultValue?): Converts string to number safely.
  • wait(ms): Async delay.

Bot Detection (bot)

(/libs/bot.ts)

Detects if a request likely originates from a known bot based on the User-Agent string.

import { bot } from 'react-edge/libs'; // Or re-exported from 'react-edge/worker'

function handler(request: Request) {
	if (bot(request)) {
		// Serve simplified content or block
	}
	// ... normal handling
}

Obfuscation (obfuscate/deobfuscate)

(/libs/obfuscate.ts)

Simple functions to "obfuscate" serializable data (strings, objects, arrays) into a Base64 string with fixed salt prefixes/suffixes, and deobfuscate it back. Used internally for hydrating preloaded state ($STORE, $USE_FETCH_PERSISTED_STATE) to discourage trivial inspection/modification. Not a security feature.

import { obfuscate, deobfuscate } from 'react-edge/libs';

const data = { sensitive: 'value' };
const obs = obfuscate(data); //-> 'kf12oi...'
const deobs = deobfuscate(obs); //-> { sensitive: 'value' }

Lazy Initialization (lazy)

(/libs/lazy.ts)

Creates a proxy object where properties defined as functions are only executed once on first access and their result is cached.

import { lazy } from 'react-edge/libs';

const config = lazy({
	heavyResource: () => {
		console.log('Initializing heavy resource...');
		return /* ... */;
	},
	simpleValue: 'abc'
});

console.log(config.simpleValue); // Access directly
const resource1 = config.heavyResource; // Function executes here, result cached
const resource2 = config.heavyResource; // Returns cached result, function doesn't run again

HTTP Utilities (fetch, headers)

(use-request-utils/fetch, use-request-utils/headers)

Re-exports the enhanced fetch wrapper and headers utilities from use-request-utils.

Cache (EphemeralCache, EdgeCache)

(use-request-utils/ephemeral-cache, /worker/edge-cache.ts)

  • EphemeralCache: Re-export of the in-memory response cache from use-request-utils. Used internally by fetch.http and the client-side RPC proxy. See Ephemeral Cache documentation.

  • EdgeCache: A class for interacting with the Cloudflare Cache API and KV for mapping/tagging. Used by WorkerEntry for server-side caching. Requires Cache API and optionally KVNamespace bindings.

    // Example usage within worker
    import { EdgeCache } from 'react-edge/worker';
    
    const cache = new EdgeCache({
    	cache: caches.default, // Default Cache API instance
    	cacheMapKV: env.CACHE_KV, // KV binding for storing tags/metadata
    	config: { version: 'v1.0' }, // App config
    	host: 'https://mydomain.com',
    	executionContext: ctx // Worker execution context for waitUntil
    });
    
    // Set response with tags and TTL
    await cache.set('/my/path', new Response('data'), { tags: ['data', 'path'], ttlSeconds: 60 });
    
    // Get cached response (respecting TTL passed here or stored TTL)
    const cachedResponse = await cache.get('/my/path', 30);
    
    // Purge cache by tag
    await cache.deleteBy({ tags: ['data'] });

KV Namespace Helper (kv)

(/worker/kv.ts)

A wrapper class for Cloudflare KV Namespace, providing convenience methods and automatic key prefixing.

import { kv as KVHelper } from 'react-edge/worker'; // Renamed to avoid conflict

const kvStore = new KVHelper({ kv: env.MY_KV_NAMESPACE, prefix: 'user:' });

// Set/Get with automatic prefixing
await kvStore.set('123', { name: 'Alice' }); // Stores under "user:123"
const user = await kvStore.get<{ name: string }>('123', { type: 'json' });

// List keys with prefix
const keys = await kvStore.list({ prefix: 'a' }); // Lists keys starting with "user:a"

Authentication (Auth*, jwt, cookies, crypto)

(use-request-utils/*)

Re-exports authentication helpers (AuthBasic, AuthBearer, AuthJwt), JWT utilities (jwt), cookie management (cookies), and crypto functions (crypto) from use-request-utils. Refer to their respective documentations:


CLI Commands

React Edge comes with a powerful CLI (via the react-edge-cli package, usually added as a dev dependency and run via yarn edge or npm run edge) for development and deployment:

Development

# Start development server (runs Vite for app and wrangler for worker)
yarn edge dev

# Start development server for app only (Vite)
yarn edge dev --app

# Start development server for worker only (wrangler)
yarn edge dev --worker

Build

# Build entire project (app and worker)
yarn edge build

# Build for specific environment (uses wrangler env)
yarn edge build --env production

# Build app or worker separately
yarn edge build --app
yarn edge build --worker

Other Commands

# Deploy to Cloudflare Workers (using wrangler)
yarn edge deploy

# View worker logs (using wrangler)
yarn edge logs

# Run linting (ESLint)
yarn edge lint

# Run tests (Vitest)
yarn edge test

# Type checking (tsc)
yarn edge type-check

Internationalization (i18n)

(/libs/i18n.ts)

Provides a simple i18n implementation using a global __ function.

  1. Define Translations: Create JSON or JS files for each language.

    // src/translations/en.ts
    export default {
    	greeting: 'Hello!',
    	welcome_user: 'Welcome, {name}!'
    };
    // src/translations/es.ts
    export default {
    	greeting: '¡Hola!',
    	welcome_user: '¡Bienvenido, {name}!'
    };
  2. Configure WorkerEntry: Pass the imported translations to the WorkerEntry constructor. The worker determines the language (e.g., from headers or path) and selects the appropriate translation object.

    // src/worker.ts
    import app from 'react-edge/app'; // Assuming WorkerEntry is exported here
    import en from './translations/en';
    import es from './translations/es';
    
    const workerApp = new worker.AppWorkerEntry({
    	// ... other options
    	i18n: { en, es },
    	lang: 'en' // Default language
    });
  3. Use in Components: The selected translations are automatically hydrated to the client. Use the global __ function.

    function Greeting({ userName }) {
    	return (
    		<div>
    			<h1>{__('greeting')}</h1>
    			<p>{__('welcome_user', { name: userName })}</p>
    		</div>
    	);
    }

Author

Felipe Rohde

License

MIT

Readme

Keywords

none

Package Sidebar

Install

npm i react-edge

Weekly Downloads

34

Version

0.1.233

License

MIT

Unpacked Size

501 kB

Total Files

109

Last publish

Collaborators

  • feliperohdee