Next.js から Cloudflare ワーカーを使用した React Edge へ: 解放の物語
クイックインデックス
- 最後のわら
- Cloudflare の代替手段?
-
React Edge: すべての開発者の苦悩から (またはほぼ) 誕生した React フレームワーク
- 型付き RPC の魔法
- useFetch の力: 魔法が起こる場所
-
useFetch を超えて: 完全なアーセナル
- RPC: クライアントサーバー通信の技術
- 理にかなった i18n システム
- 「正しく機能する」JWT 認証
- 共有ストア
- エレガントなルーティング
- エッジキャッシュを使用した分散キャッシュ
- リンク: 先を考えるコンポーネント
- app.useContext: エッジへのポータル
- app.useUrlState: URL と同期された状態
- app.useStorageState: 永続状態
- app.useDebounce: 周波数制御
- app.useDistinct: 重複のない状態
- React Edge CLI: 指先ひとつでパワーを発揮
- 結論
最後のわら
すべては Vercel の請求書から始まりました。いいえ、実際には、それはずっと前から始まり、小さなフラストレーションが積み重なっていました。 DDoS 保護、より詳細なログ、さらには適切なファイアウォール、ビルド キューなどの基本機能に料金を支払う必要性。ますます高価になるベンダー ロックインに閉じ込められている感覚。
「そして最悪のことに、私たちの貴重な SEO ヘッダーが、ページ ルーターを使用するアプリケーションのサーバー上でレンダリングを停止してしまいました。開発者にとっては本当に頭の痛い問題です!?」
しかし、本当にすべてを考え直させたのは、Next.js が目指している方向性でした。 use client、use server ディレクティブの導入により、理論的には開発が簡素化されるはずですが、実際には管理がさらに複雑になります。それは PHP の時代に戻って、どこで実行するかを指示するディレクティブでファイルにマークを付けるようなものでした。
これで終わりではありません。 App Router — 興味深いアイデアですが、Next.js 内にほぼまったく新しいフレームワークを作成する方法で実装されました。突然、同じことを行うのに 2 つの完全に異なる方法 (「古い」方法と「新しい」) が存在し、微妙に異なる動作と隠れた落とし穴が存在しました。
Cloudflareの代替案?
そこで思いつきました。ワーカーがエッジで実行され、ストレージには R2、分散データには KV を備えた Cloudflare の素晴らしいインフラストラクチャを活用してはいかがでしょうか... もちろん、驚くべき DDoS 保護、グローバル CDN、ファイアウォールとともに、ページルールとルーティング、その他Cloudflareが提供するすべてのもの。
そして最も優れている点は、使用した分だけ料金を支払う公正な価格モデルです。
こうして React Edge が誕生しました。車輪の再発明を目的とするのではなく、真にシンプルでモダンな開発エクスペリエンスを提供するフレームワークです。
React Edge: すべての開発者の苦悩から (またはほぼ) 誕生した React フレームワーク
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 開発の再考
クライアント/サーバーの壁を気にせずに React アプリケーションを開発できたらどうなるだろうかと考えたことはありますか? use client や use server などの何十ものディレクティブを覚えなくても?さらに良いことに、サーバー関数をローカルであるかのように、完全な型付けと設定なしで呼び出すことができたらどうでしょうか?
React Edge を使用すると、次のことは必要なくなります。
- 個別の API ルートを作成する
- 読み込み/エラー状態を手動で管理する
- デバウンスを自分で実装する
- シリアル化/逆シリアル化が心配
- CORS に対処する
- クライアント/サーバー間の入力を管理
- 認証ルールを手動で処理する
- 国際化設定に苦労しています
そして最も重要な点は、これらすべてが、クライアント使用またはサーバー使用として何もマークすることなく、サーバーとクライアントの両方でシームレスに機能することです。フレームワークはコンテキストに基づいて何をすべきかを自動的に認識します。飛び込んでみませんか?
型付き RPC の魔法
これができると想像してみてください:
// 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' }) ); };
これを 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 }
useFetchの力: 魔法が起こる場所
データ取得の再考
React でのデータ取得について知っていることはすべて忘れてください。 React Edge の app.useFetch フックは、まったく新しい強力なアプローチを導入します。次のようなフックを想像してください:
- SSR 中にサーバーにデータをプリロードします。
- ちらつきなく、クライアント上のデータを自動的にハイドレートします。
- クライアントとサーバーの間で完全な型付けを維持します。
- インテリジェントなデバウンスにより反応性をサポートします。
- 同一の通話を自動的に多重化します。
- プログラムによる更新とポーリングを有効にします。
実際に動作を見てみましょう:
// 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 }
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> ); };
useFetch を超えて: 完全なアーセナル
RPC: クライアントサーバー通信の技術
セキュリティとカプセル化
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 API 階層
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 を階層に編成すると、次のような利点があります。
- 論理的な構成: 関連する機能を直感的にグループ化します。
- 自然な名前空間: 明確なパス (users.preferences.getTheme など) との名前の競合を回避します。
- カプセル化: 各レベルでヘルパー メソッドをプライベートに保ちます。
- 保守性: 各サブクラスは個別に保守およびテストできます。
- 完全な型付け: TypeScript は階層全体を理解します。
React Edge の RPC システムにより、クライアントとサーバー間の通信が非常に自然になるため、リモート呼び出しを行っていることをほとんど忘れてしまいます。 API を階層に編成する機能により、コードをクリーンで安全に保ちながら複雑な構造を構築できます。
理にかなった i18n のシステム
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}!', };
「正しく機能する」JWT 認証
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; } };
なぜこれが革命的なのでしょうか?
-
定型文ゼロ
- 手動による Cookie 管理は不要
- インターセプターは必要ありません
- 手動更新トークンはありません
-
デフォルトのセキュリティ
- トークンは自動的に暗号化されます
- Cookie は安全で httpOnly です
- 自動再検証
-
完全な入力
- JWT ペイロードが型指定されています
- 統合された Zod 検証
- 入力された認証エラー
シームレスな統合
// 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> ); };
主な特長
- グループ化されたルート: 共有パスとミドルウェアの下で関連するルートを論理的にグループ化します。
- 柔軟なハンドラー: ページを返すハンドラー、または API 応答を指示するハンドラーを定義します。
- ルートごとのヘッダー: 個々のルートの HTTP ヘッダーをカスタマイズします。
- 組み込みキャッシュ: ttl とタグを使用してキャッシュ戦略を簡素化します。
利点
- 一貫性: 関連するルートをグループ化することで、一貫したミドルウェア アプリケーションとコード構成が確保されます。
- スケーラビリティ: システムは、大規模アプリケーション向けにネストされたモジュール型ルーティングをサポートします。
- パフォーマンス: キャッシュのネイティブ サポートにより、手動構成を行わなくても最適な応答時間が確保されます。
エッジキャッシュを使用した分散キャッシュ
React Edge には、JSON データとページ全体の両方に対してシームレスに動作する強力なキャッシュ システムが含まれています。このキャッシュ システムは、インテリジェントなタグ付けとプレフィックス ベースの無効化をサポートしているため、幅広いシナリオに適しています。
例: タグを使用した API 応答のキャッシュ
// 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' }) ); };
主な特長
- タグベースの無効化: キャッシュ エントリはタグを使用してグループ化できるため、データ変更時に簡単かつ選択的に無効化できます。
- プレフィックス マッチング: 共通のプレフィックスを使用して複数のキャッシュ エントリを無効にし、検索クエリや階層データなどのシナリオに最適です。
- 存続時間 (TTL): キャッシュ エントリの有効期限を設定して、高いパフォーマンスを維持しながらデータの鮮度を確保します。
利点
- パフォーマンスの向上: 頻繁にアクセスされるデータに対してキャッシュされた応答を提供することで、API の負荷を軽減します。
- スケーラビリティ: 分散キャッシュ システムにより、大規模なデータセットと高トラフィックを効率的に処理します。
- 柔軟性: キャッシュをきめ細かく制御できるため、開発者はデータの精度を犠牲にすることなくパフォーマンスを最適化できます。
リンク: 先を考えるコンポーネント
リンク コンポーネントは、クライアント側リソースをプリロードするためのインテリジェントでパフォーマンス指向のソリューションであり、ユーザーにとってよりスムーズで高速なナビゲーション エクスペリエンスを保証します。そのプリフェッチ機能は、ユーザーがリンク上にマウスを移動するとトリガーされ、アイドル状態の時間を利用して宛先データを事前にリクエストします。
仕組み
- 条件付きプリフェッチ: プリフェッチ属性 (デフォルトで有効) は、プリロードを実行するかどうかを制御します。
- インテリジェント キャッシュ: セットは、すでにプリフェッチされたリンクを保存し、冗長なフェッチ呼び出しを回避するために使用されます。
- Mouse Enter イベント: ユーザーがリンク上にマウスを移動すると、handleMouseEnter 関数はプリロードが必要かどうかを確認し、必要な場合は宛先へのフェッチ リクエストを開始します。
- エラー耐性: リクエスト中のあらゆる障害が抑制され、コンポーネントの動作が一時的なネットワークの問題による影響を受けないようになります。
使用例
// 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: エッジへのポータル
app.useContext フックは React Edge の基礎であり、ワーカーのコンテキスト全体へのシームレスなアクセスを許可します。ルーティング、状態、RPC 呼び出しなどを管理するための強力なインターフェイスを提供します。
例: ダッシュボードでの app.useContext の使用
// 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 の主な機能
- ルート管理: 一致したルート、そのパラメータ、クエリ文字列に簡単にアクセスできます。
- RPC 統合: 追加の構成を行わずに、クライアントから直接、型指定された安全な RPC 呼び出しを実行します。
- 共有ストア アクセス: 可視性 (パブリック/プライベート) を完全に制御して、ワーカーとクライアントの共有状態で値を取得または設定します。
- ユニバーサル URL アクセス: 動的レンダリングとインタラクションのための現在のリクエストの完全な URL に簡単にアクセスします。
なぜ強力なのか
app.useContext フックは、ワーカーとクライアントの間のギャップを橋渡しします。これにより、ボイラープレートを使用せずに、共有状態、安全なデータ取得、コンテキスト レンダリングに依存する機能を構築できます。これにより、複雑なアプリケーションが簡素化され、保守が容易になり、開発が迅速化されます。
app.useUrlState: URLと同期された状態
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' }) ); };
パラメータ
-
初期状態
- 状態のデフォルトの構造と値を定義するオブジェクト。
-
オプション:
- debounce: 状態が変化した後に URL を更新する速度を制御します。過剰なアップデートを防ぐのに役立ちます。
- kebabCase: URL にシリアル化するときに状態キーを kebab-case に変換します (例: filter.locations → filter-locations)。
- omitKeys: URL から除外するキーを指定します。たとえば、機密データや大きなオブジェクトは省略できます。
- omitValues: 存在する場合、関連するキーを URL から除外する値。
- pickKeys: 指定されたキーのみが含まれるようにシリアル化された状態を制限します。
- プレフィックス: 名前空間のすべてのクエリ パラメーターにプレフィックスを追加します。
- url: 同期するベース URL。通常はアプリのコンテキストから派生します。
利点
- 同一の useState API: 既存のコンポーネントと簡単に統合できます。
- SEO フレンドリー: 状態に依存するビューが共有可能およびブックマーク可能な URL に確実に反映されます。
- 更新のデバウンス: スライダーやテキスト ボックスなど、急速に変化する入力に対する過剰なクエリ更新を防ぎます。
- クリーンな URL: kebabCase やomitKeys などのオプションにより、クエリ文字列が読みやすく関連性のある状態に保たれます。
- 状態のハイドレーション: コンポーネントのマウント時に URL から状態を自動的に初期化し、ディープリンクをシームレスにします。
- どこでも動作: サーバー側のレンダリングとクライアント側のナビゲーションをサポートし、アプリケーション全体で一貫した状態を保証します。
実用的なアプリケーション
- プロパティ リストのフィルター: 共有可能な検索のために、listingTypes などのユーザーが適用したフィルターを同期し、境界を URL にマップします。
- 動的ビュー: 地図のズーム、中心点、その他のビュー設定がページの更新やリンク後も維持されるようにします。
- ユーザー設定: ユーザーが選択した設定を URL に保存すると、簡単に共有したりブックマークしたりできます。
app.useStorageState: 永続的な状態
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' }) ); };
永続化オプション
- デバウンス: ストレージへの保存の頻度を制御します。
- ストレージ: localStorage と sessionStorage のどちらかを選択します。
- omitKeys/pickKeys: どのデータを保持するかをきめ細かく制御します。
パフォーマンス
- デバウンスによる最適化されたアップデート。
- 自動シリアル化/逆シリアル化。
- メモリ内キャッシュ。
一般的な使用例
- 検索履歴
- お気に入りリスト
- ユーザー設定
- フィルター状態
- 一時的なショッピングカート
- ドラフトフォーム
app.useDebounce: 周波数制御
リアクティブ値を簡単にデバウンスします:
// 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: 重複のない状態
型の安全性を維持しながら、一意の値を持つ配列を保持します。
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' }) ); };
主な特長
- 個別値の検出:
- 現在の値と以前の値を追跡します。
- 基準に基づいて、変更が意味のあるものであるかどうかを自動的に検出します。
- 詳しい比較:
- 複雑なオブジェクトに対して深いレベルでの値の等価性チェックを有効にします。
- カスタム比較:
- 「個別の」変更を構成する内容を定義するカスタム関数をサポートします。
- デバウンス:
- 変更が頻繁に発生する場合、不必要な更新を減らします。
利点
- 同一の useState API: 既存のコンポーネントと簡単に統合できます。
- 最適化されたパフォーマンス: 値が大幅に変更されていない場合、不必要な再フェッチや再計算を回避します。
- UX の強化: 過剰な UI 更新を防ぎ、よりスムーズなインタラクションを実現します。
- ロジックの簡略化: 状態管理における同等性または重複の手動チェックを排除します。
React Edge のフックは調和して動作するように設計されており、流動的で厳密に型指定された開発エクスペリエンスを提供します。これらを組み合わせることで、はるかに少ないコードで複雑でリアクティブなインターフェイスを作成できます。
React Edge CLI: 指先ひとつでパワーを発揮
React Edge の CLI は、重要なツールを 1 つの直感的なインターフェイスに集めて開発者の作業を簡素化するように設計されました。初心者でも経験豊富な開発者でも、CLI を使用すると、プロジェクトを効率的かつ簡単に構成、開発、テスト、デプロイできます。
主な特長
モジュール式で柔軟なコマンド:
- build: 環境とモード (開発または運用) を指定するオプションを使用して、アプリとワーカーの両方をビルドします。
- dev: ローカルまたはリモートの開発サーバーを起動し、アプリまたはワーカーでの個別の作業を可能にします。
- デプロイ: Cloudflare Workers と Cloudflare R2 の総合力を活用して高速かつ効率的なデプロイを可能にし、エッジ インフラストラクチャのパフォーマンスとスケーラビリティを確保します。
- logs: ワーカーのログをターミナルで直接監視します。
- lint: 自動修正のサポートにより、Prettier および ESLint の実行を自動化します。
- test: Vitest を使用してオプションのカバレッジでテストを実行します。
- type-check: プロジェクト全体で TypeScript の入力を検証します。
実際の使用例
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 サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











