지난 분기, 우리는 오랫동안 시도하고 싶었던 완벽한 기회를 제공하는 클라이언트 프로젝트를 진행했습니다: 두 프레임워크에서 동일한 애플리케이션을 구축한 다음 모든 것을 비교하는 것입니다. todo 앱이 아닙니다. 카운터가 아닙니다. 인증, 실시간 데이터 업데이트, 복잡한 양식 및 수천 개의 데이터 포인트를 렌더링하는 차트 모듈이 있는 실제 프로덕션 대시보드입니다.

결과는 우리를 놀라게 했습니다. 한 프레임워크가 "승리"했기 때문이 아니라 -- 트레이드오프가 Twitter 담론이 암시하는 것보다 훨씬 미묘했기 때문입니다. 예상한 것들도 있습니다. 우리를 완전히 깜짝 놀라게 한 것들도 있습니다. 이것이 우리가 실제로 배운 것입니다.

목차

우리는 SolidJS와 React에서 동일한 앱을 만들었습니다: Signals vs Hooks

우리가 구축한 앱

이 애플리케이션은 전자상거래 클라이언트를 위한 실시간 분석 대시보드입니다. 포함된 내용은 다음과 같습니다:

  • JWT 토큰 및 새로고침 로직을 포함한 인증 흐름
  • 6개의 위젯 패널이 있는 대시보드, 각각 다른 API 엔드포인트에서 데이터를 가져옵니다
  • WebSocket 연결을 사용한 실시간 주문 피드
  • 5,000+ 데이터 포인트를 렌더링하는 대화형 차트 (차트 라이브러리 사용)
  • 종속 드롭다운 및 날짜 범위 선택기가 있는 복잡한 필터 양식
  • 중첩된 상태 관리가 있는 관리자 설정 패널
  • 사이드바 네비게이션이 있는 반응형 레이아웃

두 버전 모두 동일한 백엔드 API에 연결합니다. 둘 다 TypeScript를 사용합니다. 둘 다 빌드 도구로 Vite를 사용합니다. 우리는 타사 종속성을 최대한 유사하게 유지했습니다 -- 동일한 차트 라이브러리 (Chart.js), 동일한 HTTP 클라이언트 (ky), 동일한 WebSocket 래퍼.

React 버전은 hooks가 있는 React 19를 사용합니다. SolidJS 버전은 Solid 1.9를 사용합니다. 우리는 어떤 메타 프레임워크도 사용하지 않았습니다 (Next.js, SolidStart 없음). 라우팅 및 SSR이 비교를 혼탁하게 하지 않도록 프레임워크 차이를 분리하고 싶었습니다.

Signals vs Hooks: 정신 모델의 전환

이것이 큰 것입니다. 그리고 솔직히, 우리 팀은 약 일주일 동안 SolidJS에서 React 형태의 코드를 작성하지 않으려고 노력했습니다.

React Hooks가 작동하는 방식

당신은 이것을 알고 있지만, 비교를 위해 명시적으로 나타내는 것은 가치가 있습니다. React의 모델은: 상태가 변경되면 컴포넌트 함수가 다시 실행됩니다. 전체 함수. 모든 useState, 모든 useMemo, 모든 useCallback -- 이들은 모두 전체 함수가 다시 실행된다는 사실을 우회하기 위한 메커니즘입니다.

// React: 모든 상태 변경에 대해 이 전체 함수가 다시 실행됩니다
function OrderFeed() {
  const [orders, setOrders] = useState([]);
  const [filter, setFilter] = useState('all');
  
  // 우리가 메모이제이션하지 않는 한 매 렌더링마다 다시 계산됩니다
  const filteredOrders = useMemo(() => 
    orders.filter(o => filter === 'all' || o.status === filter),
    [orders, filter]
  );

  // 이 ref는 개념적으로 매 렌더링마다 다시 생성됩니다
  const handleNewOrder = useCallback((order) => {
    setOrders(prev => [order, ...prev]);
  }, []);

  useEffect(() => {
    const ws = connectWebSocket(handleNewOrder);
    return () => ws.close();
  }, [handleNewOrder]);

  return <OrderList orders={filteredOrders} />;
}

