모든 것은 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 애플리케이션을 개발하는 것이 어떤 것인지 궁금한 적이 있습니까? 클라이언트 사용이나 서버 사용과 같은 수십 개의 지시문을 외울 필요 없이? 그리고 더 좋은 점은 전체 입력 및 구성 없이 서버 기능을 마치 로컬인 것처럼 호출할 수 있다면 어떨까요?
가장 좋은 점은 이 모든 것이 클라이언트 사용 또는 서버 사용으로 표시할 필요 없이 서버와 클라이언트 모두에서 작동한다는 것입니다. 프레임워크는 컨텍스트에 따라 수행할 작업을 알고 있습니다. 갈까요?
이렇게 할 수 있다고 상상해 보세요.
// 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 }
가장 인상적인 부분 중 하나는 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의 가장 강력한 기능 중 하나는 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는 번역을 자동으로 감지하고 로드하며 사용자 기본 설정을 쿠키에 쉽게 저장할 수 있습니다. 하지만 이미 예상하셨죠?
// 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' }) ); };
인증은 항상 웹 애플리케이션의 문제점이었습니다. JWT 토큰 관리, 쿠키 보안, 유효성 재검사 등 이 모든 작업에는 일반적으로 많은 상용구 코드가 필요합니다. 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의 가장 강력한 기능 중 하나는 작업자와 클라이언트 간에 상태를 안전하게 공유하는 기능입니다. 이것이 어떻게 작동하는지 살펴보겠습니다:
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 }
Link 구성 요소는 클라이언트 측에서 리소스를 미리 로드하기 위한 지능적이고 성능이 뛰어난 솔루션으로, 사용자에게 보다 유연하고 빠른 탐색을 보장합니다. 링크 위에 커서를 올리면 프리페치 기능이 활성화되어 사용자가 활동하지 않는 순간을 활용하여 대상 데이터를 미리 요청합니다.
어떻게 작동하나요?
조건부 프리페치: 프리페치 속성(기본적으로 활성화됨)은 사전 로드 수행 여부를 제어합니다.
스마트 캐시: 세트는 이미 미리 로드된 링크를 저장하여 중복 호출을 방지하는 데 사용됩니다.
Mouse Enter: 사용자가 링크 위에 커서를 올리면 handlerMouseEnter 함수가 사전 로드가 필요한지 확인하고 필요한 경우 대상으로 가져오기 요청을 시작합니다.
오류 안전: 요청의 모든 실패가 억제되어 구성 요소의 동작이 순간적인 네트워크 오류로 인해 영향을 받지 않습니다.
// 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 페이지에서 데이터를 미리 로드하기 시작하여 거의 즉각적인 전환을 제공합니다. 기발한 아이디어죠? 그런데 나는 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 Hooks는 조화롭게 작동하도록 설계되어 유연하고 형식화된 개발 경험을 제공합니다. 이들을 결합하면 훨씬 적은 코드로 복잡하고 반응적인 인터페이스를 만들 수 있습니다.
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!