모든 것은 Vercel 송장으로 시작되었습니다. 아니요, 실제로는 훨씬 일찍 시작되었습니다. 작은 좌절감이 쌓였습니다. DDoS 보호, 더 자세한 로그, 괜찮은 방화벽, 대기열 구축 등과 같은 기본 기능에 대한 비용을 지불해야 하는 필요성. 점점 더 비용이 많이 드는 공급업체 종속에 갇힌 느낌.
"그리고 가장 나쁜 점은 페이지 라우터를 사용하는 애플리케이션의 서버에서 귀중한 SEO 헤더의 렌더링이 중단되었다는 것입니다. 모든 개발자에게 진정한 골칫거리입니다! ?"
그러나 모든 것을 다시 생각하게 만든 것은 Next.js가 향하는 방향이었습니다. 클라이언트 사용, 서버 사용 지시문이 도입되면서 이론적으로는 개발이 단순화되지만 실제로는 관리하기가 더 복잡해졌습니다. 마치 PHP 시절로 돌아가서 파일이 실행되어야 하는 위치를 지시하는 지시어로 파일을 표시하는 것과 같았습니다.
여기서 끝나지 않습니다. App Router - 흥미로운 아이디어지만 Next.js 내에서 거의 완전히 새로운 프레임워크를 생성하는 방식으로 구현되었습니다. 갑자기 동일한 작업을 수행하는 완전히 다른 두 가지 방법('기존'과 '새')이 나타났습니다. 미묘하게 다른 동작과 숨겨진 함정이 있었습니다.
그때 문득 생각났습니다. 에지에서 실행되는 작업자, 스토리지용 R2, 분산 데이터용 KV와 함께 Cloudflare의 놀라운 인프라를 활용해 보는 것은 어떨까요? 물론 놀라운 DDoS 보호, 글로벌 CDN, 방화벽도 함께 제공됩니다. , 페이지 규칙 및 라우팅 등 Cloudflare가 제공하는 모든 기능을 제공합니다.
가장 좋은 점은 사용한 만큼만 비용을 지불하는 공정한 가격 모델입니다.
리액트 엣지는 이렇게 탄생했습니다. 바퀴를 재발명하는 것을 목표로 하지 않고 진정으로 단순하고 현대적인 개발 경험을 제공하는 프레임워크입니다.
React Edge 개발을 시작했을 때 저는 이해하기 쉬운 프레임워크를 만드는 것이라는 명확한 목표를 가지고 있었습니다. 더 이상 혼란스러운 지시문으로 씨름할 필요가 없고, 기본 기능에 대해 더 이상 엄청난 비용을 지불할 필요가 없으며, 가장 중요한 것은 더 이상 클라이언트/서버 분리로 인한 인위적인 복잡성을 처리할 필요가 없다는 것입니다. 저는 단순함을 희생하지 않으면서 성능을 제공하는 프레임워크인 속도를 원했습니다. React의 API에 대한 지식과 JavaScript 및 Golang 개발자로서 수년간의 경험을 활용하여 스트림 및 멀티플렉싱을 처리하여 렌더링 및 데이터 관리를 최적화하는 방법을 정확히 알고 있었습니다.
강력한 인프라와 글로벌 입지를 갖춘 Cloudflare Workers는 이러한 가능성을 탐색할 수 있는 완벽한 환경을 제공했습니다. 저는 진정한 하이브리드를 원했고 이러한 도구와 경험의 조합은 현대적이고 효율적인 솔루션으로 실제 문제를 해결하는 프레임워크인 React Edge에 생명을 불어넣었습니다.
React Edge는 React 개발에 혁신적인 접근 방식을 도입합니다. 완전한 유형 안전성과 제로 구성을 통해 서버에서 클래스를 작성하고 클라이언트에서 직접 호출한다고 상상해 보십시오. 태그나 접두사에 의한 무효화를 허용하여 "작동하는" 분산 캐싱 시스템을 상상해 보십시오. 서버와 클라이언트 간에 상태를 원활하고 안전하게 공유한다고 상상해 보세요. 단순화된 인증, 효율적인 국제화 시스템, CLI 등을 추가하세요.
RPC 통신은 거의 마술처럼 느껴집니다. 클래스에서 메소드를 작성하고 클라이언트에서 마치 로컬인 것처럼 호출합니다. 지능형 다중화 시스템은 여러 구성 요소가 동일한 호출을 하더라도 하나의 요청만 서버로 전송되도록 보장합니다. 임시 캐싱은 불필요한 반복 요청을 방지하며 서버와 클라이언트 모두에서 원활하게 작동합니다.
가장 강력한 기능 중 하나는 데이터 가져오기 환경을 통합하는 app.useFetch 후크입니다. 서버에서는 SSR 중에 데이터를 미리 로드합니다. 클라이언트에서는 해당 데이터를 자동으로 수화하고 주문형 업데이트를 지원합니다. 자동 폴링 및 종속성 기반 반응성을 지원하므로 동적 인터페이스 생성이 그 어느 때보다 쉬워졌습니다.
하지만 그게 다가 아닙니다. 프레임워크는 강력한 라우팅 시스템(환상적인 Hono에서 영감을 얻었음), Cloudflare R2를 사용한 통합 자산 관리, HttpError 클래스를 통해 오류를 처리하는 우아한 방법을 제공합니다. 미들웨어는 공유 저장소를 통해 클라이언트에 쉽게 데이터를 보낼 수 있으며 보안을 위해 모든 것이 자동으로 난독화됩니다.
가장 인상적인 부분은? 프레임워크의 코드는 거의 모두 하이브리드입니다. "클라이언트" 버전과 "서버" 버전이 없습니다. 동일한 코드가 두 환경 모두에서 작동하며 상황에 자동으로 적응합니다. 클라이언트는 필요한 것만 수신하여 최종 번들을 극도로 최적화합니다.
그리고 금상첨화: 이 모든 것이 Cloudflare Workers 엣지 인프라에서 실행되어 합리적인 비용으로 뛰어난 성능을 제공합니다. 예상치 못한 청구서도 없고 값비싼 기업 요금제 뒤에 숨어 있는 기본 기능도 없습니다. 단지 진정으로 중요한 것, 즉 놀라운 응용 프로그램 구축에 집중할 수 있는 견고한 프레임워크일 뿐입니다. 또한 React Edge는 대기열, 내구성 있는 개체, KV 저장소 등을 포함한 Cloudflare의 생태계를 활용하여 애플리케이션을 위한 강력하고 확장 가능한 기반을 제공합니다.
Vite는 개발 환경, 테스트, 빌드 프로세스의 기반으로 사용되었습니다. 인상적인 속도와 현대적인 아키텍처를 갖춘 Vite는 민첩하고 효율적인 작업 흐름을 지원합니다. 개발을 가속화할 뿐만 아니라 빌드 프로세스를 최적화하여 빠르고 정확한 컴파일을 보장합니다. 의심할 여지없이 Vite는 React Edge를 위한 완벽한 선택이었습니다.
클라이언트/서버 장벽을 걱정하지 않고 React 애플리케이션을 개발하는 것이 어떤 것인지 궁금한 적이 있습니까? 클라이언트 사용이나 서버 사용과 같은 수십 개의 지시문을 외우지 않고 계십니까? 더 좋은 점은 전체 입력 및 구성 없이 서버 기능을 마치 로컬인 것처럼 호출할 수 있다면 어떨까요?
가장 중요한 점은 이 모든 것이 클라이언트 사용 또는 서버 사용으로 표시하지 않고도 서버와 클라이언트 모두에서 원활하게 작동한다는 것입니다. 프레임워크는 컨텍스트에 따라 수행할 작업을 자동으로 알고 있습니다. 뛰어들어 볼까요?
이렇게 할 수 있다고 상상해 보세요.
// 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의 가장 강력한 기능 중 하나는 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는 번역을 자동으로 감지하고 로드합니다. 쿠키에 사용자 기본 설정을 쉽게 저장할 수도 있습니다. 하지만 당연히 이런 걸 기대하시겠죠?
// translations/fr.ts export default { 'Good Morning, {name}!': 'Bonjour, {name}!', };
인증은 항상 웹 애플리케이션의 문제점이었습니다. JWT 토큰, 보안 쿠키 및 재검증을 관리하려면 많은 상용구 코드가 필요한 경우가 많습니다. 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의 가장 강력한 기능 중 하나는 작업자와 클라이언트 간에 상태를 안전하게 공유하는 기능입니다. 작동 방식은 다음과 같습니다.
// 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' }) ); };
Link 구성 요소는 클라이언트 측 리소스를 미리 로드하기 위한 지능적이고 성능 지향적인 솔루션으로, 사용자에게 더 부드럽고 빠른 탐색 환경을 보장합니다. 사용자가 링크 위로 마우스를 가져가면 미리 가져오기 기능이 실행되어 유휴 순간을 활용하여 대상 데이터를 미리 요청합니다.
// 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 페이지에 대한 데이터를 미리 로드하기 시작하여 거의 즉각적인 전환을 보장합니다. 천재적인 아이디어죠? 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는 필수 도구를 직관적인 단일 인터페이스에 모아 개발자의 삶을 단순화하도록 설계되었습니다. 초보자이든 숙련된 개발자이든 CLI를 사용하면 프로젝트를 효율적이고 쉽게 구성, 개발, 테스트 및 배포할 수 있습니다.
React Edge를 사용한 첫 번째 프로덕션 애플리케이션이 이미 출시되었다는 소식을 공유하게 되어 자랑스럽습니다! 브라질의 부동산 회사인 Lopes Imóveis는 이미 프레임워크의 성능과 유연성의 이점을 누리고 있습니다.
웹사이트에서는 검색을 최적화하고 보다 원활한 사용자 경험을 제공하기 위해 속성이 캐시에 로드됩니다. 매우 동적인 사이트이기 때문에 경로 캐싱은 재검증 중 오래된 전략과 결합하여 단 10초의 TTL을 사용합니다. 이를 통해 사이트는 백그라운드 재검증 중에도 탁월한 성능으로 업데이트된 데이터를 제공할 수 있습니다.
또한 유사한 속성에 대한 권장 사항은 백그라운드에서 효율적이고 비동기적으로 계산된 다음 통합 RPC 캐싱 시스템을 사용하여 Cloudflare의 캐시에 직접 저장됩니다. 이 접근 방식은 후속 요청에 대한 응답 시간을 줄이고 권장 사항 쿼리를 거의 즉각적으로 수행합니다. 모든 이미지는 Cloudflare R2에 저장되어 외부 공급자에 의존하지 않고도 확장 가능하고 분산된 저장소를 제공합니다.
곧 Easy Auth를 위한 대규모 자동 마케팅 프로젝트를 시작하여 이 기술의 잠재력을 더욱 보여줄 예정입니다.
그래서 독자 여러분, 우리는 React Edge 세계를 통한 이 여정의 끝에 도달했습니다! Basic 및 Bearer와 같은 더 간단한 인증 옵션과 개발자의 하루를 훨씬 더 행복하게 만들어주는 기타 트릭과 같이 탐색해야 할 놀라운 기능이 여전히 많다는 것을 알고 있습니다. 하지만 잠깐만요! 앞으로 이러한 각 기능에 대해 자세히 알아보기 위한 더 자세한 기사를 제공할 계획입니다.
스포일러 경고: 곧 React Edge가 오픈 소스로 공개되고 적절하게 문서화될 예정입니다! 개발, 작업, 글쓰기 및 약간의 사회 생활의 균형을 맞추는 것은 쉽지 않지만, 특히 Cloudflare의 인프라가 제공하는 터무니없는 속도와 함께 이 경이로운 작업을 보는 즐거움은 나를 계속 움직이게 하는 원동력입니다. 최고의 순간은 아직 오지 않았으니 계속 지켜봐 주시기 바랍니다! ?
그동안 지금 바로 탐색하고 테스트하고 싶다면 NPM에서 이미 패키지를 사용할 수 있습니다: NPM의 React Edge..
제 이메일 주소는 feliperohdee@gmail.com이며, 항상 피드백에 열려 있습니다. 이것은 이 여정의 시작일 뿐입니다. 제안과 건설적인 비판을 환영합니다. 읽으신 내용이 마음에 드셨다면 친구 및 동료와 공유하시고, 추가 업데이트를 계속 지켜봐 주시기 바랍니다. 여기까지 읽어주셔서 감사하고, 다음에 또 만나요! ???
위 내용은 Next.js에서 Cloudflare Workers와 함께 React Edge까지: 해방 이야기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!