Solid Signals가 작동하는 방식

Solid의 모델은 근본적으로 다릅니다. 컴포넌트 함수는 한 번 실행됩니다. 그 후, signal이 변경될 때 signal을 읽는 특정 표현식만 다시 실행됩니다. 가상 DOM diff가 없습니다. 조정이 없습니다. 반응형 그래프는 정확히 어떤 DOM 노드가 어떤 signal에 의존하는지 추적합니다.

// Solid: 이 함수는 한 번만 실행됩니다
function OrderFeed() {
  const [orders, setOrders] = createSignal([]);
  const [filter, setFilter] = createSignal('all');
  
  // 이것은 자동으로 추적됩니다 -- 종속성 배열이 필요하지 않습니다
  const filteredOrders = createMemo(() => 
    orders().filter(o => filter() === 'all' || o.status === filter())
  );

  // useCallback이 필요하지 않습니다 -- 이 클로저는 안정적입니다
  const handleNewOrder = (order) => {
    setOrders(prev => [order, ...prev]);
  };

  // 빈 deps를 가진 useEffect 대신 onMount를 사용합니다
  onMount(() => {
    const ws = connectWebSocket(handleNewOrder);
    onCleanup(() => ws.close());
  });

  return <OrderList orders={filteredOrders()} />;
}

실제로 의미하는 것

Solid 버전에는 종속성 배열이 없습니다. useCallback이 없습니다. 파생된 상태에 대한 useMemo이 없습니다 (createMemo는 비용이 많이 드는 계산을 위해 존재합니다 -- 차이점은 정확성을 위해 필요하지 않고 성능을 위해 선택적입니다).

개발 중에 우리를 물린 것은 다음과 같습니다:

  1. Solid에서 props를 구조 분해하면 반응성이 죽습니다. 우리는 주니어 개발자가 Solid 컴포넌트에서 props를 구조 분해하게 했고 업데이트가 전파되지 않는 이유를 디버깅하는 데 45분을 썼습니다. React에서 전체 함수가 다시 실행되기 때문에 구조 분해는 괜찮습니다. Solid에서는 JSX 또는 추적된 범위 내에서 props.value에 접근해야 합니다.

  2. Solid에서 초기 반환은 까다롭습니다. React에서처럼 컴포넌트의 상단에서 if (!data()) return <Loading />을 할 수 없습니다. 컴포넌트 함수는 한 번만 실행되므로 해당 조건부는 다시 평가되지 않습니다. 대신 <Show when={data()}>을 사용해야 합니다.

  3. stale closure 버그가 없습니다. 이것이 큰 승리였습니다. React 버전에서 첫 번째 주에 WebSocket 핸들러가 이전 상태를 캡처하는 것과 관련된 3개의 stale closure 버그가 있었습니다. Solid 버전은 0개였습니다. signal은 항상 접근 시간에 읽히고 클로저에서 캡처되지 않기 때문입니다.

번들 크기 비교

우리는 동일한 Vite 구성, gzip 압축 및 코드 분할 전략으로 두 프로덕션 빌드를 측정했습니다.

메트릭 React 19 SolidJS 1.9 차이점
프레임워크 런타임 44.2 KB (gzip) 7.1 KB (gzip) -84%
총 초기 번들 127.8 KB 89.3 KB -30%
총 앱 (모든 청크) 312.4 KB 274.1 KB -12%
첫 번째 청크 (중요 경로) 68.4 KB 41.2 KB -40%
청크 수 14 14 동일

Solid의 런타임은 극적으로 작습니다. 이것은 뉴스가 아닙니다 -- Ryan Carniato는 이에 대해 광범위하게 이야기했습니다. 하지만 우리를 놀라게 한 것은 실제 애플리케이션 코드, 타사 라이브러리 및 자산을 추가하면 전체 앱 크기 차이가 크게 줄어들었다는 것입니다.

프레임워크 런타임은 초기 로드에 가장 중요합니다. 그리고 gzipped 7.1 KB로 Solid는 기본적으로 소음 속으로 사라집니다. React의 44 KB는 특히 모바일 연결에서 눈에 띕니다.

