>웹 프론트엔드 >JS 튜토리얼 >Next.js에서 Cloudflare Workers와 함께 React Edge까지: 해방 이야기

Next.js에서 Cloudflare Workers와 함께 React Edge까지: 해방 이야기

DDD
DDD원래의
2024-11-20 13:58:12924검색
  • 마지막 지푸라기
  • Cloudflare의 대안은?
  • React Edge: 개발자의 모든(또는 거의) 고통에서 파생된 React Framework
    • 형식 RPC의 마법
    • 사용의 힘Fetch: 마법이 일어나는 곳
  • 쓸모없는 Fetch: 완전한 무기고
    • RPC: 클라이언트-서버 통신의 기술
    • 합리적인 i18n 시스템
    • “작동”하는 JWT 인증
    • 공유스토어
    • 우아한 라우팅
    • 에지 캐시를 사용한 분산 캐시
  • 링크: 미래 지향적인 구성 요소
  • app.useContext: 엣지로 가는 관문
  • app.useUrlState: URL과 동기화된 상태
  • app.useStorageState: 지속 상태
  • app.useDebounce: 주파수 제어
  • app.useDistinct: 중복이 없는 상태
  • React Edge CLI: 손끝에서 느껴지는 힘
  • 결론

마지막 방울

모든 것은 Vercel의 송장으로 시작되었습니다. 아니요, 실제로는 훨씬 더 일찍 시작되었습니다. 작은 좌절감이 쌓였습니다. DDoS 보호, 더 자세한 로그, 적절한 방화벽, 대기열 구축 등과 같은 기본 기능에 대한 비용을 지불해야 합니다. 점점 더 비용이 많이 드는 공급업체 종속에 갇힌 느낌입니다.

"그리고 가장 나쁜 점은 페이지 라우터를 사용하는 애플리케이션의 서버에서 우리의 소중한 SEO 헤더가 단순히 렌더링되는 것을 중단했다는 점입니다. 모든 개발자에게 정말 골치 아픈 문제입니다! ?"

하지만 정말 모든 것을 다시 생각하게 만든 것은 Next.js가 가고 있는 방향이었습니다. 사용 클라이언트, 사용 서버 지시문을 도입하면 이론적으로는 개발이 단순화되지만 실제로는 관리하기가 더 복잡해집니다. 마치 PHP 시대로 돌아가서 파일이 어디서 실행되어야 하는지 지시어를 표시하는 것과 같았습니다.

여기서 끝나지 않습니다. App Router는 흥미로운 아이디어지만 Next.js 내에서 실질적으로 새로운 프레임워크를 생성하는 방식으로 구현되었습니다. 갑자기 우리는 같은 일을 하는 완전히 다른 두 가지 방법을 갖게 되었습니다. '기존'과 '새' - 미묘하게 다른 동작과 숨겨진 함정이 있습니다.

Cloudflare의 대안?

그때 깨달았습니다. 에지에서 실행되는 작업자, 스토리지용 R2, 분산 데이터용 KV 등 Cloudflare의 놀라운 인프라를 활용해 보는 것은 어떨까요? 물론 놀라운 DDoS 보호, 글로벌 CDN, 방화벽, 규칙도 함께 제공됩니다. 페이지, 경로 및 Cloudflare가 제공하는 모든 기능을 제공합니다.

그리고 최고: 사용한 만큼만 비용을 지불하는 공정한 가격 모델입니다.

리액트 엣지는 이렇게 탄생했습니다. 바퀴를 재발명하려는 것이 아니라 정말 간단하고 현대적인 개발 경험을 제공하는 프레임워크입니다.

React Edge: 개발자의 모든(또는 거의) 고통에서 파생된 React Framework

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 개발 재고

클라이언트/서버 장벽을 걱정하지 않고 React 애플리케이션을 개발하는 것이 어떤 것인지 궁금한 적이 있습니까? 클라이언트 사용이나 서버 사용과 같은 수십 개의 지시문을 외울 필요 없이? 그리고 더 좋은 점은 전체 입력 및 구성 없이 서버 기능을 마치 로컬인 것처럼 호출할 수 있다면 어떨까요?

