Heim >Web-Frontend >js-Tutorial >Von Next.js zu React Edge mit Cloudflare Workers: Eine Geschichte der Befreiung
Alles begann mit einer Vercel-Rechnung. Nein, eigentlich fing es schon viel früher an – mit kleinen Frustrationen, die sich häuften. Die Notwendigkeit, für grundlegende Funktionen wie DDoS-Schutz, detailliertere Protokolle oder sogar eine anständige Firewall, Build-Warteschlangen usw. zu bezahlen. Das Gefühl, in einer immer teurer werdenden Anbieterbindung gefangen zu sein.
„Und das Schlimmste: Unsere wertvollen SEO-Header wurden in einer Anwendung, die den Seiten-Router nutzt, einfach nicht mehr auf dem Server gerendert. Ein echtes Problem für jeden Entwickler! ?“
Aber was mich wirklich zum Umdenken brachte, war die Richtung, in die sich Next.js bewegte. Die Einführung von „Use-Client“ und „Use-Server“ – Anweisungen, die theoretisch die Entwicklung vereinfachen sollten, in der Praxis jedoch eine weitere Ebene der zu verwaltenden Komplexität hinzufügten. Es war, als würde man in die PHP-Zeit zurückkehren und Dateien mit Anweisungen markieren, um vorzugeben, wo sie ausgeführt werden sollen.
Und das ist noch nicht alles. Der App Router – eine interessante Idee, aber so umgesetzt, dass ein fast völlig neues Framework innerhalb von Next.js entstand. Plötzlich gab es zwei völlig unterschiedliche Möglichkeiten, dasselbe zu tun – die „alte“ und die „neue“ – mit subtil unterschiedlichen Verhaltensweisen und versteckten Fallstricken.
Da wurde mir klar: Warum nicht die unglaubliche Infrastruktur von Cloudflare nutzen, mit Workers am Edge, R2 für die Speicherung, KV für verteilte Daten ... Zusammen mit natürlich dem erstaunlichen DDoS-Schutz, dem globalen CDN und der Firewall , Seitenregeln und Routing und alles andere, was Cloudflare zu bieten hat.
Und das Beste daran: ein faires Preismodell, bei dem Sie für das bezahlen, was Sie nutzen, ohne Überraschungen.
So wurde React Edge geboren. Ein Framework, das nicht darauf abzielt, das Rad neu zu erfinden, sondern stattdessen ein wirklich einfaches und modernes Entwicklungserlebnis bietet.
Als ich mit der Entwicklung von React Edge begann, hatte ich ein klares Ziel: ein Framework zu schaffen, das Sinn macht. Kein Ringen mehr mit verwirrenden Anweisungen, keine Zahlung überhöhter Gebühren für Grundfunktionen und vor allem kein Umgang mit künstlicher Komplexität, die durch die Trennung von Client und Server verursacht wird. Ich wollte Geschwindigkeit – ein Framework, das Leistung liefert, ohne auf Einfachheit zu verzichten. Dank meiner Kenntnisse der React-API und jahrelanger Erfahrung als JavaScript- und Golang-Entwickler wusste ich genau, wie man mit Streams und Multiplexing umgeht, um das Rendering und die Datenverwaltung zu optimieren.
Cloudflare Workers bot mit seiner leistungsstarken Infrastruktur und globalen Präsenz die perfekte Umgebung, um diese Möglichkeiten zu erkunden. Ich wollte etwas wirklich Hybrides, und diese Kombination aus Tools und Erfahrung hat React Edge zum Leben erweckt: ein Framework, das reale Probleme mit modernen und effizienten Lösungen löst.
React Edge führt einen revolutionären Ansatz für die React-Entwicklung ein. Stellen Sie sich vor, Sie schreiben eine Klasse auf dem Server und rufen sie direkt vom Client auf, mit vollständiger Typsicherheit und ohne Konfiguration. Stellen Sie sich ein verteiltes Caching-System vor, das „einfach funktioniert“ und die Ungültigmachung durch Tags oder Präfixe ermöglicht. Stellen Sie sich vor, den Status nahtlos und sicher zwischen Server und Client zu teilen. Fügen Sie eine vereinfachte Authentifizierung, ein effizientes Internationalisierungssystem, CLI und mehr hinzu.
Seine RPC-Kommunikation fühlt sich fast magisch an – Sie schreiben Methoden in einer Klasse und rufen sie vom Client aus auf, als wären sie lokal. Das intelligente Multiplexing-System stellt sicher, dass auch bei einem gleichzeitigen Aufruf mehrerer Komponenten nur eine Anfrage an den Server gesendet wird. Ephemeres Caching vermeidet unnötige wiederholte Anfragen und alles funktioniert nahtlos sowohl auf dem Server als auch auf dem Client.
Eine der leistungsstärksten Funktionen ist der app.useFetch-Hook, der das Datenabruferlebnis vereinheitlicht. Auf dem Server werden während der SSR Daten vorab geladen. Auf dem Client versorgt es sich automatisch mit diesen Daten und unterstützt On-Demand-Updates. Dank der Unterstützung für automatische Abfragen und abhängigkeitsbasierte Reaktivität war die Erstellung dynamischer Schnittstellen noch nie so einfach.
Aber das ist noch nicht alles. Das Framework bietet ein leistungsstarkes Routing-System (inspiriert vom fantastischen Hono), integriertes Asset-Management mit Cloudflare R2 und eine elegante Möglichkeit, Fehler über die HttpError-Klasse zu behandeln. Middlewares können Daten problemlos über einen gemeinsamen Speicher an den Client senden, und alles wird aus Sicherheitsgründen automatisch verschleiert.
Der beeindruckendste Teil? Fast der gesamte Code des Frameworks ist hybrid. Es gibt keine „Client“-Version und keine „Server“-Version – derselbe Code funktioniert in beiden Umgebungen und passt sich automatisch an den Kontext an. Der Kunde erhält nur das, was er benötigt, wodurch das endgültige Paket äußerst optimiert wird.
Und das Tüpfelchen auf dem i: All dies läuft auf der Edge-Infrastruktur von Cloudflare Workers und bietet außergewöhnliche Leistung zu fairen Kosten. Keine überraschenden Rechnungen, keine grundlegenden Funktionen, die hinter teuren Unternehmensplänen verborgen sind – nur ein solides Framework, mit dem Sie sich auf das konzentrieren können, was wirklich wichtig ist: die Entwicklung fantastischer Anwendungen. Darüber hinaus nutzt React Edge das Ökosystem von Cloudflare, einschließlich Warteschlangen, langlebigen Objekten, KV-Speicher und mehr, und bietet so eine robuste und skalierbare Grundlage für Ihre Anwendungen.
Vite wurde als Basis für die Entwicklungsumgebung, Tests und den Build-Prozess verwendet. Mit seiner beeindruckenden Geschwindigkeit und modernen Architektur ermöglicht Vite einen agilen und effizienten Workflow. Es beschleunigt nicht nur die Entwicklung, sondern optimiert auch den Build-Prozess und sorgt so für eine schnelle und genaue Kompilierung. Ohne Zweifel war Vite die perfekte Wahl für React Edge.
Haben Sie sich jemals gefragt, wie es wäre, React-Anwendungen zu entwickeln, ohne sich um die Client/Server-Barriere kümmern zu müssen? Ohne sich Dutzende von Anweisungen wie „Use Client“ oder „Use Server“ zu merken? Besser noch: Was wäre, wenn Sie Serverfunktionen so aufrufen könnten, als wären sie lokal, mit vollständiger Eingabe und ohne Konfiguration?
Und das Beste daran: All dies funktioniert nahtlos sowohl auf dem Server als auch auf dem Client, ohne dass irgendetwas als „Client verwenden“ oder „Server verwenden“ markiert wird. Das Framework weiß automatisch anhand des Kontexts, was zu tun ist. Sollen wir eintauchen?
Stellen Sie sich vor, Sie könnten Folgendes tun:
// 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 }
Vergessen Sie alles, was Sie über das Abrufen von Daten in React wissen. Der app.useFetch-Hook von React Edge führt einen völlig neuen und leistungsstarken Ansatz ein. Stellen Sie sich einen Haken vor, der:
Sehen wir es uns in Aktion an:
// 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' }) ); };
Das obige Beispiel verbirgt eine leistungsstarke Funktion: intelligentes Multiplexing. Wenn Sie ctx.rpc.batch verwenden, gruppiert React Edge nicht nur Anrufe, sondern dedupliziert auch automatisch identische Anrufe:
// 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> ); };
Das RPC-System in React Edge ist auf Sicherheit und Kapselung ausgelegt. Nicht alles in einer RPC-Klasse wird dem Client automatisch angezeigt:
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! };
Eine der leistungsstärksten Funktionen von RPC ist die Möglichkeit, APIs in Hierarchien zu organisieren:
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> ); };
Die Organisation von APIs in Hierarchien bietet mehrere Vorteile:
Das RPC-System in React Edge macht die Client-Server-Kommunikation so natürlich, dass Sie fast vergessen, dass Sie Remote-Anrufe tätigen. Mit der Möglichkeit, APIs in Hierarchien zu organisieren, können Sie komplexe Strukturen aufbauen und gleichzeitig Ihren Code sauber und sicher halten.
React Edge führt ein elegantes und flexibles Internationalisierungssystem ein, das variable Interpolation und komplexe Formatierung unterstützt, ohne auf umfangreiche Bibliotheken angewiesen zu sein.
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; };
Verwendung im 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 erkennt und lädt Ihre Übersetzungen automatisch. Es ermöglicht sogar das mühelose Speichern von Benutzereinstellungen in Cookies. Aber das würde man natürlich erwarten, oder?
// translations/fr.ts export default { 'Good Morning, {name}!': 'Bonjour, {name}!', };
Authentifizierung war schon immer ein Problem bei Webanwendungen. Für die Verwaltung von JWT-Tokens, sicheren Cookies und der erneuten Validierung ist oft viel Boilerplate-Code erforderlich. React Edge ändert dies völlig.
So einfach ist die Implementierung eines vollständigen Authentifizierungssystems:
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; } };
Null-Boilerplate
Standardmäßige Sicherheit
Vollständige Eingabe
Nahtlose 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' }) ); };
Eine der leistungsstärksten Funktionen von React Edge ist die Fähigkeit, den Status sicher zwischen dem Worker und dem Client auszutauschen. So funktioniert es:
// 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 }
Das Routing-System von React Edge ist von Hono inspiriert, verfügt jedoch über erweiterte Funktionen für 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 umfasst ein leistungsstarkes Caching-System, das sowohl für JSON-Daten als auch für ganze Seiten nahtlos funktioniert. Dieses Caching-System unterstützt intelligentes Tagging und präfixbasierte Ungültigmachung und eignet sich daher für eine Vielzahl von Szenarien.
// 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' }) ); };
Die Link-Komponente ist eine intelligente und leistungsorientierte Lösung zum Vorladen von clientseitigen Ressourcen, die den Benutzern ein reibungsloseres und schnelleres Navigationserlebnis gewährleistet. Die Prefetching-Funktionalität wird ausgelöst, wenn der Benutzer mit der Maus über den Link fährt, wodurch freie Momente genutzt werden, um Zieldaten im Voraus anzufordern.
// 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 }
Wenn der Benutzer mit der Maus über den Link „Über uns“ fährt, beginnt die Komponente mit dem Vorladen der Daten für die Seite „/about“, wodurch ein fast sofortiger Übergang gewährleistet wird. Geniale Idee, nicht wahr? Inspiriert von der React.dev-Dokumentation.
Der app.useContext-Hook ist ein Eckpfeiler von React Edge und gewährt nahtlosen Zugriff auf den gesamten Kontext des Workers. Es bietet eine leistungsstarke Schnittstelle zum Verwalten von Routing, Status, RPC-Aufrufen und mehr.
// 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> ); };
Hauptfunktionen von app.useContext
Der app.useContext-Hook schließt die Lücke zwischen dem Worker und dem Client. Sie können damit Funktionen erstellen, die auf gemeinsamem Status, sicherem Datenabruf und kontextbezogenem Rendering ohne Boilerplate basieren. Dies vereinfacht komplexe Anwendungen, wodurch sie einfacher zu warten und schneller zu entwickeln sind.
Der app.useUrlState-Hook hält Ihren Anwendungsstatus mit den URL-Abfrageparametern synchron und bietet eine detaillierte Kontrolle darüber, was in der URL enthalten ist, wie der Status serialisiert wird und wann er aktualisiert wird.
// 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' }) ); };
Anfangszustand
Optionen:
Der app.useStorageState-Hook ermöglicht es Ihnen, den Status im Browser mithilfe von localStorage oder sessionStorage bei voller TypeScript-Unterstützung beizubehalten.
// 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' }) ); };
Reaktive Werte mühelos entprellen:
// 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 }
Behalten Sie Arrays mit eindeutigen Werten bei und wahren Sie gleichzeitig die Typsicherheit.
Der app.useDistinct-Hook ist darauf spezialisiert, zu erkennen, wann sich ein Wert tatsächlich geändert hat, und unterstützt tiefe Vergleiche und Entprellungen:
// 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' }) ); };
Die Hooks in React Edge sind so konzipiert, dass sie harmonisch zusammenarbeiten und ein flüssiges und stark typisiertes Entwicklungserlebnis bieten. Ihre Kombination ermöglicht die Erstellung komplexer und reaktiver Schnittstellen mit viel weniger Code.
Die CLI für React Edge wurde entwickelt, um das Leben von Entwicklern zu vereinfachen, indem wichtige Tools in einer einzigen, intuitiven Oberfläche zusammengefasst werden. Unabhängig davon, ob Sie Anfänger oder erfahrener Entwickler sind, stellt die CLI sicher, dass Sie Projekte effizient und mühelos konfigurieren, entwickeln, testen und bereitstellen können.
Ich bin stolz, Ihnen mitteilen zu können, dass die erste Produktionsanwendung mit React Edge bereits live ist! Es handelt sich um ein brasilianisches Immobilienunternehmen, Lopes Imóveis, das bereits von der Leistung und Flexibilität des Frameworks profitiert.
Auf ihrer Website werden Eigenschaften in den Cache geladen, um die Suche zu optimieren und ein reibungsloseres Benutzererlebnis zu bieten. Da es sich um eine hochdynamische Site handelt, verwendet das Routen-Caching eine TTL von nur 10 Sekunden in Kombination mit der Stale-While-Revalidate-Strategie. Dadurch wird sichergestellt, dass die Website aktualisierte Daten mit außergewöhnlicher Leistung liefert, selbst bei erneuten Validierungen im Hintergrund.
Darüber hinaus werden Empfehlungen für ähnliche Eigenschaften effizient und asynchron im Hintergrund berechnet und dann mithilfe des integrierten RPC-Caching-Systems direkt im Cache von Cloudflare gespeichert. Dieser Ansatz verkürzt die Antwortzeiten für nachfolgende Anfragen und macht Abfrageempfehlungen nahezu augenblicklich. Alle Bilder werden auf Cloudflare R2 gespeichert und bieten so skalierbaren und verteilten Speicher, ohne auf externe Anbieter angewiesen zu sein.
Bald werden wir auch ein umfangreiches automatisiertes Marketingprojekt für Easy Auth starten, um das Potenzial dieser Technologie noch weiter zu demonstrieren.
Und damit, liebe Leser, sind wir am Ende dieser Reise durch die Welt von React Edge angelangt! Ich weiß, dass es immer noch eine Menge unglaublicher Funktionen zu entdecken gibt, wie einfachere Authentifizierungsoptionen wie Basic und Bearer und andere Tricks, die den Tag eines Entwicklers viel glücklicher machen. Aber warte! Die Idee besteht darin, in Zukunft ausführlichere Artikel zu veröffentlichen, die sich eingehend mit den einzelnen Funktionen befassen.
Spoiler-Alarm: Bald wird React Edge Open Source sein und ordnungsgemäß dokumentiert sein! Entwicklung, Arbeit, Schreiben und ein bisschen soziales Leben unter einen Hut zu bringen, ist nicht einfach, aber die Aufregung, dieses Wunderwerk in Aktion zu sehen, insbesondere angesichts der absurden Geschwindigkeit, die die Infrastruktur von Cloudflare bietet, ist der Treibstoff, der mich antreibt. Bleiben Sie also dran, denn das Beste kommt noch! ?
Wenn Sie in der Zwischenzeit sofort damit beginnen möchten, es zu erkunden und zu testen, ist das Paket bereits auf NPM verfügbar: React Edge auf NPM.
Meine E-Mail-Adresse lautet feliperohdee@gmail.com und ich bin immer offen für Feedback – das ist erst der Anfang dieser Reise. Anregungen und konstruktive Kritik sind willkommen. Wenn Ihnen das, was Sie gelesen haben, gefallen hat, teilen Sie es mit Ihren Freunden und Kollegen und bleiben Sie auf dem Laufenden, um weitere Updates zu erhalten. Vielen Dank, dass Sie bis hierhin gelesen haben, und bis zum nächsten Mal! ???
Das obige ist der detaillierte Inhalt vonVon Next.js zu React Edge mit Cloudflare Workers: Eine Geschichte der Befreiung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!