Tree Shaking

Solid는 React보다 tree-shake를 잘합니다. 사용되지 않은 Solid primitives는 완전히 제거됩니다. React에서는 모든 기능을 사용하든지 않든지 조정자 및 파이버 아키텍처가 배송됩니다. 우리는 두 가지 모두에서 최소 페이지를 구축하여 이것을 확인했습니다 -- Solid의 하한선은 더 낮습니다.

우리는 SolidJS와 React에서 동일한 앱을 만들었습니다: Signals vs Hooks - 아키텍처

렌더링 성능 벤치마크

우리는 Chrome DevTools 성능 프로필, Lighthouse 및 커스텀 타이밍 계측을 사용하여 구조화된 벤치마크를 실행했습니다. M2 MacBook Pro의 모든 테스트 (CPU 스로틀링은 4배 슬로우다운으로 설정하여 중간 범위 기기를 시뮬레이션함).

차트 렌더링 (5,000개 데이터 포인트)

메트릭 React 19 SolidJS 1.9
초기 렌더링 142ms 89ms
필터 변경에 따른 리렌더링 67ms 23ms
렌더링 중 메모리 18.4 MB 11.2 MB
생성된 DOM 노드 5,847 5,812

Solid는 가상 DOM 트리를 diff하지 않기 때문에 리렌더링에서 일관되게 더 빨랐습니다. 필터 signal이 변경되면 해당 signal을 읽는 표현식만 업데이트됩니다. React 19의 컴파일러 개선은 도움이 되었습니다 -- React 18의 동일한 테스트는 리렌더링에 95ms였습니다 -- 하지만 Solid는 여전히 명확한 이점이 있었습니다.

실시간 주문 피드 (초당 100개 업데이트)

이것이 정말 흥미로워지는 부분입니다. 우리는 초당 100개의 WebSocket 메시지를 주문 피드에 밀어 넣었습니다.

메트릭 React 19 SolidJS 1.9
프레임 드롭 (10초당) 12 2
평균 페인트 시간 8.3ms 3.1ms
최대 페인트 시간 34ms 11ms
CPU 사용량 (평균) 24% 9%

Solid는 이 벤치마크를 완전히 압도했습니다. 고주파 업데이트는 세밀한 반응성이 가장 큰 배당금을 지불하는 곳입니다. React는 업데이트를 배치하고 있었습니다 (좋은 일이지만), 각 배치에서 필요한 것보다 더 많은 DOM을 diff하고 있었습니다.

주목해야 할 점: React 19의 useTransition과 자동 배치는 이것을 React 18보다 훨씬 나았을 것입니다. 그리고 대부분의 실제 앱의 경우, 초당 100개의 업데이트를 밀어 넣지 않습니다. 초당 10개의 업데이트에서 두 프레임워크 모두 부드러웠습니다.

40개 필드가 있는 복잡한 양식

메트릭 React 19 SolidJS 1.9
키 입력 입력 지연 2-4ms <1ms
양식 제출 렌더링 28ms 12ms
키 입력당 컴포넌트 리렌더링 3-8 1

React에서 한 필드에 입력하면 신중하게 메모이제이션하지 않는 한 부모 컴포넌트가 리렌더링됩니다. Solid에서 필드에 입력하면 정말로 해당 필드의 DOM 텍스트 노드만 업데이트됩니다. 다른 것은 아무것도 다시 실행되지 않습니다.

개발자 경험 및 생태계

성능이 모든 것은 아닙니다. 당신은 실제로 그것을 구축하고, 디버깅하고, 고용하고, 유지해야 합니다.

생태계 크기 (2025년 초 기준)

요소 React SolidJS
npm 주간 다운로드 ~28M ~85K
GitHub 별 233K+ 33K+
Stack Overflow 질문 470K+ ~2K
UI 컴포넌트 라이브러리 50+ 성숙한 옵션 5-8 옵션
채용 공고 (LinkedIn US) ~45,000 ~200
메타 프레임워크 Next.js (성숙) SolidStart (베타)

