ホームページ >ウェブフロントエンド >jsチュートリアル >Next.js から Cloudflare Workers を使用した React Edge へ: 解放ストーリー
すべては Vercel からの請求書から始まりました。いいえ、実際にはもっと早くから始まり、小さなフラストレーションが蓄積していました。 DDoS 保護、より詳細なログ、さらには適切なファイアウォール、ビルド キューなどの基本機能に対して料金を支払う必要があります。ますます高価になるベンダーロックインに閉じ込められているような感覚。
「そして最悪のことに、私たちの貴重な SEO ヘッダーが、ページ ルーターを使用するアプリケーションのサーバー上でレンダリングされなくなってしまいました。開発者にとっては本当に頭の痛い問題です!?」
しかし、私にすべてを本当に考え直させたのは、Next.js が取っている方向性でした。使用クライアント、使用サーバーの導入により、理論上は開発が簡素化されるはずですが、実際には管理がさらに複雑になります。まるで PHP の時代に戻って、ファイルにディレクティブをマークして、どこで実行するかを指示しているかのようでした。
そして、それだけではありません。 App Router は興味深いアイデアですが、Next.js 内に実質的に新しいフレームワークを作成する方法で実装されました。突然、同じことを行うのに 2 つのまったく異なる方法ができました。 「古い」ものと「新しい」もの - 微妙に異なる動作と隠された罠があります。
そのとき私は気づきました。エッジで実行されるワーカー、ストレージ用の R2、分散データ用の KV を備えた Cloudflare の素晴らしいインフラストラクチャを活用してはいかがでしょうか...さらに、もちろん、驚くべき DDoS 保護、グローバル CDN、ファイアウォール、ルールを利用してはいかがでしょうかページとルート、そしてCloudflareが提供するその他すべてのものについて。
そして最良の点は、使用した分に対して驚くことなく支払う公正価格モデルです。
こうして React Edge が誕生しました。車輪の再発明を試みるのではなく、真にシンプルでモダンな開発エクスペリエンスを提供するフレームワークです。
React Edge の開発を始めたとき、私には明確な目標がありました。それは、意味のあるフレームワークを作成することです。紛らわしいディレクティブに苦労することも、基本的な機能に大金を払うことも、そして最も重要なことに、クライアントとサーバーの分離によって生じる人為的な複雑さに対処する必要ももうありません。私は、シンプルさを犠牲にすることなくパフォーマンスを実現するスピードを求めていました。 React API の知識と、JavaScript および Golang 開発者としての長年の知識を活用して、ストリームと多重化を処理してレンダリングとデータ管理を最適化する方法を正確に知っていました。
Cloudflare Workers は、強力なインフラストラクチャと世界的な存在感を備えており、これらの可能性を探求するのに最適な環境を私に提供してくれました。私は真にハイブリッドなものを望んでいました。このツールと経験の組み合わせが、最新の効率的なソリューションで実際の問題を解決するフレームワークである React Edge に命を吹き込みました。
React Edge は、React 開発に革新的なアプローチをもたらします。完全な型付けと設定なしで、サーバー上にクラスを作成し、それをクライアントから直接呼び出すことができることを想像してください。タグやプレフィックスによる無効化を可能にして「機能する」分散キャッシュ システムを想像してみてください。透過的かつ安全な方法でサーバーとクライアントの間で状態を共有できることを想像してみてください。認証を簡素化し、効率的な国際化アプローチを実現するだけでなく、CLI なども提供します。
RPC 通信は魔法のように自然です。クラス内にメソッドを記述し、ローカルであるかのようにクライアントからメソッドを呼び出します。インテリジェントな多重化システムにより、複数のコンポーネントが同じ呼び出しを行った場合でも、サーバーに対して行われるリクエストは 1 つだけになります。一時キャッシュは不必要に繰り返されるリクエストを回避し、これはすべてサーバーとクライアントの両方で機能します。
最も強力なポイントの 1 つは、データ取得エクスペリエンスを統合する app.useFetch フックです。サーバー上では、SSR 中にデータがプリロードされます。クライアントでは、このデータが自動的にハイドレートされ、オンデマンドの更新が可能になります。また、自動ポーリングと依存関係ベースの反応性のサポートにより、動的インターフェイスの作成がかつてないほど簡単になりました。
しかし、それだけではありません。このフレームワークは、強力なルーティング システム (素晴らしい Hono からインスピレーションを得た)、Cloudflare R2 と統合された資産管理、および HttpError クラスを介してエラーを処理するエレガントな方法を提供します。ミドルウェアは共有ストアを通じてクライアントにデータを簡単に送信でき、セキュリティのためにすべてが自動的に難読化されます。
最も印象的だったのは?フレームワークのコードのほぼすべてがハイブリッドです。 「クライアント」バージョンと「サーバー」バージョンはありません。同じコードが両方の環境で動作し、コンテキストに自動的に適応します。顧客は必要なものだけを受け取り、最終的なバンドルが非常に最適化されます。
そしておまけに、これらはすべて Cloudflare Workers エッジインフラストラクチャ上で実行され、適正なコストで優れたパフォーマンスを提供します。この法案に驚くようなことはなく、強制的なエンタープライズ プランの背後に隠された基本的な機能もなく、本当に重要なこと、つまり素晴らしいアプリケーションの作成に集中できる強固なフレームワークがあるだけです。さらに、React Edge は、キュー、デュラブル オブジェクト、KV ストレージなどを含む Cloudflare エコシステム全体を活用して、アプリケーションに堅牢でスケーラブルな基盤を提供します。
Vite は、開発環境とテストとビルドの両方のベースとして使用されました。 Vite は、驚異的なスピードと最新のアーキテクチャを備えており、機敏で効率的なワークフローを実現します。開発をスピードアップするだけでなく、ビルドプロセスを最適化し、コードが迅速かつ正確にコンパイルされるようにします。疑いもなく、Vite は React Edge にとって完璧な選択でした。
クライアント/サーバーの壁を気にせずに React アプリケーションを開発できたらどうなるだろうかと考えたことはありますか? use client や use server などの何十ものディレクティブを覚える必要はありません。さらに良いことに、サーバー関数をローカルであるかのように、完全な型付けと設定なしで呼び出すことができたらどうでしょうか?
そして最も良い点は、このすべてがサーバーとクライアントの両方で機能することです。use client または use server で何かをマークする必要はありません。フレームワークはコンテキストに基づいて何をすべきかを認識します。行きましょうか?
これができると想像してみてください:
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! 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) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // 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]); // ... resto do componente }
React でのデータ取得について知っていることはすべて忘れてください。 React Edge の app.useFetch は、まったく新しい強力なアプローチをもたらします。次のようなフックを想像してください:
これを実際に見てみましょう:
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
上記の例では、インテリジェントな多重化という強力な機能が隠されています。 ctx.rpc.batch を使用すると、React Edge は呼び出しをバッチ処理するだけでなく、同一の呼び出しを自動的に重複排除します。
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // 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]); // ... resto do componente }
最も印象的な部分の 1 つは、useFetch による SSR の処理方法です。
// Primeiro, definimos nossa API no servidor class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Cache automático por 5 minutos return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Agora, no cliente, a mágica acontece const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Busca reativa com debounce inteligente const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Quando filters muda, refaz a busca deps: [filters], // Mas espera 300ms de 'silêncio' antes de buscar depsDebounce: { filters: 300 } } ); // Agora, vamos buscar os detalhes das propriedades encontradas const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // Isso parece fazer múltiplas chamadas, mas... return ctx.rpc.batch([ // Na verdade, tudo é multiplexado em uma única requisição! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Atualiza sempre que searchResults mudar deps: [searchResults] } ); // Interface bonita e responsiva return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Erro na busca: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
React Edge の RPC システムは、セキュリティとカプセル化を念頭に置いて設計されました。 RPC クラス内のすべてが自動的にクライアントに公開されるわけではありません:
const PropertyListingPage = () => { const { data } = app.useFetch(async (ctx) => { // Mesmo que você faça 100 chamadas idênticas... return ctx.rpc.batch([ ctx.rpc.getProperty('123'), ctx.rpc.getProperty('123'), // mesma chamada ctx.rpc.getProperty('456'), ctx.rpc.getProperty('456'), // mesma chamada ]); }); // Mas na realidade: // 1. O batch agrupa todas as chamadas em UMA única requisição HTTP // 2. Chamadas idênticas são deduplicas automaticamente // 3. O resultado é distribuído corretamente para cada posição do array // 4. A tipagem é mantida para cada resultado individual! // Entao.. // 1. getProperty('123') // 2. getProperty('456') // E os resultados são distribuídos para todos os chamadores! };
RPC の最も強力な機能の 1 つは、API を階層に編成する機能です。
const ProductPage = ({ productId }: Props) => { const { data, loaded, loading, error } = app.useFetch( async (ctx) => ctx.rpc.getProduct(productId), { // Controle fino de quando executar shouldFetch: ({ worker, loaded }) => { // No worker (SSR): sempre busca if (worker) return true; // No cliente: só busca se não tiver dados return !loaded; } } ); // No servidor: // 1. useFetch faz a chamada RPC // 2. Dados são serializados e enviados ao cliente // 3. Componente renderiza com os dados // No cliente: // 1. Componente hidrata com os dados do servidor // 2. Não faz nova chamada (shouldFetch retorna false) // 3. Se necessário, pode refazer a chamada com data.fetch() return ( <Suspense fallback={<ProductSkeleton />}> <ProductView product={data} loading={loading} error={error} /> </Suspense> ); };
API を階層に編成すると、次のような利点が得られます。
React Edge の RPC システムにより、クライアントとサーバー間の通信が非常に自然になるため、リモート呼び出しを行っていることをほとんど忘れてしまいます。また、API を階層に整理する機能により、コードを整理して安全に保ちながら、複雑な構造を作成できます。
React Edge は、重いライブラリを使用せずに変数補間と複雑な書式設定をサポートする、エレガントで柔軟な国際化システムをもたらします。
class PaymentsAPI extends Rpc { // Propriedades nunca são expostas private stripe = new Stripe(process.env.STRIPE_KEY); // Métodos começando com $ são privados private async $validateCard(card: CardInfo) { return await this.stripe.cards.validate(card); } // Métodos começando com _ também são privados private async _processPayment(amount: number) { return await this.stripe.charges.create({ amount }); } // Este método é público e acessível via RPC async createPayment(orderData: OrderData) { // Validação interna usando método privado const validCard = await this.$validateCard(orderData.card); if (!validCard) { throw new HttpError(400, 'Invalid card'); } // Processamento usando outro método privado const payment = await this._processPayment(orderData.amount); return payment; } } // No cliente: const PaymentForm = () => { const { rpc } = app.useContext<App.Context>(); // ✅ Isso funciona const handleSubmit = () => rpc.createPayment(data); // ❌ Isso não é possível - métodos privados não são expostos const invalid1 = () => rpc.$validateCard(data); const invalid2 = () => rpc._processPayment(100); // ❌ Isso também não funciona - propriedades não são expostas const invalid3 = () => rpc.stripe; };
コードでの使用法:
// APIs aninhadas para melhor organização class UsersAPI extends Rpc { // Subclasse para gerenciar preferences preferences = new UserPreferencesAPI(); // Subclasse para gerenciar notificações 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 { // Métodos privados continuam privados 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); } } } // No cliente: const UserProfile = () => { const { rpc } = app.useContext<App.Context>(); const { data: profile } = app.useFetch( async (ctx) => { // Chamadas aninhadas são totalmente tipadas const [user, theme, notificationSettings] = await ctx.rpc.batch([ // Método da classe principal ctx.rpc.getProfile('123'), // Método da subclasse de preferências ctx.rpc.preferences.getTheme('123'), // Método da subclasse de notificações ctx.rpc.notifications.getSettings('123') ]); return { user, theme, notificationSettings }; } ); // ❌ Métodos privados continuam inacessíveis const invalid = () => rpc.notifications.$sendPush('123', 'hello'); };
React Edge は翻訳を自動的に検出して読み込み、ユーザー設定を Cookie に簡単に保存できます。でも、あなたはすでにそれを予想していましたよね?
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
Web アプリケーションでは認証が常に問題点でした。 JWT トークン、安全な Cookie、再検証の管理 - 通常、これらすべてには多くの定型コードが必要です。 React Edge はこれを完全に変えます。
完全な認証システムの実装がいかに簡単かをご覧ください:
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // 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]); // ... resto do componente }
// Primeiro, definimos nossa API no servidor class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Cache automático por 5 minutos return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Agora, no cliente, a mágica acontece const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Busca reativa com debounce inteligente const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Quando filters muda, refaz a busca deps: [filters], // Mas espera 300ms de 'silêncio' antes de buscar depsDebounce: { filters: 300 } } ); // Agora, vamos buscar os detalhes das propriedades encontradas const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // Isso parece fazer múltiplas chamadas, mas... return ctx.rpc.batch([ // Na verdade, tudo é multiplexado em uma única requisição! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Atualiza sempre que searchResults mudar deps: [searchResults] } ); // Interface bonita e responsiva return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Erro na busca: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
定型文ゼロ
デフォルトのセキュリティ
入力を完了してください
シームレスな統合
const PropertyListingPage = () => { const { data } = app.useFetch(async (ctx) => { // Mesmo que você faça 100 chamadas idênticas... return ctx.rpc.batch([ ctx.rpc.getProperty('123'), ctx.rpc.getProperty('123'), // mesma chamada ctx.rpc.getProperty('456'), ctx.rpc.getProperty('456'), // mesma chamada ]); }); // Mas na realidade: // 1. O batch agrupa todas as chamadas em UMA única requisição HTTP // 2. Chamadas idênticas são deduplicas automaticamente // 3. O resultado é distribuído corretamente para cada posição do array // 4. A tipagem é mantida para cada resultado individual! // Entao.. // 1. getProperty('123') // 2. getProperty('456') // E os resultados são distribuídos para todos os chamadores! };
React Edge の最も強力な機能の 1 つは、ワーカーとクライアントの間で状態を安全に共有する機能です。これがどのように機能するかを見てみましょう:
const ProductPage = ({ productId }: Props) => { const { data, loaded, loading, error } = app.useFetch( async (ctx) => ctx.rpc.getProduct(productId), { // Controle fino de quando executar shouldFetch: ({ worker, loaded }) => { // No worker (SSR): sempre busca if (worker) return true; // No cliente: só busca se não tiver dados return !loaded; } } ); // No servidor: // 1. useFetch faz a chamada RPC // 2. Dados são serializados e enviados ao cliente // 3. Componente renderiza com os dados // No cliente: // 1. Componente hidrata com os dados do servidor // 2. Não faz nova chamada (shouldFetch retorna false) // 3. Se necessário, pode refazer a chamada com data.fetch() return ( <Suspense fallback={<ProductSkeleton />}> <ProductView product={data} loading={loading} error={error} /> </Suspense> ); };
React Edge のルーティング システムは Hono からインスピレーションを得ていますが、SSR のスーパーパワーを備えています。
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
React Edge には、JSON データとページ全体の両方で機能する強力なキャッシュ システムがあります。
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // 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]); // ... resto do componente }
リンク コンポーネントは、クライアント側でリソースをプリロードするためのインテリジェントでパフォーマンスの高いソリューションであり、ユーザーにとってよりスムーズで高速なナビゲーションを保証します。プリフェッチ機能は、リンク上にカーソルを置くとアクティブになり、ユーザーの非アクティブな瞬間を利用して、宛先データを事前にリクエストします。
それはどのように機能しますか?
条件付きプリフェッチ: プリフェッチ属性 (デフォルトでアクティブ) は、プリロードを実行するかどうかを制御します。
スマート キャッシュ: セットは、すでにプリロードされたリンクを保存するために使用され、冗長な呼び出しを回避します。
Mouse Enter: ユーザーがリンク上にカーソルを置くと、handleMouseEnter 関数はプリロードが必要かどうかをチェックし、必要な場合は宛先へのフェッチ リクエストを開始します。
エラー セーフ: リクエスト内の失敗はすべて抑制され、コンポーネントの動作が一時的なネットワーク エラーによって影響を受けないことが保証されます。
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
ユーザーが「About Us」リンクの上にマウスを置くと、コンポーネントは /about ページからデータのプリロードを開始し、ほぼ瞬時に移行します。素晴らしいアイデアですよね?しかし、私はそれをreact.devドキュメントで見ました。
app.useContext は React Edge の基本的なフックであり、ワーカー コンテキスト全体へのアクセスを提供します。
// pages/api/search.ts export default async handler = (req, res) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // 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]); // ... resto do componente }
app.useContext フックは、ワーカーとクライアントの間のギャップを橋渡しします。これにより、コードを繰り返すことなく、共有状態、安全なデータ取得、コンテキスト レンダリングに依存する機能を構築できます。これにより、複雑なアプリケーションが簡素化され、保守が容易になり、開発が迅速化されます。
app.useUrlState フックは、アプリケーションの状態を URL パラメーターと同期させて、URL に含まれる内容、状態のシリアル化方法、更新時期を正確に制御できるようにします。
// Primeiro, definimos nossa API no servidor class PropertiesAPI extends Rpc { async searchProperties(filters: PropertyFilters) { const results = await this.db.properties.search(filters); // Cache automático por 5 minutos return this.createResponse(results, { cache: { ttl: 300, tags: ['properties'] } }); } async getPropertyDetails(ids: string[]) { return Promise.all( ids.map(id => this.db.properties.findById(id)) ); } } // Agora, no cliente, a mágica acontece const PropertySearch = () => { const [filters, setFilters] = useState<PropertyFilters>({ price: { min: 100000, max: 500000 }, bedrooms: 2 }); // Busca reativa com debounce inteligente const { data: searchResults, loading: searchLoading, error: searchError } = app.useFetch( async (ctx) => ctx.rpc.searchProperties(filters), { // Quando filters muda, refaz a busca deps: [filters], // Mas espera 300ms de 'silêncio' antes de buscar depsDebounce: { filters: 300 } } ); // Agora, vamos buscar os detalhes das propriedades encontradas const { data: propertyDetails, loading: detailsLoading, fetch: refreshDetails } = app.useFetch( async (ctx) => { if (!searchResults?.length) return null; // Isso parece fazer múltiplas chamadas, mas... return ctx.rpc.batch([ // Na verdade, tudo é multiplexado em uma única requisição! ...searchResults.map(result => ctx.rpc.getPropertyDetails(result.id) ) ]); }, { // Atualiza sempre que searchResults mudar deps: [searchResults] } ); // Interface bonita e responsiva return ( <div> <FiltersPanel value={filters} onChange={setFilters} disabled={searchLoading} /> {searchError && ( <Alert status='error'> Erro na busca: {searchError.message} </Alert> )} <PropertyGrid items={propertyDetails || []} loading={detailsLoading} onRefresh={() => refreshDetails()} /> </div> ); };
初期状態
オプション:
app.useStorageState フックを使用すると、完全な入力サポートを備えた localStorage または sessionStorage を使用してブラウザーで状態を保持できます。
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! 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) => { // Configurar CORS // Validar request // Tratar erros // Serializar resposta // ...100 linhas depois... } // 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]); // ... resto do componente }
次のように入力して一意の値の配列を維持します:
app.useDistinct は、値が実際に変更されたときの検出に特化したフックで、詳細な比較とデバウンスをサポートしています。
// No servidor class UserAPI extends Rpc { async searchUsers(query: string, filters: UserFilters) { // Validação com Zod const validated = searchSchema.parse({ query, filters }); return this.db.users.search(validated); } } // No cliente const UserSearch = () => { const { rpc } = app.useContext<App.Context>(); // TypeScript sabe exatamente o que searchUsers aceita e retorna! const { data, loading, error, fetch: retry } = app.useFetch( async (ctx) => ctx.rpc.searchUsers('John', { age: '>18' }) ); };
React Edge フックは調和して動作するように設計されており、流動的で型指定された開発エクスペリエンスを提供します。これらを組み合わせることで、はるかに少ないコードで複雑でリアクティブなインターフェイスを作成できます。
React Edge CLI は、重要なツールを単一の直感的なインターフェイスにまとめることにより、開発者の作業を簡素化するように設計されました。初心者でも専門家でも、CLI を使用すると、プロジェクトを手間をかけずに効率的に構成、開発、テスト、デプロイできます。
主な機能
React Edge を使用した最初の運用アプリケーションが動作していることを共有できることを誇りに思います。これはブラジルの不動産会社、Lopes Imóveis であり、フレームワークのすべてのパフォーマンスと柔軟性をすでに活用しています。
不動産業者の Web サイトでは、検索を最適化し、より流動的なエクスペリエンスをユーザーに提供するために、物件がキャッシュに読み込まれます。これは非常に動的な Web サイトであるため、ルート キャッシュでは stale-while-revalidate 戦略と組み合わせて、わずか 10 秒の TTL が使用されます。これにより、バックグラウンドでの再検証中であっても、サイトは優れたパフォーマンスで最新のデータを確実に提供できます。
さらに、同様のプロパティの推奨事項がバックグラウンドで効率的に計算され、RPC に統合されたキャッシュ システムを使用して Cloudflare のキャッシュに直接保存されます。このアプローチにより、後続のリクエストの応答時間が短縮され、推奨事項のクエリがほぼ瞬時に行われます。さらに、すべてのイメージは Cloudflare R2 に保存され、外部プロバイダーに依存せずにスケーラブルな分散ストレージを提供します。
そして間もなく、Easy Auth の巨大な自動マーケティング プロジェクトも立ち上げ、このテクノロジーの可能性をさらに実証する予定です。
それで、親愛なる読者の皆さん、React Edge の世界を巡るこの冒険も終わりに来ました! Basic や Bearer などの最も単純な認証や、開発者の日常生活をより幸せにするその他の小さな秘密など、探索すべき信じられないほどのことがまだたくさんあることを私は知っています。でも、落ち着いてください!将来的には、これらの各機能を真っ先に掘り下げる、より詳細な記事を提供する予定です。
そして、ネタバレ: もうすぐ React Edge がオープンソースになり、適切に文書化されます。開発、仕事、執筆、そしてちょっとした社会生活とのバランスをとるのは簡単ではありませんが、特に Cloudflare のインフラストラクチャによってもたらされる途方もないスピードで、この驚異が実際に動作しているのを見るときの興奮が、私を動かす原動力です。最高の状態はまだこれからなので、不安は抑えてください。 ?
それまでの間、今すぐ探索とテストを開始したい場合は、パッケージが NPM で利用可能になりました: React Edge on NPM..
私のメールアドレスは feliperohdee@gmail.com です。いつでもフィードバックをお待ちしています。これはこの旅、提案、建設的な批判の始まりにすぎません。読んだ内容が気に入ったら、友人や同僚と共有し、今後の新しい内容に注目してください。ここまで、そして次回までお付き合いいただき、ありがとうございました! ???
以上がNext.js から Cloudflare Workers を使用した React Edge へ: 解放ストーリーの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。