React Edge를 사용하면 다음을 수행할 필요가 없습니다.

  • 별도의 API 경로 생성
  • 로딩/오류 상태를 수동으로 관리
  • 수동으로 디바운스 구현
  • 직렬화/역직렬화에 대한 고민
  • CORS 처리
  • 클라이언트/서버 간 입력 관리
  • 인증 규칙을 수동으로 처리
  • 국제화 방법 관리

가장 좋은 점은 이 모든 것이 클라이언트 사용 또는 서버 사용으로 표시할 필요 없이 서버와 클라이언트 모두에서 작동한다는 것입니다. 프레임워크는 컨텍스트에 따라 수행할 작업을 알고 있습니다. 갈까요?

형식화된 RPC의 마법

이렇게 할 수 있다고 상상해 보세요.

// 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' })
  );
};

이것을 Next.js/Vercel과 비교해보세요:

// 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
}

사용의 힘Fetch: 마법이 일어나는 곳

데이터 가져오기에 대한 재고

React의 데이터 가져오기에 대해 알고 있는 모든 것을 잊어버리세요. React Edge의 app.useFetch는 완전히 새롭고 강력한 접근 방식을 제공합니다. 다음과 같은 후크를 상상해 보세요.

  • 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' })
  );
};

멀티플렉싱의 마법

위의 예에는 지능형 멀티플렉싱이라는 강력한 기능이 숨겨져 있습니다. 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
}

SSR 퍼펙트 하이드레이션

가장 인상적인 부분 중 하나는 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>
 );
};

쓸모없는 Fetch: 완전한 무기고

RPC: 클라이언트-서버 통신 기술

보안 및 캡슐화

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 계층

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를 계층 구조로 구성하면 다음과 같은 여러 이점이 있습니다.

  • 논리적 구성: 관련 기능을 직관적으로 그룹화
  • 자연 네임스페이스: 명확한 경로로 이름 충돌 방지(users.preferences.getTheme)
  • 캡슐화: 각 수준에서 도우미 메소드를 비공개로 유지
  • 유지관리성: 각 하위 클래스는 독립적으로 유지관리 및 테스트 가능
  • 완전한 타이핑: TypeScript는 전체 계층 구조를 이해합니다

React Edge의 RPC 시스템은 클라이언트-서버 통신을 너무나 자연스럽게 만들어 원격 호출을 하고 있다는 사실조차 잊어버릴 정도입니다. 또한 API를 계층 구조로 구성하는 기능을 사용하면 코드를 체계적이고 안전하게 유지하면서 복잡한 구조를 만들 수 있습니다.

합리적인 i18n 시스템

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 인증

인증은 항상 웹 애플리케이션의 문제점이었습니다. 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>
 );
};

이것이 왜 혁명적인가?

  1. 제로 상용구

    • 수동 쿠키 관리 없음
    • 요격기가 필요하지 않습니다
    • 업그레이드 토큰 매뉴얼 없음
  2. 기본 보안

    • 토큰은 자동으로 암호화됩니다
    • 쿠키는 안전하며 httpOnly
    • 자동 재검증
  3. 입력 완료

    • JWT 페이로드가 입력되었습니다
    • Zod 통합을 통한 검증
    • 입력된 인증 오류
  4. 완벽한 통합

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>
  );
};

작동 방식

  • 공개 데이터: 공개로 표시된 데이터는 클라이언트와 안전하게 공유되므로 구성 요소에 쉽게 액세스할 수 있습니다.
  • 개인 데이터: 민감한 데이터는 작업자 환경 내에 유지되며 고객에게 절대 노출되지 않습니다.
  • 미들웨어 통합: 미들웨어는 공개 및 비공개 데이터로 저장소를 채울 수 있어 서버 로직과 클라이언트 측 렌더링 간의 지속적인 정보 흐름을 보장합니다.

이익

  1. 보안: 공개 데이터 범위와 비공개 데이터 범위를 분리하여 기밀 정보를 안전하게 보호합니다.
  2. 편의성: 매장 데이터에 대한 투명한 액세스로 작업자와 클라이언트 간의 상태 관리가 단순화됩니다.
  3. 유연성: 스토어는 미들웨어와 쉽게 통합되어 요청 처리에 따라 동적 상태 업데이트가 가능합니다.

우아한 라우팅

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' })
  );
};

