Heim >Web-Frontend >js-Tutorial >Von Next.js zu React Edge mit Cloudflare Workers: Eine Geschichte der Befreiung

Von Next.js zu React Edge mit Cloudflare Workers: Eine Geschichte der Befreiung

Susan Sarandon
Susan SarandonOriginal
2024-11-20 14:26:17918Durchsuche

Schnellindex

  • Der letzte Strohhalm
  • Die Alternative mit Cloudflare ?
  • React Edge: Das React Framework, das aus dem Schmerz jedes Entwicklers (oder fast) entstanden ist
    • Die Magie des typisierten RPC
    • Die Kraft von useFetch: Where the Magic Happens
  • Unbrauchbar. Fetch: Das komplette Arsenal
    • RPC: Die Kunst der Client-Server-Kommunikation
    • Ein i18n-System, das Sinn macht
    • JWT-Authentifizierung, die „einfach funktioniert“
    • Der Shared Store
    • Elegantes Routing
    • Verteilter Cache mit Edge-Cache
  • Link: Die Komponente, die vorausdenkt
  • app.useContext: Das Portal zum Rand
  • app.useUrlState: Mit der URL synchronisierter Status
  • app.useStorageState: Persistenter Zustand
  • app.useDebounce: Frequenzsteuerung
  • app.useDistinct: Status ohne Duplikate
  • Die React Edge CLI: Leistung immer zur Hand
  • Fazit

Der letzte Strohhalm

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.

Die Alternative mit Cloudflare?

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.

React Edge: Das React Framework, das aus dem Schmerz jedes Entwicklers (oder fast) entstanden ist

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.

React-Entwicklung für das Edge-Computing-Zeitalter neu denken

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?

Mit React Edge müssen Sie nicht mehr:

  • Erstellen Sie separate API-Routen
  • Lade-/Fehlerstatus manuell verwalten
  • Implementieren Sie die Entprellung selbst
  • Sorgen wegen Serialisierung/Deserialisierung
  • Mit CORS umgehen
  • Verwalten Sie die Eingabe zwischen Client/Server
  • Authentifizierungsregeln manuell verwalten
  • Probleme mit der Internationalisierungsstruktur

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?

Die Magie des typisierten RPC

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' })
  );
};

Vergleichen Sie dies mit Next.js/Vercel:

// 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
}

Die Kraft von useFetch: Wo die Magie passiert

Datenabruf neu denken

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:

  • Ladet Daten während der SSR vorab auf den Server.
  • Feuchtigkeit der Daten auf dem Client automatisch ohne Flimmern.
  • Behält die vollständige Eingabe zwischen Client und Server bei.
  • Unterstützt die Reaktionsfähigkeit mit intelligenter Entprellung.
  • Automatisch multiplext identische Anrufe.
  • Ermöglicht programmatische Aktualisierungen und Abfragen.

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' })
  );
};

Die Magie des Multiplexings

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
}

SSR Perfekte Flüssigkeitszufuhr

// 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>
  );
};

Unbrauchbar. Fetch: Das komplette Arsenal

RPC: Die Kunst der Client-Server-Kommunikation

Sicherheit und Kapselung

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!
};

RPC-API-Hierarchien

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>
  );
};

Vorteile von Hierarchien

Die Organisation von APIs in Hierarchien bietet mehrere Vorteile:

  • Logische Organisation: Gruppieren Sie verwandte Funktionen intuitiv.
  • Natürlicher Namensraum: Vermeiden Sie Namenskonflikte mit klaren Pfaden (z. B. „users.preferences.getTheme“).
  • Kapselung: Halten Sie Hilfsmethoden auf jeder Ebene privat.
  • Wartbarkeit: Jede Unterklasse kann unabhängig gewartet und getestet werden.
  • Vollständige Typisierung: TypeScript versteht die gesamte Hierarchie.

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.

Ein System von i18n, das Sinn macht

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');
};

Nullkonfiguration

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}!',
};

JWT-Authentifizierung, die „einfach funktioniert“

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>
  );
};

Client-Nutzung: Keine Konfiguration

