필사 모드: 프론트엔드 아키텍처 완전 가이드 — React Server Components·Signals·Islands·Meta Frameworks·Bundlers를 2025년 기준으로 한 번에 정리
한국어프롤로그 — "프론트엔드는 왜 또 바뀌었나"
2018년: "React 쓰세요"
2021년: "Next.js 쓰세요"
2023년: "Server Components? Astro? Remix?"
2025년: "Signals? RSC? Islands? Turbopack vs Rspack vs Vite?"
프론트엔드는 **5년마다 혁명**이 반복된다. 2025년의 핵심 단어 6개:
1. **RSC (React Server Components)** — 서버에서 렌더, 번들 0 KB 추가
2. **Signals** — 세밀한 반응성, Virtual DOM 우회
3. **Islands** — 대부분 정적, 필요한 부분만 hydration
4. **Meta Framework** — Next, Remix, Nuxt, SvelteKit
5. **Edge Runtime** — Vercel, Cloudflare에서 V8 isolate 실행
6. **Rust-based Bundler** — Turbopack(Vercel), Rspack(바이트댄스), Biome
클라우드 네이티브(Ep 15)가 인프라를 만들었다면, 프론트엔드는 **그 위에서 사용자를 만난다**.
이 글은 Season 2 Ep 16 — **프론트엔드 아키텍처**.
RSC가 왜 혁명인지, Signals가 왜 Virtual DOM을 우회하는지, Astro Islands의 실전, Meta Framework 4대 비교, State Management 2025 판, Rust Bundler 전쟁까지.
1부 — 프론트엔드 렌더링 모델 6가지
6가지 모델
| 모델 | 설명 | 대표 |
|---|---|---|
| **SPA** | 클라이언트 렌더링 | CRA, Vite + React |
| **MPA** | 서버 페이지 반환 | PHP, Rails |
| **SSR** | 서버 렌더 + Hydration | Next Pages, Nuxt 2 |
| **SSG** | 빌드 시 정적 생성 | Astro, Gatsby, Hugo |
| **ISR** | 정적 + 주기적 재생성 | Next, Astro |
| **Streaming SSR + RSC** | 서버 컴포넌트 + 스트리밍 | Next App Router |
Core Web Vitals로 보는 차이
| 지표 | SPA | SSR | SSG | RSC Streaming |
|---|---|---|---|---|
| FCP (First Contentful Paint) | 느림 | 보통 | 빠름 | 매우 빠름 |
| LCP | 느림 | 보통 | 빠름 | 빠름 |
| TTI (Time to Interactive) | 느림 | 보통 | 빠름 | 빠름 |
| TBT (Total Blocking Time) | 높음 | 보통 | 낮음 | 낮음 |
| SEO | 나쁨 | 좋음 | 최고 | 좋음 |
| 번들 크기 | 크다 | 크다 | 작다 | **최소** |
**2025 표준**: 콘텐츠 사이트는 **Astro/Next RSC**, 앱은 **Next App Router / Remix**.
2부 — React Server Components 이해하기
RSC는 무엇인가
전통 React: 모든 컴포넌트가 클라이언트 번들에 포함
RSC: 일부 컴포넌트를 "서버 전용"으로 표시
→ 서버에서 렌더, 결과만 전송
→ 클라이언트 번들에 포함 X
**효과**:
- 번들 크기 **30-50% 감소**
- DB/파일 접근 컴포넌트 가능
- 큰 라이브러리(marked, shiki 등) 서버만 사용
Server Component vs Client Component
// app/blog/page.tsx — 기본 Server Component
export default async function BlogPage() {
const posts = await db.post.findMany(); // 서버에서 직접 DB 접근
return (
{posts.map(p => <Article key={p.id} post={p} />)}
);
}
// app/Article.tsx — 여전히 Server Component
export function Article({ post }) {
return <article>{post.content}</article>;
}
// app/Comments.tsx — Client Component ('use client' 지시어)
'use client';
export function Comments({ postId }) {
const [text, setText] = useState('');
return <input value={text} onChange={e => setText(e.target.value)} />;
}
Server Component 규칙
**할 수 있는 것**:
- async/await (최상위)
- DB/파일시스템 접근
- 서버 환경 변수
- Node 라이브러리
**할 수 없는 것**:
- `useState`, `useEffect`, 이벤트 핸들러
- 브라우저 API (window, document)
- Context 소비는 가능하지만 Provider는 Client에서
"Pass-through" 패턴
// Server Component
export default function Page() {
return (
);
}
**핵심**: Client Component가 Server Component를 import 할 수 없지만, **children으로 받을 수 있음**.
Streaming SSR + Suspense
export default function Page() {
return (
);
}
**효과**: 느린 부분이 빠른 부분을 막지 않음. 사용자는 LCP에 해당하는 콘텐츠 먼저 봄.
RSC의 한계 2025
- **생태계 미성숙**: 많은 라이브러리가 `use client`를 요구
- **Next App Router 한정**: Remix는 Loader 모델, 다른 방식
- **디버깅 어려움**: 서버/클라이언트 경계 헷갈림
- **Monorepo/패키지 호환성**: UI 라이브러리 author 고생
3부 — Signals — Virtual DOM의 대안
Virtual DOM의 비용
React 렌더링:
1. setState → 컴포넌트 함수 재실행
2. 새 VDOM 트리 생성
3. 이전 트리와 diff
4. 변경된 DOM 노드만 업데이트
문제: 컴포넌트 전체가 재실행됨 → 큰 컴포넌트에서 낭비
Signal 원리
// SolidJS
function Counter() {
const [count, setCount] = createSignal(0);
createEffect(() => console.log('count =', count()));
return (
);
}
**핵심**: `count()`를 읽는 **정확한 DOM 노드**만 업데이트. 컴포넌트 재실행 X.
Signal 프레임워크 비교
| 프레임워크 | Signal API | 특징 |
|---|---|---|
| **SolidJS** | `createSignal`, `createEffect` | JSX but no VDOM, 2020년부터 |
| **Svelte 5 Runes** | `$state`, `$derived`, `$effect` | 컴파일러 최적화, 2024 |
| **Angular 18+** | `signal()`, `computed()`, `effect()` | Google 공식 채택, 2024 |
| **Vue 3 (Refs)** | `ref()`, `computed()` | 오래된 Signal의 선배 |
| **Preact Signals** | `signal()`, `computed()` | React와 호환 가능 |
React의 대응: `use()`와 자체 상태
React는 Signal을 직접 도입하지 않고 **RSC + React Compiler(Forget)**로 대응.
- **React Compiler**: 컴포넌트 자동 memoization
- **Activity API** (실험): 보이지 않는 컴포넌트 상태 유지
언제 Signal을 고를까
**적합**:
- 빈번한 상태 업데이트 (게임, 실시간 대시보드)
- 성능 중요한 UI
- 팀이 새 패러다임 학습 가능
**부적합**:
- 기존 React 생태계 활용
- 대규모 팀 (학습 곡선)
4부 — Islands Architecture
개념
전통 SPA: 전체 페이지가 JS 앱 → 큰 번들, hydration 오래
Islands: 정적 HTML + 필요한 부분만 "섬"처럼 hydration
**장점**: 적은 JS, 빠른 LCP/TTI
**단점**: 섬 간 상태 공유 복잡
Astro 예시
// astro.build/page.astro (서버 실행)
const posts = await db.post.findMany();
{posts.map(p => <article>{p.title}</article>)}
**directive**:
- `client:load` — 즉시
- `client:visible` — IntersectionObserver
- `client:idle` — `requestIdleCallback`
- `client:media="(max-width: 500px)"` — 미디어 쿼리
- (지시어 없음) — 완전 정적
Fresh (Deno), Qwik, Marko
- **Fresh**: Deno의 Astro 아날로그, Preact 기반
- **Qwik**: "Resumable" — hydration 없이 이벤트 시점에 JS 로드
- **Marko**: eBay 제작, Islands 전 조상 (2014)
Islands 2025 현실
**Astro**: 블로그, 문서, 마케팅 사이트 지배
**Qwik**: 실험적이지만 흥미로운 아이디어(Resumability)
**Fresh**: Deno 생태계에서 유지
5부 — Meta Framework 4대 비교 2025
Next.js 15 — 가장 큰 생태계
강점:
- App Router + RSC + Streaming
- Vercel 배포 1-click
- Image, Font, Analytics 내장
- Turbopack (Rust bundler) 개발 모드
- Partial Prerendering (실험) — 정적 shell + 동적 hole
약점:
- 복잡성 (App Router 학습 곡선)
- Vercel 락인 (일부 기능)
- 버전마다 breaking change
Remix (React Router 7로 병합, 2024)
강점:
- Web standards 중심 (Request/Response, FormData)
- Nested routing + Loaders/Actions
- Progressive Enhancement 철학
- React Router 팀 운영 → 라우팅 강력
- 어디든 배포 (Cloudflare, Node, Deno)
약점:
- RSC 지원 늦음 (2025 초)
- 생태계 Next 대비 작음
- Shopify 인수 후 React Router 7 통합
SvelteKit 2 — Svelte 5 Runes 시대
강점:
- Svelte 5 Runes → 최고 성능
- 번들 크기 작음
- SSR/SSG/SPA 모두 지원
- 학습 곡선 낮음
약점:
- 생태계 작음
- 대기업 채택 적음
- Runes 전환으로 혼란 (2024)
Nuxt 3 — Vue 생태계의 Next
강점:
- Vue 3 + Composition API
- Nitro (범용 서버, 여러 런타임 지원)
- 자동 import, auto-route
- 좋은 DX
약점:
- React 대비 생태계 작음
- 국제적 채택 Next 대비 낮음
선택 가이드 2025
팀이 React 숙련 + Vercel 인프라 → Next
Web standards 선호 + Cloudflare → Remix(React Router 7)
성능/번들 크기 우선 + 학습 의지 → SvelteKit
Vue 숙련 + 범용 배포 → Nuxt
콘텐츠 사이트 (블로그/문서) → Astro (React/Vue/Svelte 모두 지원)
6부 — State Management 2025
상태의 4가지 종류
1. **Server State** (서버에서 가져옴) — TanStack Query, SWR
2. **URL State** (쿼리, path) — useSearchParams, nuqs
3. **Form State** — React Hook Form, TanStack Form
4. **Client State** (클라이언트만) — Zustand, Jotai, Redux
TanStack Query — 서버 상태의 표준
function Posts() {
const { data, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then(r => r.json()),
staleTime: 5 * 60 * 1000, // 5분
});
if (isLoading) return <Spinner />;
if (error) return <Error />;
return data.map(p => <Post key={p.id} />);
}
**왜 필수**: 캐싱, 중복 제거, 백그라운드 갱신, optimistic update, pagination, infinite scroll. 전부 기본 제공.
Zustand — Client 상태 승자
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
reset: () => set({ count: 0 }),
}));
function Counter() {
const { count, increment } = useStore();
return <button onClick={increment}>{count}</button>;
}
**장점**: Redux 대비 보일러플레이트 90% 적음, TypeScript 친화적, 4KB.
Jotai — Atomic State
const countAtom = atom(0);
const doubledAtom = atom((get) => get(countAtom) * 2);
function Counter() {
const [count, setCount] = useAtom(countAtom);
const [doubled] = useAtom(doubledAtom);
return <div>{count} / {doubled}</div>;
}
**특징**: Recoil 영감, 작은 atom 조합, dependency tracking 자동.
Redux — 이제 선택? Toolkit으로 완전히 다름
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: (state) => { state.count += 1; }, // Immer로 mutation OK
},
});
const store = configureStore({ reducer: { counter: counterSlice.reducer } });
**2025 현실**: Redux는 **대규모 엔터프라이즈**에서 여전히 현역. 하지만 **신규 프로젝트는 Zustand/Jotai 선호**.
URL State — nuqs 2025
function Search() {
const [query, setQuery] = useQueryState('q'); // ?q=...
return <input value={query ?? ''} onChange={e => setQuery(e.target.value)} />;
}
**장점**: URL이 Single Source of Truth. 뒤로가기, 공유 가능.
7부 — Form과 Validation 2025
React Hook Form + Zod
const schema = z.object({
email: z.string().email(),
age: z.number().min(18),
});
type FormData = z.infer<typeof schema>;
function SignupForm() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
});
return (
{errors.email && <span>{errors.email.message}</span>}
);
}
**2025 조합**:
- **React Hook Form** (unmount 없이 빠름)
- **Zod** (스키마 + 타입)
- **Server Action** (Next) 또는 **Mutation** (TanStack Query)
TanStack Form — 새로운 플레이어 (2024)
headless, framework-agnostic. React Hook Form 대체 가능.
Server Action (Next) — Form의 미래
// actions.ts
'use server';
const schema = z.object({ email: z.string().email() });
export async function subscribe(formData: FormData) {
const parsed = schema.safeParse({ email: formData.get('email') });
if (!parsed.success) return { error: parsed.error.message };
await db.subscriber.create({ data: parsed.data });
return { success: true };
}
// page.tsx
export default function Page() {
return (
);
}
**장점**: No API route, progressive enhancement, 타입 안전.
8부 — Bundler 전쟁 2025
역사
2015: Webpack 지배
2018: Parcel, Rollup 공존
2020: esbuild (Go, 10-100x 빠름)
2020: Vite (esbuild dev + Rollup prod)
2022: Turbopack (Vercel, Rust)
2023: Rspack (ByteDance, Rust, Webpack 호환)
2024: Rolldown (Rollup의 Rust 리라이트)
2025 주요 선수
| Bundler | 언어 | 특징 | 사용처 |
|---|---|---|---|
| **Vite 5** | esbuild + Rollup | 개발 매우 빠름, 생태계 대세 | SvelteKit, Nuxt, Remix |
| **Turbopack** | Rust | Next.js 통합, dev 모드 안정화 | Next.js 15+ |
| **Rspack** | Rust | Webpack 호환 API | Modern.js, ByteDance 사내 |
| **esbuild** | Go | 10x 빠름, 간단 | 라이브러리, 툴링 |
| **Rolldown** | Rust | Rollup 대체 (2025 rc) | Vite의 미래 |
| **Bun** | Zig | 올인원 (bundler + runtime + pm) | 독립 사용 |
Vite가 왜 이겼나
1. Dev: esbuild로 ESM dev server → 즉시 업데이트
2. Build: Rollup으로 프로덕션 번들 (tree-shaking 최고)
3. Plugin 생태계: Rollup 플러그인 호환
4. Framework-agnostic: React, Vue, Svelte, Solid 모두
**2025 체감**: webpack 기반 프로젝트를 Vite로 옮기면 **dev start 30초 → 1초**.
Turbopack vs Rspack
Turbopack: Vercel이 Next.js용으로 최적화, 2025년 prod 모드 stable
Rspack: Webpack 플러그인/로더 그대로 → 마이그레이션 쉬움
선택: Next면 Turbopack, 기존 Webpack 프로젝트면 Rspack
9부 — CSS 전략 2025
3대 방식
1. **Utility CSS (Tailwind)** — 2025 지배적
2. **CSS-in-JS (Emotion, Styled Components)** — 쇠퇴
3. **CSS Modules** — 꾸준, RSC 호환
Tailwind CSS v4 (2024)
**v4 변경**: Rust 기반 엔진 (Oxide), 설정 CSS 파일로 이동, import 한 번.
CSS-in-JS의 몰락
**2024-2025 변화**:
- **Emotion/Styled Components**: RSC 비호환 → 사용률 감소
- **Vanilla Extract**: zero-runtime CSS-in-JS, RSC 호환
- **Panda CSS**: build-time 생성, Chakra 팀 신규 프로젝트
CSS Modules 재부상
// Button.module.css
.button { background: blue; padding: 0.5rem 1rem; }
// Button.tsx
export function Button() {
return <button className={styles.button}>Click</button>;
}
**장점**: zero runtime, RSC 호환, 표준. Next/Remix 기본 지원.
shadcn/ui — 컴포넌트의 새 패턴
npx shadcn@latest add button card dialog
→ components/ui/ 에 코드가 복사됨 (npm install 아님)
**철학**: 패키지 X, 코드 복사. 자유롭게 수정. Radix UI + Tailwind.
**2025 현실**: React UI의 **사실상 표준**. Toolpad, Clerk, Vercel 모두 채택.
10부 — 프론트엔드 테스팅 2025
레이어별 도구
| 레이어 | 도구 | 비율 |
|---|---|---|
| Unit | Vitest (Jest 대체), Bun test | 60% |
| Component | Testing Library + Vitest, Storybook Test | 20% |
| E2E | Playwright (Cypress 대체 중) | 15% |
| Visual | Chromatic, Percy, Playwright | 5% |
Vitest — Jest 킬러
describe('Button', () => {
it('renders', () => {
const { getByRole } = render(<Button>Click</Button>);
expect(getByRole('button')).toHaveTextContent('Click');
});
});
**왜 Jest 대체**: Vite 기반 → 빠름, ESM 네이티브, TypeScript 지원 좋음.
Playwright — E2E의 승자
test('user can sign up', async ({ page }) => {
await page.goto('/signup');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Sign up' }).click();
await expect(page.getByText('Welcome')).toBeVisible();
});
**장점**: Cypress 대비 빠름, 멀티 브라우저 (Chromium, Firefox, WebKit), parallel 기본.
11부 — 접근성(a11y) 2025
필수 체크리스트 10개
- [ ] 모든 이미지에 alt 텍스트
- [ ] 폼 label 연결 (`<label htmlFor="">`)
- [ ] 키보드 네비게이션 가능 (Tab, Enter, Escape)
- [ ] 포커스 가시성 (focus ring)
- [ ] ARIA roles 올바르게 (버튼은 button, 링크는 a)
- [ ] 색 대비 WCAG AA (4.5:1)
- [ ] 스크린 리더 테스트 (VoiceOver, NVDA)
- [ ] 모션 줄이기 존중 (`prefers-reduced-motion`)
- [ ] 언어 명시 (`<html lang="ko">`)
- [ ] 동적 콘텐츠 알림 (`aria-live`)
도구
- **axe DevTools** (브라우저 확장)
- **Lighthouse** (Chrome DevTools)
- **React Axe** (개발 시 자동 감지)
- **ESLint plugin jsx-a11y**
Radix UI / shadcn/ui
접근성 기본 내장. 직접 구현 금지 — 라이브러리 활용.
12부 — 성능 최적화 12가지
Core Web Vitals 맞추기
1. **이미지 최적화**: WebP/AVIF, `<Image>` 컴포넌트, lazy loading
2. **폰트**: `font-display: swap`, `<link rel="preload">`
3. **CSS 최적화**: Critical CSS inline, 나머지 defer
4. **JS 분할**: `import()` dynamic, route-based splitting
5. **Prefetch**: `<Link prefetch>`, rel="prefetch"
6. **CDN**: 정적 자산 CDN, Edge Functions
7. **Compression**: Brotli (20% 작음 vs gzip)
8. **React Compiler**: 자동 memoization
9. **Server Components**: 번들 감소
10. **Streaming SSR**: TTFB 단축
11. **Database**: N+1 해결, 인덱스
12. **Monitoring**: Vercel Analytics, Web Vitals API
Vercel Speed Insights
export default function Layout({ children }) {
return (
{children}
);
}
**측정**: LCP, FID, CLS, INP, TTFB — 실제 사용자 기준.
13부 — 6개월 로드맵
**1개월차**: Next.js 15 App Router 제대로 이해. RSC vs Client Component 경계
**2개월차**: Tailwind CSS v4 + shadcn/ui로 실전 UI. Radix 접근성 활용
**3개월차**: TanStack Query로 서버 상태, Zustand로 클라이언트 상태
**4개월차**: React Hook Form + Zod + Server Action. 폼 처리 패턴
**5개월차**: Playwright E2E, Vitest 단위 테스트. CI 통합
**6개월차**: Signals 학습 (SolidJS 소형 프로젝트). Islands (Astro) 시도
14부 — 체크리스트 12개
- [ ] Next App Router or Remix or Astro 선택 (프로젝트 성격)
- [ ] Server Components 활용 (번들 감소)
- [ ] TanStack Query로 서버 상태 관리
- [ ] React Hook Form + Zod로 폼 처리
- [ ] Tailwind CSS v4 + shadcn/ui
- [ ] Playwright E2E 테스트
- [ ] 이미지는 Next Image / Astro Image
- [ ] Lighthouse 90+ 점수 유지
- [ ] a11y 체크 (axe DevTools)
- [ ] Core Web Vitals 모니터링
- [ ] Vite / Turbopack / Rspack으로 빠른 dev
- [ ] CDN/Edge 배포 (Vercel, Cloudflare)
15부 — 안티패턴 10가지
1. **useEffect로 데이터 fetching** → TanStack Query 써라
2. **모든 컴포넌트 Client Component** → RSC 활용 못함
3. **Redux 보일러플레이트 폭발** → Zustand/Jotai로 이동
4. **CSS-in-JS runtime** → 성능 저하, RSC 비호환
5. **Any prop drilling** → Context or 상태관리
6. **이미지 최적화 안 함** → LCP 박살
7. **Bundle analyzer 안 씀** → 무엇이 큰지 모름
8. **접근성 무시** → 법적 리스크 (유럽, 미국)
9. **Lighthouse 안 봄** → 사용자 경험 나쁨
10. **폼을 직접 useState로** → React Hook Form 써라
마무리 — "프론트엔드는 계속 변하지만 원칙은 같다"
2025년 프론트엔드의 3대 원칙:
1. **서버에서 할 수 있는 건 서버에서** (RSC, Streaming)
2. **보낼 JS는 최소로** (Islands, Signals, Code splitting)
3. **사용자 경험 우선** (Core Web Vitals, a11y)
프레임워크는 바뀌지만:
- HTML/CSS/JS는 여전히 기반
- 접근성은 영원히 중요
- 성능은 비즈니스에 직결
- 테스트 없으면 리팩터 못함
다음 글은 Season 2 Ep 17 — **테스팅 완전 가이드**.
Unit, Integration, E2E, Property-based, Contract Testing, Mutation Testing, Test Doubles, TDD vs BDD, CI 통합까지.
"코드를 신뢰하려면 테스트가 있어야 한다."
다음 글 예고 — "테스팅 완전 가이드: Unit·Integration·E2E·Property·Contract·Mutation"
Season 2 Ep 17은:
- Testing Pyramid vs Trophy
- Test Doubles (Stub/Mock/Fake/Spy)
- Property-based Testing (fast-check, Hypothesis)
- Contract Testing (Pact)
- Mutation Testing (Stryker)
- Snapshot Testing 현명하게
- TDD vs BDD vs TLD
- CI 통합 전략
테스트 없으면 리팩터 없다. 다음 글에서.
현재 단락 (1/397)
2018년: "React 쓰세요"