주요 특징

  • 그룹화된 경로: 공유 경로 및 미들웨어 아래 관련 경로를 논리적으로 그룹화합니다. 유연한 핸들러: 페이지를 반환하거나 API 응답을 전달하는 핸들러를 정의합니다.
  • 경로별 헤더: 개별 경로에 대한 HTTP 헤더를 사용자 정의합니다.
  • 통합 캐시: ttl 및 태그로 캐싱 전략을 단순화합니다.

이익

  1. 일관성: 관련 경로를 함께 그룹화하면 일관된 미들웨어 애플리케이션 및 코드 구성이 보장됩니다.
  2. 확장성: 이 시스템은 대규모 애플리케이션을 위한 중첩 및 모듈식 라우팅을 지원합니다.
  3. 성능: 기본 캐시 지원으로 수동 구성 없이 최적의 응답 시간을 보장합니다.

엣지 캐시가 포함된 분산 캐시

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
}

주요 특징

  • 태그 기반 무효화: 캐시 항목을 태그를 사용하여 그룹화할 수 있으므로 데이터 변경 시 쉽고 선택적으로 무효화할 수 있습니다.
  • 접두사 일치: 공통 접두사를 사용하여 여러 캐시 항목을 무효화합니다. 이는 검색 쿼리 또는 계층적 데이터와 같은 시나리오에 이상적입니다.
  • TTL(Time to Live): 캐시 항목의 만료 시간을 설정하여 고성능을 유지하면서 최신 데이터를 보장합니다.

이익

  1. 성능 향상: 자주 액세스하는 데이터에 대해 캐시된 응답을 제공하여 API 부하를 줄입니다.
  2. 확장성: 분산 캐싱 시스템으로 대용량 데이터세트와 높은 트래픽을 효율적으로 관리합니다.
  3. 유연성: 캐싱을 세밀하게 제어하여 개발자가 데이터 정확성을 희생하지 않고도 성능을 최적화할 수 있습니다.

링크: 미래 지향적 사고 구성 요소

Link 구성 요소는 클라이언트 측에서 리소스를 미리 로드하기 위한 지능적이고 성능이 뛰어난 솔루션으로, 사용자에게 보다 유연하고 빠른 탐색을 보장합니다. 링크 위에 커서를 올리면 프리페치 기능이 활성화되어 사용자가 활동하지 않는 순간을 활용하여 대상 데이터를 미리 요청합니다.

어떻게 작동하나요?

  1. 조건부 프리페치: 프리페치 속성(기본적으로 활성화됨)은 사전 로드 수행 여부를 제어합니다.

  2. 스마트 캐시: 세트는 이미 미리 로드된 링크를 저장하여 중복 호출을 방지하는 데 사용됩니다.

  3. Mouse Enter: 사용자가 링크 위에 커서를 올리면 handlerMouseEnter 함수가 사전 로드가 필요한지 확인하고 필요한 경우 대상으로 가져오기 요청을 시작합니다.

  4. 오류 안전: 요청의 모든 실패가 억제되어 구성 요소의 동작이 순간적인 네트워크 오류로 인해 영향을 받지 않습니다.

// 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: 엣지로 가는 게이트웨이

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의 주요 기능

  • 경로 관리: 해당 경로, 해당 매개변수 및 쿼리 문자열에 쉽게 액세스할 수 있습니다.
  • RPC 통합: 추가 구성 없이 클라이언트에서 직접 입력된 보안 RPC 호출을 수행합니다.
  • 공유 스토어 액세스: 가시성(공개/비공개)을 완전히 제어하여 작업자-클라이언트 공유 상태에서 값을 검색하거나 설정합니다.
  • 범용 URL 액세스: 동적 렌더링 및 상호 작용을 위해 현재 요청의 전체 URL에 쉽게 액세스할 수 있습니다.

강력한 이유

app.useContext 후크는 작업자와 클라이언트 사이의 격차를 해소합니다. 이를 통해 반복적인 코드 없이 공유 상태, 보안 데이터 가져오기 및 상황별 렌더링에 의존하는 기능을 구축할 수 있습니다. 이는 복잡한 애플리케이션을 단순화하여 유지 관리가 더 쉽고 개발 속도가 빨라집니다.

app.useUrlState: URL과 동기화된 상태

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>
 );
};

