ホームページ >ウェブフロントエンド >jsチュートリアル >Next.js から Cloudflare ワーカーを使用した React Edge へ: 解放の物語
すべては Vercel の請求書から始まりました。いいえ、実際には、それはずっと前から始まり、小さなフラストレーションが積み重なっていました。 DDoS 保護、より詳細なログ、さらには適切なファイアウォール、ビルド キューなどの基本機能に料金を支払う必要性。ますます高価になるベンダー ロックインに閉じ込められている感覚。
「そして最悪のことに、私たちの貴重な SEO ヘッダーが、ページ ルーターを使用するアプリケーションのサーバー上でレンダリングを停止してしまいました。開発者にとっては本当に頭の痛い問題です!?」
しかし、本当にすべてを考え直させたのは、Next.js が目指している方向性でした。 use client、use server ディレクティブの導入により、理論的には開発が簡素化されるはずですが、実際には管理がさらに複雑になります。それは 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 などの何十ものディレクティブを覚えなくても?さらに良いことに、サーバー関数をローカルであるかのように、完全な型付けと設定なしで呼び出すことができたらどうでしょうか?
そして最も重要な点は、これらすべてが、クライアント使用またはサーバー使用として何もマークすることなく、サーバーとクライアントの両方でシームレスに機能することです。フレームワークはコンテキストに基づいて何をすべきかを自動的に認識します。飛び込んでみませんか?
これができると想像してみてください:
// 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 }
React でのデータ取得について知っていることはすべて忘れてください。 React Edge の app.useFetch フックは、まったく新しい強力なアプローチを導入します。次のようなフックを想像してください:
実際に動作を見てみましょう:
// 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' }) ); };
上記の例では、インテリジェントな多重化という強力な機能が隠されています。 ctx.rpc.batch を使用すると、React Edge は呼び出しをグループ化するだけでなく、同一の呼び出しを自動的に重複排除します。
// 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> ); };
React Edge の RPC システムは、セキュリティとカプセル化を念頭に置いて設計されています。 RPC クラス内のすべてが自動的にクライアントに公開されるわけではありません:
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 の最も強力な機能の 1 つは、API を階層に編成する機能です。
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> ); };
API を階層に編成すると、次のような利点があります。
React Edge の RPC システムにより、クライアントとサーバー間の通信が非常に自然になるため、リモート呼び出しを行っていることをほとんど忘れてしまいます。 API を階層に編成する機能により、コードをクリーンで安全に保ちながら複雑な構造を構築できます。
React Edge は、重いライブラリに依存せずに変数補間と複雑な書式設定をサポートする、エレガントで柔軟な国際化システムを導入しています。
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; };
コード内での使用法:
// 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 は翻訳を自動的に検出して読み込みます。ユーザー設定を Cookie に簡単に保存することもできます。でも、当然、これは予想できますよね?
// translations/fr.ts export default { 'Good Morning, {name}!': 'Bonjour, {name}!', };
Web アプリケーションでは認証が常に問題点でした。 JWT トークン、セキュア Cookie、再検証の管理には、多くの場合、多くの定型コードが必要になります。 React Edge はこれを完全に変えます。
完全な認証システムの実装がいかに簡単かを次に示します:
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; } };
定型文ゼロ
デフォルトのセキュリティ
完全な入力
シームレスな統合
// 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' }) ); };
React Edge の最も強力な機能の 1 つは、ワーカーとクライアントの間で状態を安全に共有できる機能です。仕組みは次のとおりです:
// 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 }
React Edge のルーティング システムは Hono からインスピレーションを受けていますが、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 には、JSON データとページ全体の両方に対してシームレスに動作する強力なキャッシュ システムが含まれています。このキャッシュ システムは、インテリジェントなタグ付けとプレフィックス ベースの無効化をサポートしているため、幅広いシナリオに適しています。
// 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 }
ユーザーが「About Us」リンクの上にマウスを移動すると、コンポーネントは /about ページのデータのプリロードを開始し、ほぼ瞬時に移行します。天才的なアイデアですね。 React.dev ドキュメントからインスピレーションを受けています。
app.useContext フックは React Edge の基礎であり、ワーカーのコンテキスト全体へのシームレスなアクセスを許可します。ルーティング、状態、RPC 呼び出しなどを管理するための強力なインターフェイスを提供します。
// 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> ); };
app.useContext の主な機能
app.useContext フックは、ワーカーとクライアントの間のギャップを橋渡しします。これにより、ボイラープレートを使用せずに、共有状態、安全なデータ取得、コンテキスト レンダリングに依存する機能を構築できます。これにより、複雑なアプリケーションが簡素化され、保守が容易になり、開発が迅速化されます。
app.useUrlState フックは、アプリケーションの状態を URL クエリ パラメーターと同期させて、URL に含まれる内容、状態のシリアル化方法、更新時期をきめ細かく制御できます。
// 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' }) ); };
初期状態
オプション:
app.useStorageState フックを使用すると、TypeScript を完全にサポートし、localStorage または sessionStorage を使用してブラウザーで状態を保持できます。
// 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 }
型の安全性を維持しながら、一意の値を持つ配列を保持します。
app.useDistinct フックは、値が実際に変更されたときの検出に特化しており、詳細な比較とデバウンスをサポートしています。
// 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' }) ); };
React Edge のフックは調和して動作するように設計されており、流動的で厳密に型指定された開発エクスペリエンスを提供します。これらを組み合わせることで、はるかに少ないコードで複雑でリアクティブなインターフェイスを作成できます。
React Edge の CLI は、重要なツールを 1 つの直感的なインターフェイスに集めて開発者の作業を簡素化するように設計されました。初心者でも経験豊富な開発者でも、CLI を使用すると、プロジェクトを効率的かつ簡単に構成、開発、テスト、デプロイできます。
React Edge を使用した最初の運用アプリケーションがすでに公開されていることを誇りに思います。これはブラジルの不動産会社、Lopes Imóveis であり、フレームワークのパフォーマンスと柔軟性の恩恵をすでに享受しています。
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 ワーカーを使用した React Edge へ: 解放の物語の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。