이것이 방의 코끼리입니다. React의 생태계는 몇 배 더 큽니다. 우리가 Solid에 날짜 범위 선택기가 필요했을 때, 우리는 React에서 하나를 포팅하거나 우리 자신을 작성해야 했습니다. React에서는 15개 옵션에서 선택할 수 있었습니다.

우리는 Solid에서 접근 가능한 UI primitives를 위해 Kobalte를 사용했으며, 그것은 진정으로 좋습니다. 하지만 그것은 컴포넌트 커버리지 측면에서 Radix UI가 아닙니다.

디버깅

React DevTools는 성숙하고 잘 유지되며 모든 프론트엔드 개발자가 알고 있는 것입니다. Solid는 자체 DevTools 확장을 가지고 있으며, 그것은 괜찮습니다 -- signal 그래프를 검사할 수 있습니다. 이는 실제로 React의 컴포넌트 트리 보기보다 반응성 버그를 추적하는 데 더 유용합니다. 하지만 덜 광택되어 있습니다.

TypeScript 지원

둘 다 훌륭합니다. Solid의 TypeScript 지원은 컴파일러가 빌드 타임에 더 많이 처리하기 때문에 JSX에 대해 약간 더 낫습니다. Solid 코드베이스에서 더 적은 유형 체조를 했습니다.

실제로 중요한 프로덕션 트레이드오프

두 버전을 모두 구축하고 Solid 버전을 스테이징에 배포한 후 (클라이언트는 궁극적으로 프로덕션을 위해 React를 선택했습니다 -- 아래 자세히 알아보기), 실제로 중요한 트레이드오프는 다음과 같습니다:

고용

우리 클라이언트는 8명의 프론트엔드 개발자 팀을 가지고 있습니다. 모두 React를 압니다. 아무도 Solid를 사용해 본 적이 없습니다. 교육 시간 추정: 능숙함까지 2-3주, 숙달까지 2-3개월. 그것은 실제 비용입니다. 이것이 대부분의 프로덕션 결정에서 가장 큰 요소이며, 자주 고용해야 하는 팀의 경우 일반적으로 React 또는 Next.js와 같은 프레임워크를 추천하는 이유입니다.

타사 라이브러리 호환성

우리는 React 특정 통합이 있는 3개의 라이브러리에 대한 커스텀 래퍼를 작성해야 했습니다. 이것은 대략 20시간의 개발 시간을 추가했습니다. React 프로젝트에서는 그 시간이 존재하지 않습니다.

서버 측 렌더링

SolidStart는 유망하지만 우리의 프로젝트 중에 여전히 베타 단계였습니다. Next.js는 전투 테스트를 거쳤습니다. 모든 기능을 갖춘 프로덕션 등급 SSR이 필요한 프로젝트의 경우, 우리는 여전히 Next.js 개발 또는 콘텐츠 모델에 따라 Astro를 선택하고 있습니다.

중요한 성능

여기 솔직한 진실은: 이 특정 대시보드의 경우, 두 프레임워크 모두 프로덕션에 충분히 좋은 성능을 발휘했다는 것입니다. Solid 버전은 측정 가능하게 더 빨랐지만, React 버전은 절대 느리지 않았습니다. 사용자는 대부분의 상호 작용에서 차이를 알아차리지 못했을 것입니다.

예외는 높은 업데이트 빈도의 실시간 피드였습니다. 앱에 그와 같은 사용 사례가 있는 경우 Solid의 성능 이점은 단순한 벤치마크 자랑이 아니라 의미 있습니다.

SolidJS를 React보다 선택해야 할 때

이 실험 후, 다음은 우리의 솔직한 결정 프레임워크입니다:

SolidJS를 선택할 때:

  • 앱이 고주파 반응형 업데이트를 가집니다 (대시보드, 거래 플랫폼, 실시간 협업)
  • 번들 크기가 중요합니다 (임베드된 위젯, 마이크로 프론트엔드, 모바일 우선)
  • 팀이 작고 새로운 패러다임을 배우려고 합니다
  • 프레임워크와 싸우지 않고 세밀한 반응성을 원합니다
  • 새로운 것을 구축하고 있으며 대규모 컴포넌트 라이브러리 생태계가 필요하지 않습니다