매개변수

  1. 초기상태

    • 해당 상태에 대한 기본 구조와 값을 정의하는 객체입니다.
  2. 옵션:

    • 디바운스: 상태 변경 후 URL이 얼마나 빨리 업데이트되는지 제어합니다.
    • kebabCase: URL로 직렬화할 때 상태 키를 kebab-case로 변환합니다.
    • omitKeys: URL에서 제외할 키를 지정합니다.
    • 생략값: 존재하는 경우 URL에서 관련 키를 제외하는 값입니다.
    • pickKeys: 특정 키만 포함하도록 직렬화된 상태를 제한합니다.
    • 접두사: 모든 쿼리 매개변수에 접두사를 추가합니다.
    • url: 동기화를 위한 기본 URL로, 일반적으로 애플리케이션 컨텍스트에서 파생됩니다.

이익

  • SEO 친화적: 상태 의존적 뷰가 공유 가능한 URL에 반영되도록 합니다.
  • Debounce를 통한 업데이트: 빠르게 변화하는 입력에 대한 과도한 쿼리 업데이트를 방지합니다.
  • URL 정리: kebabCase 및 omitKeys와 같은 옵션을 사용하면 쿼리 문자열을 읽기 쉽게 유지할 수 있습니다.
  • 상태 하이드레이션: 구성 요소를 조립할 때 URL의 상태를 자동으로 초기화합니다.
  • 모든 환경에서 작동: 서버측 렌더링 및 클라이언트측 브라우징을 지원합니다.

실제 응용

  • 목록 필터: 사용자가 적용한 필터를 URL과 동기화합니다.
  • 동적 보기: 지도 확대/축소, 중심점 또는 기타 설정이 유지됩니다.
  • 사용자 기본 설정: 공유를 위해 선택한 설정을 URL에 저장합니다.

app.useStorageState: 지속 상태

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' })
  );
};

지속성 옵션

  • 디바운스: 녹화 빈도 제어
  • 저장소: localStorage와 sessionStorage 중에서 선택
  • omitKeys/pickKeys: 지속형 데이터에 대한 세부 제어

성능

  • 디바운스에 최적화된 업데이트
  • 자동 직렬화/역직렬화
  • 메모리 캐시

일반적인 사용 사례

  • 검색기록
  • 즐겨찾기 목록
  • 사용자 기본 설정
  • 필터 상태
  • 임시 장바구니
  • 초안 양식

app.useDebounce: 주파수 제어

쉽게 반응 값 디바운스:

// 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: 중복이 없는 상태

입력을 통해 고유한 값의 배열 유지:

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' })
  );
};

주요 특징

  1. 고유값 감지:
    • 현재 및 이전 값 모니터링
    • 기준에 따라 변경 사항이 중요한지 자동으로 감지
  2. 심층 비교:
    • 복잡한 객체에 대한 심층적인 동일성 검사가 가능합니다
  3. 맞춤형 비교:
    • "뚜렷한" 변화를 구성하는 요소를 정의하는 사용자 정의 기능을 지원합니다
  4. 디바운스됨:
    • 변경이 너무 자주 발생할 때 불필요한 업데이트를 줄입니다

이익

  • useState와 동일한 API: 기존 구성 요소에 쉽게 통합할 수 있습니다.
  • 성능 최적화: 값이 크게 변경되지 않은 경우 불필요한 다시 가져오기 또는 재계산을 방지합니다. 향상된 UX: 지나치게 반응적인 UI 업데이트를 방지하여 보다 원활한 상호 작용을 제공합니다.
  • 단순화된 논리: 상태 관리에서 동일성 또는 중복에 대한 수동 검사를 제거합니다.

React Edge Hooks는 조화롭게 작동하도록 설계되어 유연하고 형식화된 개발 경험을 제공합니다. 이들을 결합하면 훨씬 적은 코드로 복잡하고 반응적인 인터페이스를 만들 수 있습니다.

React Edge CLI: 손끝에서 느껴지는 힘

React Edge CLI는 필수 도구를 직관적인 단일 인터페이스에 모아 개발자의 삶을 단순화하도록 설계되었습니다. 초보자든 전문가든 CLI를 사용하면 프로젝트를 효율적이고 번거로움 없이 구성, 개발, 테스트 및 배포할 수 있습니다.

주요 기능

