react-edge
v0.1.246
Published
> A revolutionary React framework for building blazing-fast applications on the edge, powered by Cloudflare Workers.
Readme
React Edge Framework 🚀
A revolutionary React framework for building blazing-fast applications on the edge, powered by Cloudflare Workers.
🚀 Motivation
Read the full story on DEV.to
Index
- Installation
- Features
- Core Concepts
- UI Components & Helpers
- State Management Hooks (Re-exports)
- Utilities
- CLI Commands
- Internationalization (i18n)
- Author
- License
Installation
npm install react-edgeFeatures
- 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 forfetchRpchydration. - 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 leveraginguse-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 usingcreateRoute,createRouteGroup, etc.request(Request): The incoming HTTP request object.cache?(EdgeCache, optional): An instance ofEdgeCachefor Cloudflare Cache API interaction.cors?(boolean | WorkerEntry.Cors, optional): Enables or configures CORS headers.trueenables 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 (extendingRpc). Required if using RPC.url?(URL, optional): The URL object for the request (defaults to parsingrequest.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
cacheoptions 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 toDefaultErrorBoundary.render?((component: any, node?: HTMLElement) => void, optional): A custom rendering function. If not provided, useshydrateRoot(if#roothas content) orcreateRoot().render.
waitRenderFlag?(boolean, optional, default:true): Iftrue, waits for thewindow.$RENDERflag (set by the server stream) before rendering to ensure all initial data is available. Set tofalsefor immediate rendering (less common).
Process:
- Waits for the DOM and necessary window flags (
$ROUTER_STATE,$RENDER). - Retrieves initial state (router state, i18n data, preloaded store data, preloaded fetch data) from window flags set during SSR.
- Identifies the correct page component using the router configuration and the hydrated router state.
- Initializes i18n.
- Creates the initial
App.Contextvalue. - Renders the application inside necessary providers (
StrictMode,ErrorBoundary,AppContext,HelmetProvider). - Uses
hydrateRootif the#rootelement already contains server-rendered HTML, otherwise usescreateRoot().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 (ComponentTypeorFC).cache?: OptionalEdgeCachesettings (booleanor{ tags?: string[], ttlSeconds?: number }).headers?: OptionalHeadersor an async function returningHeadersto merge with the response.static?: Iftrue, hints that the page content is static (optimizations might apply, hydration behavior differs).
redirect: Performs an HTTP redirect.value: ARedirectobject ({ url: string; status?: number; headers?: Headers }) or an async function returning{ cache?: App.CacheOptions; value: Redirect }.cache?: OptionalEdgeCachesettings (only ifvalueis an object, not a function).
response: Returns a rawResponseobject (e.g., for API endpoints).value: AResponseobject or an async function returning{ cache?: App.CacheOptions; value: Response }.cache?: OptionalEdgeCachesettings (only ifvalueis an object, not a function).
middlewares?: An array ofRouteMiddlewarefunctions 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.WorkerContextassociated with the request being processed. This context is populated byWorkerEntryand includes the server-side RPC client proxy (if an RPC service is configured). - Client/Browser: Returns an
App.Contextobject. Initialpath,pathParams,rawPath,searchParams, andstorevalues are hydrated from data embedded in the initial HTML (window.$ROUTER_STATE,window.$STORE). Therpcproperty is initialized with a client-side RPC proxy that sends requests to the/api/rpcendpoint. Theurlproperty reflects the currentwindow.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 receivingApp.Context(withrpcproxy) and arguments from the returnedfetchfunction. Return the promise from your RPC call ornullto skip.options?: Configuration object (see Hook Options & Return Value). Thekeyfor 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 thefetch.httpclient fromuse-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 thefetch.httpclient and fetched data.ignoreAbort?(boolean): Iftrue, 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 istrue.triggerDeps?(any[]): Trigger re-fetch when these dependencies change, without necessarily changing function arguments..triggerDepsDebounce?(number): Debounce time (ms) fortriggerDepschanges (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'sfetchFn.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 afterreset()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:
- Worker (SSR):
- When a component using
fetchRpcorfetchHttprenders on the server, the framework detects these hooks. - If
shouldFetchallows (considering{ worker: true }), the hook'sfetchFnis 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 likefetchRpcare considered "unhandled" for SSR of that specific fetch call, as their state cannot be captured after anawait. 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
loadedstatus) is obfuscated and embedded into the HTML response within a<script>tag using the pattern:window.$USE_FETCH_PERSISTED_STATE(key) = "obfuscated_data";. Thekeyis auto-generated.
- When a component using
- 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
loadingtofalseandloadedappropriately. This prevents an unnecessary fetch on initial load. - If not found (or if
shouldFetchdictates), 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 tofetchthehrefwhen the user hovers over the link, potentially warming up caches or preloading resources. Fetch errors are suppressed. Prefetching only occurs once perhrefper 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 arraybwithina.pathJoin(...args): Joins path segments, trimming slashes.readStream(stream, onRead?): Reads aReadableStreamto a string.searchParamsToObject(params): ConvertsURLSearchParamsto 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 aReadableStreamfrom 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' }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.
- See Fetch Utilities documentation.
- See Headers Utilities documentation.
Cache (EphemeralCache, EdgeCache)
(use-request-utils/ephemeral-cache, /worker/edge-cache.ts)
EphemeralCache: Re-export of the in-memory response cache fromuse-request-utils. Used internally byfetch.httpand 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 byWorkerEntryfor server-side caching. RequiresCacheAPI and optionallyKVNamespacebindings.// Example usage within worker import { env } from 'cloudflare:workers'; 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' }); // 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 { env } from 'cloudflare:workers';
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 --workerBuild
# 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 --workerOther 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-checkInternationalization (i18n)
(/libs/i18n.ts)
Provides a simple i18n implementation using a global __ function.
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}!' };Configure
WorkerEntry: Pass the imported translations to theWorkerEntryconstructor. 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 });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
- Email: [email protected]
- GitHub: @feliperohdee