React를 선택할 때:

  • 팀이 이미 React를 알고 있습니다 (이것만으로도 보통 결정적입니다)
  • 성숙한 메타 프레임워크가 필요합니다 (Next.js, Remix)
  • 타사 라이브러리 가용성이 중요합니다
  • 고용 중이며 대규모 React 개발자 인재 풀이 필요합니다
  • 앱의 성능 요구 사항이 일반적입니다 (극단적이지 않음)

우리의 headless CMS 개발 프로젝트의 경우, React는 여전히 지배합니다. CMS 통합 (Sanity, Contentful, Storyblok)은 첫 클래스 React SDK를 가지고 있기 때문입니다. Solid 지원은 존재하지만 종종 커뮤니티 유지됩니다.

코드 비교: 실제 패턴

프로젝트의 실제 패턴 몇 가지를 나란히 살펴보겠습니다.

로딩 상태로 데이터 가져오기

React:

function Dashboard() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchDashboardData()
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <Skeleton />;
  if (error) return <ErrorBanner error={error} />;
  return <DashboardGrid data={data} />;
}

SolidJS:

function Dashboard() {
  const [data] = createResource(fetchDashboardData);

  return (
    <Suspense fallback={<Skeleton />}>
      <ErrorBoundary fallback={(err) => <ErrorBanner error={err} />}>
        <DashboardGrid data={data()} />
      </ErrorBoundary>
    </Suspense>
  );
}

Solid의 createResourceSuspense와 함께 진정으로 우아합니다. React도 Suspense를 가지고 있습니다 (그리고 React 19에서 더 나아졌습니다), 하지만 Solid의 버전은 함께 일하기가 더 자연스러웠습니다. 더 적은 보일러플레이트, 관리할 상태 변수 감소.

조건부 렌더링

React:

{user ? (
  <Profile user={user} />
) : (
  <LoginPrompt />
)}

SolidJS:

<Show when={user()} fallback={<LoginPrompt />}>
  {(u) => <Profile user={u()} />}
</Show>

Solid의 <Show> 컴포넌트는 컴포넌트 함수가 한 번만 실행될 때 삼항식이 같은 방식으로 작동하지 않기 때문에 존재합니다. 그것에 익숙해지는 데 시간이 걸렸지만, 콜백 패턴은 좁혀진 non-null 참조를 제공하며, 이는 TypeScript에 좋습니다.

목록 렌더링

React:

{orders.map(order => (
  <OrderRow key={order.id} order={order} />
))}

SolidJS:

<For each={orders()}>
  {(order) => <OrderRow order={order} />}
</For>

Solid의 <For>는 참조에 의해 배열 항목을 추적하기 때문에 키가 필요하지 않습니다. 항목이 배열에서 이동하면 Solid는 실제 DOM 노드를 이동합니다. React는 언마운트하고 다시 마운트합니다. 이것이 Solid의 목록 성능이 그렇게 좋은 이유입니다.

FAQ

SolidJS는 2025년에 프로덕션 준비가 되었나요?

예. Solid 1.x는 2년 이상 안정적이었습니다. Cloudflare 및 Samsung과 같은 회사는 프로덕션에서 사용했습니다. 핵심 라이브러리는 성숙하고 잘 테스트되었습니다. 그 주변의 생태계 (SolidStart, 컴포넌트 라이브러리)는 React보다 작지만 빠르게 성장하고 있습니다. 문제는 Solid가 준비되었는지 여부가 아니라 팀과 프로젝트 요구 사항이 생태계 크기와 일치하는지 여부입니다.

React에서 SolidJS로 점진적으로 마이그레이션할 수 있나요?

쉽지 않습니다. JSX 구문이 유사하다는 점에도 불구하고 런타임 모델은 근본적으로 다릅니다. iframe 또는 웹 컴포넌트 경계 없이 Solid 컴포넌트를 React 앱 내에 임베드할 수 없습니다 (또는 그 반대). 마이그레이션은 기본적으로 다시 작성해야 합니다. 고려 중이라면 기존 React 코드를 변환하려고 하기 보다는 새로운 격리된 기능 또는 마이크로 프론트엔드로 시작하세요.