// 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;
  }
};

Warum ist das revolutionär?

  1. Null-Boilerplate

    • Keine manuelle Cookie-Verwaltung
    • Keine Abfangjäger erforderlich
    • Keine manuellen Aktualisierungstoken
  2. Standardmäßige Sicherheit

    • Tokens werden automatisch verschlüsselt
    • Cookies sind sicher und nur http
    • Automatische Revalidierung
  3. Vollständige Eingabe

    • JWT-Nutzlast ist typisiert
    • Integrierte Zod-Validierung
    • Eingegebene Authentifizierungsfehler
  4. 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' })
  );
};

Der Shared Store

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:

Beispiel für Middleware- und Store-Nutzung

// 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
}

Wie es funktioniert

  • Öffentliche Daten: Als öffentlich gekennzeichnete Daten werden sicher mit dem Kunden geteilt, sodass sie für Komponenten leicht zugänglich sind.
  • Private Daten: Sensible Daten verbleiben in der Umgebung des Mitarbeiters und werden niemals dem Kunden zugänglich gemacht.
  • Integration mit Middleware: Middleware kann den Speicher sowohl mit öffentlichen als auch mit privaten Daten füllen und so einen nahtlosen Informationsfluss zwischen serverseitiger Logik und clientseitigem Rendering gewährleisten.

Vorteile

  1. Sicherheit: Getrennte öffentliche und private Datenbereiche stellen sicher, dass vertrauliche Informationen geschützt bleiben.
  2. Bequemlichkeit: Der transparente Zugriff auf Speicherdaten vereinfacht die Statusverwaltung für Mitarbeiter und Kunden.
  3. Flexibilität: Der Store lässt sich problemlos in Middleware integrieren und ermöglicht dynamische Statusaktualisierungen basierend auf der Anforderungsbearbeitung.

Elegantes Routing

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>
  );
};

Hauptmerkmale

  • Gruppierte Routen: Logische Gruppierung verwandter Routen unter einem gemeinsamen Pfad und einer gemeinsamen Middleware.
  • Flexible Handler: Definieren Sie Handler, die Seiten oder direkte API-Antworten zurückgeben.
  • Pro-Route-Header: Passen Sie HTTP-Header für einzelne Routen an.
  • Integriertes Caching: Vereinfachen Sie Caching-Strategien mit TTL und Tags.

Vorteile

  1. Konsistenz: Durch die Gruppierung verwandter Routen stellen Sie eine konsistente Middleware-Anwendung und Code-Organisation sicher.
  2. Skalierbarkeit: Das System unterstützt verschachteltes und modulares Routing für groß angelegte Anwendungen.
  3. Leistung: Native Unterstützung für Caching sorgt für optimale Reaktionszeiten ohne manuelle Konfigurationen.

Verteilter Cache mit Edge-Cache

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.

Beispiel: API-Antworten mit Tags zwischenspeichern

// 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' })
  );
};

Hauptmerkmale

  • Tag-basierte Ungültigmachung: Cache-Einträge können mithilfe von Tags gruppiert werden, was eine einfache und selektive Ungültigmachung bei Datenänderungen ermöglicht.
  • Präfixabgleich: Machen Sie mehrere Cache-Einträge mithilfe eines gemeinsamen Präfixes ungültig, ideal für Szenarien wie Suchanfragen oder hierarchische Daten.
  • Time-to-Live (TTL): Legen Sie Ablaufzeiten für Cache-Einträge fest, um die Aktualität der Daten sicherzustellen und gleichzeitig eine hohe Leistung aufrechtzuerhalten.

Vorteile

  1. Verbesserte Leistung: Reduzieren Sie die Belastung von APIs, indem Sie zwischengespeicherte Antworten für häufig aufgerufene Daten bereitstellen.
  2. Skalierbarkeit: Behandeln Sie große Datensätze und hohen Datenverkehr effizient mit einem verteilten Caching-System.
  3. Flexibilität: Fein abgestimmte Kontrolle über das Caching, sodass Entwickler die Leistung optimieren können, ohne die Datengenauigkeit zu beeinträchtigen.

