A revolutionary React framework for building blazing-fast applications on the edge, powered by Cloudflare Workers.
Read the full story on DEV.to
- Installation
- Features
- Core Concepts
- UI Components & Helpers
- State Management Hooks (Re-exports)
- Utilities
- CLI Commands
- Internationalization (i18n)
- Author
- License
npm install react-edge
- 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 forfetchRpc
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 leveraginguse-request-utils
. - Developer Experience: Fast HMR with Vite, simple configuration, strong TypeScript integration.
- Performance: Optimized for edge environments, minimizing bundle size and latency.
(/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.
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 ofEdgeCache
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 (extendingRpc
). Required if using RPC. -
url?
(URL
, optional): The URL object for the request (defaults to parsingrequest.url
).
-
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.
If options.cors
is enabled, WorkerEntry
automatically handles OPTIONS
requests and adds appropriate Access-Control-*
headers to responses based on the configuration.
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.
(/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#root
has content) orcreateRoot().render
.
-
-
waitRenderFlag?
(boolean
, optional, default:true
): Iftrue
, waits for thewindow.$RENDER
flag (set by the server stream) before rendering to ensure all initial data is available. Set tofalse
for 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.Context
value. - Renders the application inside necessary providers (
StrictMode
,ErrorBoundary
,AppContext
,HelmetProvider
). - Uses
hydrateRoot
if the#root
element already contains server-rendered HTML, otherwise usescreateRoot().render
.
(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
.
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
orFC
). -
cache?
: OptionalEdgeCache
settings (boolean
or{ tags?: string[], ttlSeconds?: number }
). -
headers?
: OptionalHeaders
or an async function returningHeaders
to 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
: ARedirect
object ({ url: string; status?: number; headers?: Headers }
) or an async function returning{ cache?: App.CacheOptions; value: Redirect }
. -
cache?
: OptionalEdgeCache
settings (only ifvalue
is an object, not a function).
-
-
response
: Returns a rawResponse
object (e.g., for API endpoints).-
value
: AResponse
object or an async function returning{ cache?: App.CacheOptions; value: Response }
. -
cache?
: OptionalEdgeCache
settings (only ifvalue
is an object, not a function).
-
-
middlewares?
: An array ofRouteMiddleware
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.
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>
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>
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 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
// 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;
(/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).
-
Server/Worker: Returns the current
App.WorkerContext
associated with the request being processed. This context is populated byWorkerEntry
and includes the server-side RPC client proxy (if an RPC service is configured). -
Client/Browser: Returns an
App.Context
object. Initialpath
,pathParams
,rawPath
,searchParams
, andstore
values are hydrated from data embedded in the initial HTML (window.$ROUTER_STATE
,window.$STORE
). Therpc
property is initialized with a client-side RPC proxy that sends requests to the/api/rpc
endpoint. Theurl
property reflects the currentwindow.location
.
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
};
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>
);
}
(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.
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
).
(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.
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);
}
}
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.
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.
// ...
}
(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
(withrpc
proxy) and arguments from the returnedfetch
function. Return the promise from your RPC call ornull
to skip. -
options?
: Configuration object (see Hook Options & Return Value). Thekey
for SSR hydration is automatically generated.
(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>
(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.http
client fromuse-request-utils/fetch
. -
options
: Configuration object (see below).
(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>
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.http
client 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) fortriggerDeps
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'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.
The data fetching hooks (fetchRpc
, fetchHttp
) are designed to work seamlessly with SSR and client-side hydration:
-
Worker (SSR):
- When a component using
fetchRpc
orfetchHttp
renders on the server, the framework detects these hooks. - If
shouldFetch
allows (considering{ worker: true }
), the hook'sfetchFn
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 likefetchRpc
are 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
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";
. Thekey
is 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
loading
tofalse
andloaded
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.
(/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 tofetch
thehref
when the user hovers over the link, potentially warming up caches or preloading resources. Fetch errors are suppressed. Prefetching only occurs once perhref
per session. - Other standard
<a>
tag attributes (className
,target
, etc.) are passed through.
(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>
);
}
(/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.
React Edge re-exports several useful hooks from use-good-hooks
for convenience.
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.
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.
-
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.
React Edge bundles several utilities, some from use-request-utils
and some specific to the framework.
(/libs/util.ts
)
A collection of helper functions:
-
cleanString(str)
: Removes accents and converts to lowercase. -
indexOfUint8Array(a, b)
: Finds index of byte arrayb
withina
. -
pathJoin(...args)
: Joins path segments, trimming slashes. -
readStream(stream, onRead?)
: Reads aReadableStream
to a string. -
searchParamsToObject(params)
: ConvertsURLSearchParams
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 aReadableStream
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.
(/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
}
(/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' }
(/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
(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.
(use-request-utils/ephemeral-cache
, /worker/edge-cache.ts
)
-
EphemeralCache
: Re-export of the in-memory response cache fromuse-request-utils
. Used internally byfetch.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 byWorkerEntry
for server-side caching. RequiresCache
API and optionallyKVNamespace
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'] });
(/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"
(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:
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:
# 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 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
# 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
(/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 theWorkerEntry
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 });
-
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> ); }
Felipe Rohde
- Email: feliperohdee@gmail.com
- GitHub: @feliperohdee