SolidJS는 서버 측 렌더링을 지원하나요?

예. SolidStart는 SSR, 스트리밍 및 서버 함수를 제공합니다. 이것은 기능적이고 빠르게 개선되고 있지만, 2025년 초 현재 Next.js 또는 Remix보다 성숙하지 않습니다. SSR 헤비한 프로젝트의 경우, 콘텐츠 요구 사항에 따라 Next.js 또는 Astro 평가를 계속 추천합니다.

React 19의 컴파일러는 Solid의 접근 방식과 어떻게 비교되나요?

React 19의 컴파일러 (이전에 React Forget)는 컴포넌트를 자동으로 메모이제이션하여 불필요한 리렌더링을 줄입니다. 이것은 중요한 개선입니다. 하지만 그것은 여전히 컴포넌트 함수를 다시 실행하고 가상 DOM을 diff하는 React의 모델 내에서 작동하고 있습니다. Solid는 두 단계를 모두 건너뜁니다. 컴파일러는 React를 더 빠르게 합니다, 하지만 기본 아키텍처를 변경하지 않습니다. 우리의 벤치마크에서 React 19는 React 18보다 약 30% 빠웠지만, 업데이트가 많은 시나리오에서는 여전히 Solid보다 느렸습니다.

중간 지점으로 Preact Signals는 어떤가요?

Preact Signals (그리고 @preact/signals-react 어댑터)는 React 생태계에 signal과 유사한 반응성을 가져옵니다. 이것은 흥미로운 접근 방식이지만, React의 핵심 모델에 맞서 싸우고 있습니다. 우리는 그것을 간단히 테스트했고 Suspense 및 동시 기능의 경계 경우를 찾았습니다. signal을 원하면 Solid는 임피던스 미스매치 없이 전체 경험을 제공합니다.

SolidJS는 React보다 배우기 어려운가요?

이미 React를 알고 있다면 Solid의 API는 친숙해 보일 것입니다 -- 유사한 JSX, 유사한 패턴. 어려운 부분은 React의 정신 모델을 배우지 않는 것입니다. 당신은 존재하지 않는 useEffect 패턴을 위해 도달할 것입니다. 당신은 props를 구조 분해하고 반응성을 깨트릴 것입니다. 당신은 초기 반환을 시도하고 왜 작동하지 않는지 궁금해할 것입니다. 숙련된 React 개발자가 Solid에서 생산적이 되려면 약 1-2주를 할당하고, React 맛의 Solid를 작성하지 않으려면 1-2개월을 더 할당하세요.

어느 프레임워크가 더 나은 TypeScript 지원을 가지고 있나요?

둘 다 뛰어난 TypeScript 지원을 가지고 있습니다. Solid는 특정 패턴 (예: <Show>의 콜백)에서 더 좁은 타입을 제공할 수 있기 때문에 약간의 이점이 있으며, 복잡한 React hooks가 때때로 요구하는 것만큼 많은 일반 타입 매개변수가 필요하지 않습니다. 하지만 정직하게 말해서, 둘 다 훌륭합니다. 이것이 결정 요소가 되어서는 안됩니다.

다음 프로젝트에 SolidJS를 사용해야 하나요?

구속 조건에 따라 다릅니다. 작은 팀이 성능에 민감한 것을 구축하고 있으며 새로운 패러다임을 배우려고 기꺼이 투자한다면 Solid는 진정으로 훌륭한 선택입니다. React 개발자를 고용해야 하거나, 확립된 컴포넌트 라이브러리를 사용하거나, 프로덕션 등급의 메타 프레임워크가 필요하다면 React가 더 안전한 선택입니다. 보편적인 답은 없습니다 -- 특정 상황에 맞는 답만 있습니다. 프로젝트에 대한 결정을 통해 생각하고 싶으시면 연락주세요 그리고 우리는 트레이드오프를 평가하는 데 도움을 드릴 수 있습니다.