フロントエンドのサーマルペーパーチケット印刷のためのよくある質問とソリューションフロントエンド開発におけるチケット印刷は、一般的な要件です。しかし、多くの開発者が実装しています...

JavaScriptは現代のWeb開発の基礎であり、その主な機能には、イベント駆動型のプログラミング、動的コンテンツ生成、非同期プログラミングが含まれます。 1)イベント駆動型プログラミングにより、Webページはユーザー操作に応じて動的に変更できます。 2)動的コンテンツ生成により、条件に応じてページコンテンツを調整できます。 3)非同期プログラミングにより、ユーザーインターフェイスがブロックされないようにします。 JavaScriptは、Webインタラクション、シングルページアプリケーション、サーバー側の開発で広く使用されており、ユーザーエクスペリエンスとクロスプラットフォーム開発の柔軟性を大幅に改善しています。

スキルや業界のニーズに応じて、PythonおよびJavaScript開発者には絶対的な給与はありません。 1. Pythonは、データサイエンスと機械学習でさらに支払われる場合があります。 2。JavaScriptは、フロントエンドとフルスタックの開発に大きな需要があり、その給与もかなりです。 3。影響要因には、経験、地理的位置、会社の規模、特定のスキルが含まれます。

JavaScriptを学ぶことは難しくありませんが、挑戦的です。 1)変数、データ型、関数などの基本概念を理解します。2)非同期プログラミングをマスターし、イベントループを通じて実装します。 3)DOM操作を使用し、非同期リクエストを処理することを約束します。 4)一般的な間違いを避け、デバッグテクニックを使用します。 5)パフォーマンスを最適化し、ベストプラクティスに従ってください。

同じIDを持つ配列要素をJavaScriptの1つのオブジェクトにマージする方法は?データを処理するとき、私たちはしばしば同じIDを持つ必要性に遭遇します...

この記事の視差スクロールと要素のアニメーション効果の実現に関する議論では、Shiseidoの公式ウェブサイト(https://www.shisido.co.co.jp/sb/wonderland/)と同様の達成方法について説明します。

JavaScriptの最新トレンドには、TypeScriptの台頭、最新のフレームワークとライブラリの人気、WebAssemblyの適用が含まれます。将来の見通しは、より強力なタイプシステム、サーバー側のJavaScriptの開発、人工知能と機械学習の拡大、およびIoTおよびEDGEコンピューティングの可能性をカバーしています。

Console.log出力の違いの根本原因に関する詳細な議論。この記事では、Console.log関数の出力結果の違いをコードの一部で分析し、その背後にある理由を説明します。 �...
