415 lines
8.8 KiB
Markdown
415 lines
8.8 KiB
Markdown
---
|
|
id: frontend-solidjs-qwik
|
|
title: SolidJS / Qwik — Reactive 후속 framework
|
|
category: Coding
|
|
status: draft
|
|
source_trust_level: B
|
|
verification_status: conceptual
|
|
created_at: 2026-05-09
|
|
updated_at: 2026-05-09
|
|
tags: [frontend, solidjs, qwik, vibe-coding]
|
|
tech_stack: { language: "TS", applicable_to: ["Frontend"] }
|
|
applied_in: []
|
|
aliases: [SolidJS, Qwik, Svelte 5, fine-grained reactivity, resumability, signals]
|
|
---
|
|
|
|
# SolidJS / Qwik
|
|
|
|
> React 의 후속. **SolidJS = signals (fine-grained), Qwik = resumability (0 hydration)**. React 지식 transferable + 빠름.
|
|
|
|
## 📖 핵심 개념
|
|
- Signals: fine-grained reactivity (vs React 의 re-render).
|
|
- Resumability (Qwik): 0 hydration cost.
|
|
- Compile-time: Svelte / Solid 가 build 시 optimize.
|
|
- React API like: 학습 비용 작음.
|
|
|
|
## 💻 코드 패턴
|
|
|
|
### SolidJS — Signals
|
|
```tsx
|
|
import { createSignal, createEffect } from 'solid-js';
|
|
|
|
function Counter() {
|
|
const [count, setCount] = createSignal(0);
|
|
|
|
createEffect(() => {
|
|
console.log('count:', count()); // call as function
|
|
});
|
|
|
|
return <button onClick={() => setCount(c => c + 1)}>{count()}</button>;
|
|
}
|
|
```
|
|
|
|
→ React 비슷 but `count()` (call). Re-render 없음 — DOM 만 업데이트.
|
|
|
|
### Solid — derived (vs useMemo)
|
|
```tsx
|
|
const [first, setFirst] = createSignal('Alice');
|
|
const [last, setLast] = createSignal('Smith');
|
|
|
|
const fullName = createMemo(() => `${first()} ${last()}`);
|
|
|
|
// Or just:
|
|
const fullName = () => `${first()} ${last()}`; // function call
|
|
|
|
return <p>{fullName()}</p>;
|
|
```
|
|
|
|
→ Signals 가 trigger 때만 re-compute.
|
|
|
|
### Solid — Stores (object)
|
|
```tsx
|
|
import { createStore } from 'solid-js/store';
|
|
|
|
const [user, setUser] = createStore({ name: 'Alice', age: 30 });
|
|
|
|
setUser('age', 31); // immutable-style update
|
|
setUser({ name: 'Bob', age: 25 });
|
|
|
|
return <p>{user.name}, {user.age}</p>;
|
|
```
|
|
|
|
### Solid — Show / For (vs JSX)
|
|
```tsx
|
|
import { Show, For, Switch, Match } from 'solid-js';
|
|
|
|
<Show when={loggedIn()} fallback={<Login />}>
|
|
<Dashboard />
|
|
</Show>
|
|
|
|
<For each={items()}>
|
|
{(item) => <li>{item.name}</li>}
|
|
</For>
|
|
|
|
<Switch>
|
|
<Match when={status() === 'loading'}>Loading...</Match>
|
|
<Match when={status() === 'error'}>Error</Match>
|
|
<Match when={status() === 'success'}>Done</Match>
|
|
</Switch>
|
|
```
|
|
|
|
→ React 의 `{condition && ...}` 보다 명시.
|
|
|
|
### SolidStart (Next-like)
|
|
```tsx
|
|
// routes/users/[id].tsx
|
|
import { createAsync, useParams } from '@solidjs/router';
|
|
|
|
export default function UserPage() {
|
|
const params = useParams();
|
|
const user = createAsync(() => fetchUser(params.id));
|
|
|
|
return (
|
|
<Show when={user()} fallback={<Loading />}>
|
|
<h1>{user()!.name}</h1>
|
|
</Show>
|
|
);
|
|
}
|
|
|
|
// Server function
|
|
'use server';
|
|
async function fetchUser(id: string) {
|
|
return db.user.findUnique({ where: { id } });
|
|
}
|
|
```
|
|
|
|
### Qwik — Resumability
|
|
```tsx
|
|
import { component$, useSignal } from '@builder.io/qwik';
|
|
|
|
export default component$(() => {
|
|
const count = useSignal(0);
|
|
|
|
return (
|
|
<button onClick$={() => count.value++}>
|
|
{count.value}
|
|
</button>
|
|
);
|
|
});
|
|
```
|
|
|
|
→ `$` = lazy boundary. JS 가 사용자 click 까지 download 안 됨.
|
|
|
|
### Qwik 의 magic
|
|
```
|
|
Server: HTML + serialized state.
|
|
Client: 0 JS until 사용자 interacts.
|
|
Click: 그 handler만 download + execute.
|
|
|
|
→ Massive site = first paint 즉시.
|
|
```
|
|
|
|
### Qwik City (Next-like)
|
|
```tsx
|
|
// routes/users/[id]/index.tsx
|
|
import { component$, useSignal } from '@builder.io/qwik';
|
|
import { routeLoader$ } from '@builder.io/qwik-city';
|
|
|
|
export const useUserData = routeLoader$(async ({ params }) => {
|
|
return await db.user.findUnique({ where: { id: params.id } });
|
|
});
|
|
|
|
export default component$(() => {
|
|
const user = useUserData();
|
|
return <h1>{user.value.name}</h1>;
|
|
});
|
|
```
|
|
|
|
### Solid vs React (성능)
|
|
```
|
|
React: re-render entire component tree
|
|
Solid: update only specific DOM nodes (signals)
|
|
|
|
큰 list 의 1 item 변경:
|
|
React: 전체 list virtual DOM diff
|
|
Solid: 그 1 item DOM 만 update
|
|
|
|
→ Solid 가 5-10x 빠름 자주.
|
|
```
|
|
|
|
### Bundle size
|
|
```
|
|
React + ReactDOM: ~45 KB (gzip)
|
|
Solid: ~7 KB
|
|
Preact: ~3 KB
|
|
Svelte: ~3 KB (compile)
|
|
Qwik: 5 KB initial (lazy 더 download)
|
|
```
|
|
|
|
→ Mobile / slow network = 큰 차이.
|
|
|
|
### Svelte 5 (Runes)
|
|
```svelte
|
|
<script>
|
|
let count = $state(0);
|
|
let doubled = $derived(count * 2);
|
|
|
|
$effect(() => {
|
|
console.log('count:', count);
|
|
});
|
|
</script>
|
|
|
|
<button onclick={() => count++}>{count}</button>
|
|
<p>Doubled: {doubled}</p>
|
|
```
|
|
|
|
→ Compile-time. 작은 bundle.
|
|
|
|
### Migration React → Solid (점진)
|
|
```
|
|
1. Solid 가 같은 mental model (component, props, state).
|
|
2. Hook 이름 다름 — useState → createSignal.
|
|
3. setState set 함수 — 같음.
|
|
4. JSX 같음.
|
|
5. 학습 1-2 day.
|
|
```
|
|
|
|
### React → Qwik
|
|
```
|
|
Qwik 가 React 비슷 + $ boundary.
|
|
큰 차이: serializable state.
|
|
```
|
|
|
|
### Suspense (data loading)
|
|
```tsx
|
|
// Solid
|
|
import { Suspense, ErrorBoundary } from 'solid-js';
|
|
|
|
<ErrorBoundary fallback={<Error />}>
|
|
<Suspense fallback={<Loading />}>
|
|
<UserList />
|
|
</Suspense>
|
|
</ErrorBoundary>
|
|
```
|
|
|
|
```tsx
|
|
// Qwik
|
|
<Resource
|
|
value={users}
|
|
onPending={() => <Loading />}
|
|
onResolved={(users) => <UserList users={users} />}
|
|
/>
|
|
```
|
|
|
|
### Routing
|
|
```
|
|
Solid: @solidjs/router (file-based + dynamic)
|
|
Qwik: @builder.io/qwik-city (file-based)
|
|
React: TanStack Router / Next App Router
|
|
```
|
|
|
|
### State management
|
|
```
|
|
Solid:
|
|
- Signals (built-in)
|
|
- Stores (object)
|
|
|
|
Qwik:
|
|
- useSignal / useStore
|
|
|
|
→ External state (Redux 등) 보통 안 필요.
|
|
```
|
|
|
|
### Form
|
|
```tsx
|
|
// Solid
|
|
import { createSignal } from 'solid-js';
|
|
|
|
function Form() {
|
|
const [email, setEmail] = createSignal('');
|
|
|
|
return (
|
|
<form onSubmit={(e) => { e.preventDefault(); submit(email()); }}>
|
|
<input value={email()} onInput={(e) => setEmail(e.currentTarget.value)} />
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Server actions (Qwik)
|
|
```tsx
|
|
import { routeAction$, Form } from '@builder.io/qwik-city';
|
|
|
|
export const useCreateUser = routeAction$(async (data) => {
|
|
return db.user.create({ data });
|
|
});
|
|
|
|
export default component$(() => {
|
|
const action = useCreateUser();
|
|
return (
|
|
<Form action={action}>
|
|
<input name="email" />
|
|
<button>Submit</button>
|
|
</Form>
|
|
);
|
|
});
|
|
```
|
|
|
|
### CSS / styling
|
|
```tsx
|
|
// Solid + Tailwind
|
|
<div class="p-4 rounded">...</div>
|
|
|
|
// Solid + CSS module
|
|
import styles from './Card.module.css';
|
|
<div class={styles.card}>...</div>
|
|
```
|
|
|
|
### Animation (Solid Motion)
|
|
```tsx
|
|
import { Motion, Presence } from 'solid-motionone';
|
|
|
|
<Presence>
|
|
<Show when={visible()}>
|
|
<Motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
|
|
Content
|
|
</Motion.div>
|
|
</Show>
|
|
</Presence>
|
|
```
|
|
|
|
### Test
|
|
```ts
|
|
import { render } from '@solidjs/testing-library';
|
|
import { Counter } from './Counter';
|
|
|
|
test('increments', () => {
|
|
const { getByRole } = render(() => <Counter />);
|
|
const button = getByRole('button');
|
|
expect(button).toHaveTextContent('0');
|
|
button.click();
|
|
expect(button).toHaveTextContent('1');
|
|
});
|
|
```
|
|
|
|
### Production usage
|
|
```
|
|
SolidJS:
|
|
- Codeium (AI), Builder.io
|
|
- 작지만 성장
|
|
|
|
Qwik:
|
|
- Builder.io
|
|
- 새로움
|
|
|
|
Svelte:
|
|
- Bloomberg, NYTimes, Apple
|
|
- 큰 ecosystem
|
|
|
|
→ React 가 dominant — but alternative 가치.
|
|
```
|
|
|
|
### Why migrate?
|
|
```
|
|
React 가 "충분히 빠름" 인 경우:
|
|
- 작은 / medium app
|
|
- 익숙한 팀
|
|
|
|
다른 framework 가치:
|
|
- Massive content site (Qwik)
|
|
- Performance critical (Solid)
|
|
- 작은 bundle (Svelte)
|
|
- 학습 / 호기심
|
|
```
|
|
|
|
### Deno / Bun 호환
|
|
```
|
|
모두 Node + Deno + Bun OK.
|
|
SolidStart / Qwik City = Vite 기반 — modern.
|
|
```
|
|
|
|
### Server / SSR
|
|
```
|
|
SolidStart: SSR + 점진 hydration
|
|
Qwik City: resumability (0 hydration)
|
|
SvelteKit: SSR + 점진 hydration
|
|
```
|
|
|
|
→ Qwik 의 resumability = 가장 modern.
|
|
|
|
### Adopt hesitation
|
|
```
|
|
- 작은 community → Stack Overflow 답 적음
|
|
- 일부 lib X (React 보다)
|
|
- Hire 어려움 (사람 React 더 많음)
|
|
- 점차 변경 — but learning curve
|
|
```
|
|
|
|
### Trial 권장
|
|
```
|
|
1. 작은 side project — Solid / Qwik 시도
|
|
2. Marketing site — Astro + Solid island
|
|
3. 적합 발견 시 main project 도
|
|
|
|
→ 점진. Risk 작음.
|
|
```
|
|
|
|
## 🤔 의사결정 기준
|
|
| 상황 | 추천 |
|
|
|---|---|
|
|
| Performance critical | SolidJS |
|
|
| 큰 content + 작은 interaction | Qwik |
|
|
| 작은 bundle + 단순 | Svelte 5 |
|
|
| 일반 / 큰 ecosystem | React |
|
|
| Migration React | Solid (가장 비슷) |
|
|
| New project (호기심) | Solid 또는 Qwik |
|
|
|
|
## ❌ 안티패턴
|
|
- **Solid signal 가 React state 같은 가정**: rendering 다름.
|
|
- **Qwik $ 잊음**: lazy boundary 안 됨.
|
|
- **모든 거 signal**: 의미 없음. local state.
|
|
- **Hire 무 plan**: 몇 명 만 알 = bus factor.
|
|
- **Big rewrite**: 점진 migration 더 안전.
|
|
- **React lib 가정**: 다른 ecosystem.
|
|
|
|
## 🤖 LLM 활용 힌트
|
|
- React 알면 Solid 쉬움 (1-2 day).
|
|
- Signal = fine-grained reactivity.
|
|
- Qwik = resumability (0 hydration).
|
|
- Svelte = compile-time.
|
|
|
|
## 🔗 관련 문서
|
|
- [[Perf_React_Reconciler]]
|
|
- [[Frontend_Progressive_Enhancement]]
|
|
- [[Frontend_Astro_Patterns]]
|