Link: Die Komponente, die vorausdenkt

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.

Wie es funktioniert

  1. Bedingter Vorabruf: Das Prefetch-Attribut (standardmäßig aktiviert) steuert, ob der Vorabruf ausgeführt wird.
  2. Intelligenter Cache: Ein Set wird zum Speichern bereits vorab abgerufener Links verwendet, wodurch redundante Abrufaufrufe vermieden werden.
  3. Mouse Enter Event: Wenn der Benutzer mit der Maus über den Link fährt, prüft die handleMouseEnter-Funktion, ob ein Vorladen erforderlich ist, und startet in diesem Fall eine Abrufanforderung für das Ziel.
  4. Fehlerresistenz: Jeder Fehler während der Anfrage wird unterdrückt, um sicherzustellen, dass das Verhalten der Komponente nicht durch vorübergehende Netzwerkprobleme beeinträchtigt wird.

Beispielverwendung

// 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.

app.useContext: Das Portal zum Edge

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.

Beispiel: Verwendung von app.useContext in einem Dashboard

// 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

  • Routenverwaltung: Erhalten Sie mühelos Zugriff auf die passende Route, ihre Parameter und Abfragezeichenfolgen.
  • RPC-Integration: Führen Sie getippte und sichere RPC-Aufrufe direkt vom Client aus ohne zusätzliche Konfiguration aus.
  • Gemeinsamer Store-Zugriff: Abrufen oder Festlegen von Werten im gemeinsamen Worker-Client-Status mit vollständiger Kontrolle über die Sichtbarkeit (öffentlich/privat).
  • Universeller URL-Zugriff: Greifen Sie einfach auf die vollständige URL der aktuellen Anfrage zu, um dynamisches Rendering und Interaktionen zu ermöglichen.

Warum es mächtig ist

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.

app.useUrlState: Mit der URL synchronisierter Status

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' })
  );
};

Parameter

  1. Anfangszustand

    • Ein Objekt, das die Standardstruktur und -werte für Ihren Bundesstaat definiert.
  2. Optionen:

    • Debounce: Steuert, wie schnell die URL nach Statusänderungen aktualisiert wird. Nützlich, um übermäßige Aktualisierungen zu verhindern.
    • kebabCase: Konvertiert Statusschlüssel bei der Serialisierung in die URL in Kebab-Case (z. B. filter.locations → filter-locations).
    • omitKeys: Gibt Schlüssel an, die aus der URL ausgeschlossen werden sollen. Beispielsweise können sensible Daten oder große Objekte weggelassen werden.
    • omitValues: Werte, die, sofern vorhanden, den zugehörigen Schlüssel aus der URL ausschließen.
    • pickKeys: Beschränkt den Serialisierungsstatus so, dass er nur bestimmte Schlüssel enthält.
    • Präfix: Fügt allen Abfrageparametern für den Namensraum ein Präfix hinzu.
    • URL: Die Basis-URL für die Synchronisierung, normalerweise abgeleitet vom App-Kontext.

Vorteile

  • Identische useState API: Einfache Integration mit vorhandenen Komponenten.
  • SEO-freundlich: Stellt sicher, dass landesspezifische Ansichten in gemeinsam nutzbaren und mit Lesezeichen versehenen URLs widergespiegelt werden.
  • Entprellte Aktualisierungen: Verhindert übermäßige Abfrageaktualisierungen für sich schnell ändernde Eingaben, wie Schieberegler oder Textfelder.
  • URLs bereinigen: Optionen wie kebabCase und omitKeys sorgen dafür, dass Abfragezeichenfolgen lesbar und relevant sind.
  • Statushydrierung: Initialisiert den Status automatisch über die URL beim Mounten der Komponente und ermöglicht so eine nahtlose Deep-Linking-Funktion.
  • Funktioniert überall: Unterstützt serverseitiges Rendering und clientseitige Navigation und sorgt so für einen konsistenten Status in der gesamten Anwendung.

