Home >Web Front-end >JS Tutorial >From Next.js to React Edge with Cloudflare Workers: A Story of Liberation
It all started with a Vercel invoice. No, actually, it started way earlier—with small frustrations piling up. The need to pay for basic features like DDoS protection, more detailed logs, or even a decent firewall, build queues, etc. The feeling of being trapped in an increasingly expensive vendor lock-in.
"And worst of all: our precious SEO headers simply stopped rendering on the server in an application using the pages router. A true headache for any developer! ?"
But what truly made me rethink everything was the direction Next.js was heading. The introduction of use client, use server—directives that, in theory, should simplify development but, in practice, added another layer of complexity to manage. It was like going back to the PHP days, marking files with directives to dictate where they should run.
And it doesn’t stop there. The App Router—an interesting idea but implemented in a way that created an almost entirely new framework within Next.js. Suddenly, there were two completely different ways to do the same thing—the 'old' and the 'new'—with subtly different behaviors and hidden pitfalls.
That’s when it hit me: why not leverage the incredible infrastructure of Cloudflare with Workers running on the edge, R2 for storage, KV for distributed data... Along with, of course, the amazing DDoS protection, global CDN, firewall, page rules and routing, and everything else Cloudflare has to offer.
And the best part: a fair pricing model where you pay for what you use, with no surprises.
This is how React Edge was born. A framework that doesn’t aim to reinvent the wheel but instead delivers a truly simple and modern development experience.
When I started developing React Edge, I had a clear goal: to create a framework that made sense. No more wrestling with confusing directives, no more paying exorbitant fees for basic features, and most importantly, no more dealing with artificial complexity caused by client/server separation. I wanted speed—a framework that delivers performance without sacrificing simplicity. Leveraging my knowledge of React’s API and years of experience as a JavaScript and Golang developer, I knew exactly how to handle streams and multiplexing to optimize rendering and data management.
Cloudflare Workers, with its powerful infrastructure and global presence, provided the perfect environment to explore these possibilities. I wanted something truly hybrid, and this combination of tools and experience gave life to React Edge: a framework that solves real-world problems with modern and efficient solutions.
React Edge introduces a revolutionary approach to React development. Imagine writing a class on the server and calling it directly from the client, with full type safety and zero configuration. Imagine a distributed caching system that "just works," allowing invalidation by tags or prefixes. Imagine sharing state seamlessly and securely between server and client. Add simplified authentication, an efficient internationalization system, CLI, and more.
Its RPC communication feels almost magical—you write methods in a class and call them from the client as if they were local. The intelligent multiplexing system ensures that even if multiple components make the same call, only one request is sent to the server. Ephemeral caching avoids unnecessary repeated requests, and it all works seamlessly on both the server and the client.
One of the most powerful features is the app.useFetch hook, which unifies the data-fetching experience. On the server, it preloads data during SSR; on the client, it automatically hydrates with those data and supports on-demand updates. With support for automatic polling and dependency-based reactivity, creating dynamic interfaces has never been easier.
But that’s not all. The framework offers a powerful routing system (inspired by the fantastic Hono), integrated asset management with Cloudflare R2, and an elegant way to handle errors via the HttpError class. Middlewares can easily send data to the client through a shared store, and everything is automatically obfuscated for security.
The most impressive part? Almost all of the framework’s code is hybrid. There isn’t a “client” version and a “server” version—the same code works in both environments, adapting automatically to the context. The client receives only what it needs, making the final bundle extremely optimized.
And the icing on the cake: all of this runs on the Cloudflare Workers edge infrastructure, delivering exceptional performance at a fair cost. No surprise invoices, no basic features locked behind expensive enterprise plans—just a solid framework that lets you focus on what truly matters: building amazing applications. Additionally, React Edge leverages Cloudflare’s ecosystem, including Queues, Durable Objects, KV Storage, and more, providing a robust and scalable foundation for your applications.
Vite was used as the base for the development environment, testing, and build process. With its impressive speed and modern architecture, Vite enables an agile and efficient workflow. It not only accelerates development but also optimizes the build process, ensuring fast and accurate compilation. Without a doubt, Vite was the perfect choice for React Edge.
Have you ever wondered what it would be like to develop React applications without worrying about the client/server barrier? Without memorizing dozens of directives like use client or use server? Better yet: what if you could call server functions as if they were local, with full typing and zero configuration?
And the best part: all this works seamlessly on both the server and client without marking anything as use client or use server. The framework automatically knows what to do based on the context. Shall we dive in?
Imagine being able to do this:
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
// pages/api/search.ts export default async handler = (req, res) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... rest of the component }
Forget everything you know about data fetching in React. The app.useFetch hook from React Edge introduces a completely new and powerful approach. Imagine a hook that:
Let’s see it in action:
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
The example above hides a powerful feature: intelligent multiplexing. When you use ctx.rpc.batch, React Edge not only groups calls—it also deduplicates identical calls automatically:
// pages/api/search.ts export default async handler = (req, res) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... rest of the component }
// First, define your API on the server class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Automatic caching for 5 minutes return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Now, on the client, the magic happens const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Reactive search with intelligent debounce const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Re-fetch when filters change deps: [filters], // Wait 300ms of "silence" before fetching depsDebounce: { filters: 300 } } ); // Fetch property details for the found results const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // This looks like multiple calls, but... return ctx.rpc.batch([ // Everything is multiplexed into a single request! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Refresh when searchResults change deps: [searchResults] } ); // A beautiful and responsive interface return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Search error: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
The RPC system in React Edge is designed with security and encapsulation in mind. Not everything in an RPC class is automatically exposed to the client:
const PropertyListingPage = () => { const { data } = app.useFetch(async (ctx) => { // Even if you make 100 identical calls... return ctx.rpc.batch([ ctx.rpc.getProperty('123'), ctx.rpc.getProperty('123'), // same call ctx.rpc.getProperty('456'), ctx.rpc.getProperty('456'), // same call ]); }); // Behind the scenes: // 1. The batch groups all calls into ONE single HTTP request. // 2. Identical calls are deduplicated automatically. // 3. Results are distributed correctly to each position in the array. // 4. Typing is maintained for each individual result! // Actual RPC calls: // 1. getProperty('123') // 2. getProperty('456') // Results are distributed correctly to all callers! };
One of the most powerful features of RPC is the ability to organize APIs into hierarchies:
One of the most impressive parts is how useFetch handles SSR: const ProductPage = ({ productId }: Props) => { const { data, loaded, loading, error } = app.useFetch( async (ctx) => ctx.rpc.getProduct(productId), { // Fine-grained control over when to fetch shouldFetch: ({ worker, loaded }) => { // On the worker (SSR): always fetch if (worker) return true; // On the client: fetch only if no data is loaded return !loaded; } } ); // On the server: // 1. `useFetch` makes the RPC call. // 2. Data is serialized and sent to the client. // 3. Component renders with the data. // On the client: // 1. Component hydrates with server data. // 2. No new call is made (shouldFetch returns false). // 3. If necessary, you can re-fetch with `data.fetch()`. return ( <Suspense fallback={<ProductSkeleton />}> <ProductView product={data} loading={loading} error={error} /> </Suspense> ); };
Organizing APIs into hierarchies provides several benefits:
The RPC system in React Edge makes client-server communication so natural that you almost forget you’re making remote calls. With the ability to organize APIs into hierarchies, you can build complex structures while keeping your code clean and secure.
React Edge introduces an elegant and flexible internationalization system that supports variable interpolation and complex formatting without relying on heavyweight libraries.
class PaymentsAPI extends Rpc { // Properties are never exposed private stripe = new Stripe(process.env.STRIPE_KEY); // Methods starting with $ are private private async $validateCard(card: CardInfo) { return await this.stripe.cards.validate(card); } // Methods starting with _ are also private private async _processPayment(amount: number) { return await this.stripe.charges.create({ amount }); } // This method is public and accessible via RPC async createPayment(orderData: OrderData) { // Internal validation using a private method const validCard = await this.$validateCard(orderData.card); if (!validCard) { throw new HttpError(400, 'Invalid card'); } // Processing using another private method const payment = await this._processPayment(orderData.amount); return payment; } } // On the client: const PaymentForm = () => { const { rpc } = app.useContext<App.Context>(); // ✅ This works const handleSubmit = () => rpc.createPayment(data); // ❌ These do not work - private methods are not exposed const invalid1 = () => rpc.$validateCard(data); const invalid2 = () => rpc._processPayment(100); // ❌ This also does not work - properties are not exposed const invalid3 = () => rpc.stripe; };
Usage in the code:
// Nested APIs for better organization class UsersAPI extends Rpc { // Subclass to manage preferences preferences = new UserPreferencesAPI(); // Subclass to manage notifications notifications = new UserNotificationsAPI(); async getProfile(id: string) { return this.db.users.findById(id); } } class UserPreferencesAPI extends Rpc { async getTheme(userId: string) { return this.db.preferences.getTheme(userId); } async setTheme(userId: string, theme: Theme) { return this.db.preferences.setTheme(userId, theme); } } class UserNotificationsAPI extends Rpc { // Private methods remain private private async $sendPush(userId: string, message: string) { await this.pushService.send(userId, message); } async getSettings(userId: string) { return this.db.notifications.getSettings(userId); } async notify(userId: string, notification: Notification) { const settings = await this.getSettings(userId); if (settings.pushEnabled) { await this.$sendPush(userId, notification.message); } } } // On the client: const UserProfile = () => { const { rpc } = app.useContext<App.Context>(); const { data: profile } = app.useFetch( async (ctx) => { // Nested calls are fully typed const [user, theme, notificationSettings] = await ctx.rpc.batch([ // Method from the main class ctx.rpc.getProfile('123'), // Method from the preferences subclass ctx.rpc.preferences.getTheme('123'), // Method from the notifications subclass ctx.rpc.notifications.getSettings('123') ]); return { user, theme, notificationSettings }; } ); // ❌ Private methods remain inaccessible const invalid = () => rpc.notifications.$sendPush('123', 'hello'); };
React Edge automatically detects and loads your translations. It even allows saving user preferences in cookies effortlessly. But, of course, you’d expect this, right?
// translations/fr.ts export default { 'Good Morning, {name}!': 'Bonjour, {name}!', };
Authentication has always been a pain point in web applications. Managing JWT tokens, secure cookies, and revalidation often requires a lot of boilerplate code. React Edge completely changes this.
Here’s how simple it is to implement a full authentication system:
const WelcomeMessage = () => { const userName = 'John'; return ( <div> {/* Output: Good Morning, John! */} <h1>{__('Good Morning, {name}!', { name: userName })}</h1> </div> ); };
// worker.ts const handler = { fetch: async (request: Request, env: types.Worker.Env, context: ExecutionContext) => { const url = new URL(request.url); const lang = (() => { const lang = url.searchParams.get('lang') || worker.cookies.get(request.headers, 'lang') || request.headers.get('accept-language') || ''; if (!lang || !i18n[lang]) { return 'en-us'; } return lang; })(); const workerApp = new AppWorkerEntry({ i18n: { en: await import('./translations/en'), pt: await import('./translations/pt'), es: await import('./translations/es') } }); const res = await workerApp.fetch(); if (url.searchParams.has('lang')) { return new Response(res.body, { headers: worker.cookies.set(res.headers, 'lang', lang) }); } return res; } };
Zero Boilerplate
Security by Default
Full Typing
Seamless Integration
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
One of the most powerful features of React Edge is its ability to securely share state between the worker and the client. Here's how it works:
// pages/api/search.ts export default async handler = (req, res) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... rest of the component }
The routing system of React Edge is inspired by Hono, but with enhanced features for SSR:
// First, define your API on the server class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Automatic caching for 5 minutes return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Now, on the client, the magic happens const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Reactive search with intelligent debounce const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Re-fetch when filters change deps: [filters], // Wait 300ms of "silence" before fetching depsDebounce: { filters: 300 } } ); // Fetch property details for the found results const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // This looks like multiple calls, but... return ctx.rpc.batch([ // Everything is multiplexed into a single request! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Refresh when searchResults change deps: [searchResults] } ); // A beautiful and responsive interface return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Search error: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
React Edge includes a powerful caching system that works seamlessly for both JSON data and entire pages. This caching system supports intelligent tagging and prefix-based invalidation, making it suitable for a wide range of scenarios.
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
The Link component is an intelligent and performance-oriented solution for preloading client-side resources, ensuring a smoother and faster navigation experience for users. Its prefetching functionality is triggered when the user hovers over the link, taking advantage of idle moments to request destination data in advance.
// pages/api/search.ts export default async handler = (req, res) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... rest of the component }
When the user hovers over the “About Us” link, the component will start preloading the data for the /about page, ensuring an almost instant transition. Genius idea, isn’t it? Inspired by the react.dev documentation.
The app.useContext hook is a cornerstone of React Edge, granting seamless access to the worker's entire context. It provides a powerful interface for managing routing, state, RPC calls, and more.
// First, define your API on the server class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Automatic caching for 5 minutes return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Now, on the client, the magic happens const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Reactive search with intelligent debounce const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Re-fetch when filters change deps: [filters], // Wait 300ms of "silence" before fetching depsDebounce: { filters: 300 } } ); // Fetch property details for the found results const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // This looks like multiple calls, but... return ctx.rpc.batch([ // Everything is multiplexed into a single request! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Refresh when searchResults change deps: [searchResults] } ); // A beautiful and responsive interface return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Search error: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
Key Features of app.useContext
The app.useContext hook bridges the gap between the worker and the client. It allows you to build features that rely on shared state, secure data fetching, and contextual rendering without boilerplate. This simplifies complex applications, making them easier to maintain and faster to develop.
The app.useUrlState hook keeps your application state in sync with the URL query parameters, offering fine-grained control over what is included in the URL, how the state is serialized, and when it updates.
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
Initial State
Options:
The app.useStorageState hook allows you to persist state in the browser using localStorage or sessionStorage, with full TypeScript support.
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
Debounce reactive values effortlessly:
// pages/api/search.ts export default async handler = (req, res) => { // Configure CORS // Validate request // Handle errors // Serialize response // ...100 lines later... } // app/search/page.tsx 'use client'; import { useEffect, useState } from 'react'; export default const SearchPage = () => { const [search, setSearch] = useState(''); const [filters, setFilters] = useState({}); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); useEffect(() => { let timeout; const doSearch = async () => { setLoading(true); try { const res = await fetch('/api/search?' + new URLSearchParams({ q: search, ...filters })); if (!res.ok) throw new Error('Search failed'); setData(await res.json()); } catch (err) { console.error(err); } finally { setLoading(false); } }; timeout = setTimeout(doSearch, 300); return () => clearTimeout(timeout); }, [search, filters]); // ... rest of the component }
Keep arrays with unique values while maintaining type safety.
The app.useDistinct hook specializes in detecting when a value has truly changed, with support for deep comparison and debounce:
// On the server class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validation with Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // On the client const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript knows exactly what searchUsers accepts and returns! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
The hooks in React Edge are designed to work in harmony, providing a fluid and strongly typed development experience. Their combination allows for creating complex and reactive interfaces with much less code.
The CLI for React Edge was designed to simplify developers' lives by gathering essential tools into a single, intuitive interface. Whether you're a beginner or an experienced developer, the CLI ensures you can configure, develop, test, and deploy projects efficiently and effortlessly.
I’m proud to share that the first production application using React Edge is already live! It's a Brazilian real estate company, Lopes Imóveis, which is already reaping the benefits of the framework's performance and flexibility.
On their website, properties are loaded into cache to optimize search and provide a smoother user experience. Since it's a highly dynamic site, route caching uses a TTL of just 10 seconds, combined with the stale-while-revalidate strategy. This ensures the site delivers updated data with exceptional performance, even during background revalidations.
Additionally, recommendations for similar properties are calculated efficiently and asynchronously in the background, then saved directly to Cloudflare's cache using the integrated RPC caching system. This approach reduces response times for subsequent requests and makes querying recommendations nearly instantaneous. All images are stored on Cloudflare R2, offering scalable and distributed storage without relying on external providers.
Soon, we’ll also launch a massive automated marketing project for Easy Auth, showcasing the potential of this technology even further.
And so, dear readers, we’ve reached the end of this journey through the world of React Edge! I know there’s still a sea of incredible features to explore, like simpler authentication options such as Basic and Bearer, and other tricks that make a developer’s day much happier. But hold on! The idea is to bring more detailed articles in the future to dive deep into each of these features.
Spoiler alert: soon, React Edge will be open source and properly documented! Balancing development, work, writing, and a little bit of social life isn’t easy, but the excitement of seeing this marvel in action, especially with the absurd speed provided by Cloudflare's infrastructure, is the fuel that keeps me going. So, stay tuned, because the best is yet to come! ?
In the meantime, if you want to start exploring and testing it right now, the package is already available on NPM: React Edge on NPM..
My email is feliperohdee@gmail.com, and I’m always open to feedback—this is just the beginning of this journey. Suggestions and constructive criticism are welcome. If you enjoyed what you read, share it with your friends and colleagues, and stay tuned for more updates. Thank you for reading this far, and see you next time! ???
The above is the detailed content of From Next.js to React Edge with Cloudflare Workers: A Story of Liberation. For more information, please follow other related articles on the PHP Chinese website!