모듈식 및 유연한 명령:

  • 빌드: 개발 또는 프로덕션 환경과 모드를 지정하는 옵션을 사용하여 앱과 작업자를 모두 빌드합니다.
  • dev: 로컬 또는 원격 개발 서버를 실행하여 앱이나 작업자에서 별도로 작업할 수 있습니다.
  • 배포: Cloudflare Workers와 Cloudflare R2의 결합된 기능을 사용하여 빠르고 효율적인 배포를 수행하여 엣지 인프라의 성능과 확장성을 보장합니다.
  • 로그: 작업자 로그를 터미널에서 직접 모니터링합니다.
  • lint: 자동 수정을 지원하여 Prettier 및 ESLint 실행을 자동화합니다.
  • 테스트: Vitest를 사용하여 선택적 적용 범위로 테스트를 실행합니다.
  • type-check: 프로젝트의 TypeScript 입력을 검증합니다.

프로덕션 사용 사례

React Edge를 사용한 첫 번째 프로덕션 애플리케이션이 이제 작동 중이라는 소식을 공유하게 되어 자랑스럽습니다! 브라질 부동산 회사인 Lopes Imóveis는 이미 프레임워크의 모든 성능과 유연성을 활용하고 있습니다.

부동산 웹사이트에서는 검색을 최적화하고 사용자에게 보다 유동적인 경험을 제공하기 위해 부동산이 캐시에 로드됩니다. 매우 동적인 웹사이트이기 때문에 경로 캐시는 재검증 중 오래된 전략과 결합하여 단 10초의 TTL을 사용합니다. 이를 통해 사이트는 백그라운드 재검증 중에도 뛰어난 성능으로 최신 데이터를 제공할 수 있습니다.

또한 유사한 속성에 대한 권장 사항은 백그라운드에서 때때로 효율적으로 계산되며 RPC에 통합된 캐시 시스템을 사용하여 Cloudflare의 캐시에 직접 저장됩니다. 이 접근 방식은 후속 요청에 대한 응답 시간을 줄이고 권장 사항 쿼리를 거의 즉각적으로 수행합니다. 또한 모든 이미지는 Cloudflare R2에 저장되므로 외부 공급자에 의존하지 않고도 확장 가능하고 분산된 저장소를 제공합니다.

De Next.js a React Edge com Cloudflare Workers: Uma História de Libertação
De Next.js a React Edge com Cloudflare Workers: Uma História de Libertação

또한 곧 Easy Auth를 위한 대규모 자동 마케팅 프로젝트를 시작하여 이 기술의 잠재력을 더욱 입증할 것입니다.

결론

그래서 독자 여러분, 우리는 React Edge 세계를 통해 이 모험의 끝에 이르렀습니다! Basic, Bearer와 같은 가장 간단한 인증, 개발자의 일상을 훨씬 더 행복하게 만들어주는 기타 소소한 비밀 등 탐구해야 할 놀라운 것들이 여전히 많다는 것을 알고 있습니다. 하지만 진정하세요! 아이디어는 앞으로 이러한 각 기능을 먼저 자세히 알아볼 수 있는 더 자세한 기사를 제공하는 것입니다.

스포일러: 곧 React Edge가 오픈 소스로 제공되고 적절하게 문서화될 예정입니다! 개발, 작업, 글쓰기 및 약간의 사회 생활의 균형을 맞추는 것은 쉽지 않지만, 특히 Cloudflare의 인프라가 제공하는 터무니없는 속도와 함께 이 경이로움이 실제로 작동하는 것을 보는 즐거움은 나를 감동시키는 원동력입니다. 최고의 순간은 아직 오지 않았으니 걱정하지 마세요! ?

그동안 지금 바로 탐색하고 테스트하고 싶다면 NPM에서 패키지를 사용할 수 있습니다: NPM의 React Edge..

제 이메일은 feliperohdee@gmail.com입니다. 저는 항상 피드백에 열려 있습니다. 이것은 이 여정의 시작일 뿐이며 제안과 건설적인 비판입니다. 읽은 내용이 마음에 들었다면 친구 및 동료와 공유하고 앞으로 나올 새로운 내용을 주목하세요. 여기까지 따라와주셔서 감사하고, 다음 시간까지! ???

위 내용은 Next.js에서 Cloudflare Workers와 함께 React Edge까지: 해방 이야기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.