Praktische Anwendungen

  • Filter für Immobilieneinträge: Synchronisieren Sie vom Benutzer angewendete Filter wie Auflistungstypen und Kartengrenzen mit der URL für gemeinsam nutzbare Suchen.
  • Dynamische Ansichten: Stellen Sie sicher, dass Kartenzoom, Mittelpunkte oder andere Ansichtseinstellungen bei Seitenaktualisierungen oder Links erhalten bleiben.
  • Benutzereinstellungen: Speichern Sie vom Benutzer ausgewählte Einstellungen in der URL, um sie einfach zu teilen oder mit Lesezeichen zu versehen.

app.useStorageState: Persistenter Zustand

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' })
  );
};

Persistenzoptionen

  • Entprellen: Steuert die Häufigkeit der Speicherung im Speicher.
  • Speicher: Wählen Sie zwischen localStorage und sessionStorage.
  • omitKeys/pickKeys: Detaillierte Kontrolle darüber, welche Daten beibehalten werden.

Leistung

  • Optimierte Updates mit Debounce.
  • Automatische Serialisierung/Deserialisierung.
  • In-Memory-Caching.

Häufige Anwendungsfälle

  • Suchverlauf
  • Favoritenliste
  • Benutzereinstellungen
  • Filterstatus
  • Temporärer Warenkorb
  • Formularentwürfe

app.useDebounce: Frequenzsteuerung

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
}

app.useDistinct: Status ohne Duplikate

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' })
  );
};

Hauptmerkmale

  1. Erkennung eindeutiger Werte:
    • Verfolgt die aktuellen und vorherigen Werte.
    • Erkennt automatisch, ob eine Änderung basierend auf Ihren Kriterien sinnvoll ist.
  2. Tiefer Vergleich:
    • Ermöglicht Wertgleichheitsprüfungen auf tiefer Ebene für komplexe Objekte.
  3. Benutzerdefinierter Vergleich:
    • Unterstützt benutzerdefinierte Funktionen, um zu definieren, was eine „eindeutige“ Änderung darstellt.
  4. Entprellt:
    • Reduziert unnötige Aktualisierungen, wenn Änderungen zu häufig auftreten.

Vorteile

  • Identische useState API: Einfache Integration mit vorhandenen Komponenten.
  • Optimierte Leistung: Vermeidet unnötiges erneutes Abrufen oder Neuberechnen, wenn sich der Wert nicht wesentlich geändert hat.
  • Verbesserte UX: Verhindert überreaktive UI-Updates und führt zu reibungsloseren Interaktionen.
  • Vereinfachte Logik: Eliminiert manuelle Prüfungen auf Gleichheit oder Duplikate in der Statusverwaltung.

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 React Edge CLI: Leistung immer zur Hand

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.

Hauptmerkmale

Modulare und flexible Befehle:

  • Build: Erstellt sowohl die App als auch den Worker mit Optionen zum Angeben von Umgebungen und Modi (Entwicklung oder Produktion).
  • dev: Startet lokale oder Remote-Entwicklungsserver und ermöglicht so separate Arbeiten an der App oder dem Worker.
  • Bereitstellung: Ermöglicht schnelle und effiziente Bereitstellungen unter Nutzung der kombinierten Leistung von Cloudflare Workers und Cloudflare R2 und sorgt so für Leistung und Skalierbarkeit in der Edge-Infrastruktur.
  • Protokolle: Überwacht Arbeiterprotokolle direkt im Terminal.
  • lint: Automatisiert die Ausführung von Prettier und ESLint mit Unterstützung für automatische Korrekturen.
  • Test: Führt Tests mit optionaler Abdeckung mit Vitest aus.
  • Typprüfung: Validiert die TypeScript-Eingabe im gesamten Projekt.

Anwendungsfälle aus der Praxis

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.

From Next.js to React Edge with Cloudflare Workers: A Story of Liberation
From Next.js to React Edge with Cloudflare Workers: A Story of Liberation

Bald werden wir auch ein umfangreiches automatisiertes Marketingprojekt für Easy Auth starten, um das Potenzial dieser Technologie noch weiter zu demonstrieren.

Abschluss

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!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn