본문으로 건너뛰기

18.6 SolidStart 기초

SolidStart 는 Solid.js 팀이 공식으로 개발한 풀스택 메타프레임워크입니다. Next.js가 React 위에 있듯이, SolidStart는 Solid.js 위에 파일 기반 라우팅, 서버 함수, SSR/SSG, 스트리밍 을 추가합니다. 이 장에서는 SolidStart의 핵심 개념을 처음부터 실전 수준까지 단계별로 배웁니다.


1. SolidStart란?​

다른 메타프레임워크와 비교​

기능Next.js (React)SvelteKitSolidStart
기반 프레임워크ReactSvelteSolid.js
라우팅 방식파일 기반파일 기반파일 기반
서버 함수Server ActionsForm Actions"use server" RPC
데이터 로딩loader, fetch in RSCload 함수createAsync + cache
스트리밍React SuspenseStreamingSolid Suspense
배포 어댑터Vercel/커스텀Vercel/CF/NodeVercel/CF/Node/static
번들러Webpack/TurbopackViteVite (Vinxi 기반)

SolidStart는 Vinxi(Vite 기반 앱 번들러)를 사용하며, 서버와 클라이언트 코드를 하나의 파일에 함께 작성하는 "같은 파일에서 서버/클라이언트 혼합" 패턴을 지원합니다.

언제 SolidStart를 선택하나?​

  • SEO가 중요한 공개 사이트 (SSR 필요)
  • 블로그, 문서 사이트 (SSG 적합)
  • API 라우트와 풀스택 기능이 필요한 앱
  • 최고 성능의 서버 렌더링이 필요한 경우

순수 CSR(싱글 페이지 앱)이면 SolidStart 없이 Solid.js + Vite만으로도 충분합니다.


2. 프로젝트 생성​

새 프로젝트 시작​

npm create solid@latest my-solid-app
cd my-solid-app
npm install
npm run dev

대화형 설정에서 선택 옵션:

  • Template: bare (빈 프로젝트) 또는 with-tailwindcss, hackernews 등
  • Server Side Rendering: Yes (SSR 사용 권장)
  • TypeScript: Yes (강력 권장)

프로젝트 구조​

my-solid-app/
├── src/
│ ├── routes/ # 파일 기반 라우팅 루트
│ │ ├── index.tsx # / 경로
│ │ ├── about.tsx # /about 경로
│ │ ├── blog/
│ │ │ ├── index.tsx # /blog 경로
│ │ │ └── [slug].tsx # /blog/:slug 동적 라우트
│ │ └── (auth)/ # 레이아웃 그룹 (URL 미포함)
│ │ ├── login.tsx # /login
│ │ └── register.tsx # /register
│ ├── components/ # 재사용 컴포넌트
│ ├── lib/ # 유틸리티, DB 연결 등
│ ├── app.tsx # 앱 루트 컴포넌트
│ └── entry-server.tsx # SSR 진입점
├── public/ # 정적 파일
├── app.config.ts # SolidStart 설정
└── package.json

app.config.ts 기본 설정​

// app.config.ts
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
// Vite 플러그인 추가
vite: {
plugins: [],
},
// 서버 설정
server: {
preset: 'node', // 'vercel' | 'cloudflare' | 'netlify' | 'static'
},
});

3. 파일 기반 라우팅​

라우트 파일 명명 규칙​

src/routes/
├── index.tsx → /
├── about.tsx → /about
├── contact.tsx → /contact
├── blog/
│ ├── index.tsx → /blog
│ └── [slug].tsx → /blog/:slug
├── shop/
│ ├── [...path].tsx → /shop/* (와일드카드)
│ └── (category)/ → 레이아웃 그룹 (URL 미포함)
│ ├── electronics.tsx → /shop/electronics
│ └── clothing.tsx → /shop/clothing
└── api/
└── users.ts → /api/users (API 라우트)

기본 라우트 컴포넌트​

// src/routes/index.tsx
export default function HomePage() {
return (
<main>
<h1>홈페이지</h1>
<p>SolidStart 앱에 오신 것을 환영합니다!</p>
</main>
);
}

동적 라우트​

// src/routes/blog/[slug].tsx
import { useParams } from '@solidjs/router';

export default function BlogPost() {
const params = useParams();

return (
<article>
<h1>포스트: {params.slug}</h1>
</article>
);
}

중첩 레이아웃​

// src/routes/(app).tsx — 중첩 레이아웃 파일
import { Outlet } from '@solidjs/router';

export default function AppLayout() {
return (
<div class="app-layout">
<header>
<nav>
<a href="/">홈</a>
<a href="/dashboard">대시보드</a>
<a href="/profile">프로필</a>
</nav>
</header>
<main>
<Outlet /> {/* 자식 라우트가 여기 렌더링 */}
</main>
<footer>© 2025 My App</footer>
</div>
);
}
// src/routes/(app)/dashboard.tsx — 레이아웃의 자식
export default function Dashboard() {
return <h1>대시보드</h1>;
}

Link와 Navigate​

import { A, useNavigate } from '@solidjs/router';

function NavMenu() {
const navigate = useNavigate();

return (
<nav>
{/* A 컴포넌트 — active 클래스 자동 추가 */}
<A href="/" activeClass="active" end>홈</A>
<A href="/about" activeClass="active">소개</A>
<A href="/blog" activeClass="active">블로그</A>

{/* 프로그래매틱 내비게이션 */}
<button onClick={() => navigate('/dashboard')}>
대시보드로 이동
</button>
</nav>
);
}

4. 서버 함수 — "use server"​

기본 개념​

서버 함수는 "use server" 지시자를 붙이면 서버에서만 실행 되는 함수입니다. 클라이언트에서 호출하면 자동으로 RPC(원격 프로시저 호출) 방식으로 요청을 보냅니다.

// src/routes/index.tsx
import { createSignal } from 'solid-js';

// 이 함수는 절대 클라이언트 번들에 포함되지 않음
async function getServerData() {
'use server';
// DB 직접 접근, 환경 변수 사용, 비밀 키 사용 가능
const db = await connectDB();
return db.query('SELECT * FROM products LIMIT 10');
}

export default function HomePage() {
const [data, setData] = createSignal(null);

onMount(async () => {
const result = await getServerData();
setData(result);
});

return <div>{/* ... */}</div>;
}

파일 최상단 "use server" — 파일 전체를 서버 전용으로​

// src/lib/db.ts
'use server';

import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client);

export async function getUsers() {
return db.select().from(users);
}

export async function createUser(data: NewUser) {
return db.insert(users).values(data).returning();
}

폼 Action​

SolidStart에서 폼 제출은 action으로 처리합니다. JavaScript 없이도 동작하는 Progressive Enhancement를 지원합니다.

// src/routes/contact.tsx
import { action, useSubmission } from '@solidjs/router';

// action 정의
const sendMessage = action(async (formData: FormData) => {
'use server';

const name = formData.get('name') as string;
const email = formData.get('email') as string;
const message = formData.get('message') as string;

if (!name || !email || !message) {
throw new Error('모든 필드를 입력해주세요');
}

await sendEmail({ name, email, message });

return { success: true };
}, 'sendMessage');

export default function ContactPage() {
const submission = useSubmission(sendMessage);

return (
<div>
<h1>문의하기</h1>

{/* action="..."으로 연결 */}
<form action={sendMessage} method="post">
<input name="name" placeholder="이름" required />
<input name="email" type="email" placeholder="이메일" required />
<textarea name="message" placeholder="메시지" required />

<button type="submit" disabled={submission.pending}>
{submission.pending ? '전송 중...' : '전송'}
</button>
</form>

<Show when={submission.error}>
<p class="error">{submission.error.message}</p>
</Show>

<Show when={submission.result?.success}>
<p class="success">메시지가 전송되었습니다!</p>
</Show>
</div>
);
}

redirect와 쿠키​

import { action, redirect } from '@solidjs/router';
import { setCookie } from 'vinxi/http';

const loginAction = action(async (formData: FormData) => {
'use server';

const email = formData.get('email') as string;
const password = formData.get('password') as string;

const user = await verifyCredentials(email, password);
if (!user) throw new Error('이메일 또는 비밀번호가 올바르지 않습니다');

const token = await generateToken(user.id);

// 쿠키 설정
setCookie('auth_token', token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7일
});

// 리다이렉트
throw redirect('/dashboard');
}, 'login');

5. 데이터 로딩 — createAsync, cache, query​

cache와 createAsync — 기본 패턴​

// src/lib/queries.ts
import { cache } from '@solidjs/router';

// cache로 감싸면 서버에서 한 번만 실행, 결과를 캐시
export const getProducts = cache(async () => {
'use server';
const products = await db.select().from(productsTable);
return products;
}, 'products'); // 두 번째 인자는 캐시 키

export const getProduct = cache(async (slug: string) => {
'use server';
const [product] = await db
.select()
.from(productsTable)
.where(eq(productsTable.slug, slug));
return product ?? null;
}, 'product');
// src/routes/shop/index.tsx
import { createAsync } from '@solidjs/router';
import { getProducts } from '~/lib/queries';

export default function ShopPage() {
// createAsync — 서버에서 미리 실행, 클라이언트에서 hydration
const products = createAsync(() => getProducts());

return (
<Suspense fallback={<p>상품 로딩 중...</p>}>
<For each={products()}>
{(product) => <ProductCard product={product} />}
</For>
</Suspense>
);
}

동적 파라미터와 데이터 로딩​

// src/routes/blog/[slug].tsx
import { useParams, createAsync } from '@solidjs/router';
import { cache } from '@solidjs/router';

const getPost = cache(async (slug: string) => {
'use server';
const post = await db
.select()
.from(postsTable)
.where(eq(postsTable.slug, slug))
.limit(1);
return post[0] ?? null;
}, 'post');

export default function BlogPostPage() {
const params = useParams();
const post = createAsync(() => getPost(params.slug));

return (
<Suspense fallback={<ArticleSkeleton />}>
<Show when={post()} fallback={<p>포스트를 찾을 수 없습니다</p>}>
{(p) => (
<article>
<h1>{p().title}</h1>
<time>{p().publishedAt}</time>
<div innerHTML={p().content} />
</article>
)}
</Show>
</Suspense>
);
}

preload — 데이터 사전 로딩​

// src/routes/blog/[slug].tsx
import { cache, createAsync, useParams } from '@solidjs/router';

const getPost = cache(async (slug: string) => {
'use server';
return fetchPost(slug);
}, 'post');

// preload: 라우트 진입 전에 데이터를 미리 가져옴
export const route = {
preload: ({ params }) => getPost(params.slug),
};

export default function BlogPostPage() {
const params = useParams();
const post = createAsync(() => getPost(params.slug));
// preload 덕분에 Suspense가 즉시 해소됨
return <article>{/* ... */}</article>;
}

6. SSR / SSG 모드 설정​

SSR (서버 사이드 렌더링) — 기본값​

// app.config.ts
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
server: {
preset: 'node', // Node.js 서버에서 SSR
},
});

모든 라우트는 기본적으로 서버에서 HTML을 렌더링한 뒤 클라이언트에서 hydration합니다.

SSG (정적 사이트 생성)​

// app.config.ts
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
server: {
preset: 'static', // 빌드 시 정적 HTML 파일 생성
prerender: {
crawlLinks: true, // 링크를 따라가며 자동 사전 렌더링
routes: ['/'], // 루트부터 크롤 시작
ignore: ['/admin'], // 제외할 경로
},
},
});

동적 라우트의 경우 prerender 배열에 명시합니다.

prerender: {
routes: [
'/',
'/about',
'/blog/post-1',
'/blog/post-2',
// 실제로는 DB에서 슬러그를 가져와 동적으로 생성
],
},

라우트별 설정​

// src/routes/dashboard.tsx — 이 라우트는 CSR만 사용
export const route = {
// 서버 사이드 preload 없음
};

// SPA 모드로 내보내기
export default function Dashboard() {
return <div>대시보드 (클라이언트 전용)</div>;
}

7. 배포 어댑터​

Vercel​

npm install @solidjs/start
// app.config.ts
export default defineConfig({
server: { preset: 'vercel' },
});
// vercel.json (선택 사항)
{
"buildCommand": "npm run build",
"outputDirectory": ".vercel/output"
}

Cloudflare Pages​

// app.config.ts
export default defineConfig({
server: { preset: 'cloudflare-pages' },
});
# 배포
npx wrangler pages deploy .output/public

Cloudflare Workers에서는 process.env 대신 import.meta.env를 사용합니다.

Node.js (자체 서버)​

// app.config.ts
export default defineConfig({
server: { preset: 'node' },
});
npm run build
node .output/server/index.mjs

프록시(Nginx) 설정:

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

Static (완전 정적)​

// app.config.ts
export default defineConfig({
server: {
preset: 'static',
prerender: { crawlLinks: true, routes: ['/'] },
},
});

빌드 결과물인 .output/public 폴더를 어떤 정적 호스팅(GitHub Pages, S3, Netlify 등)에도 배포 가능합니다.


8. API 라우트​

// src/routes/api/users.ts
import type { APIEvent } from '@solidjs/start/server';

export async function GET(event: APIEvent) {
const users = await db.select().from(usersTable);
return new Response(JSON.stringify(users), {
headers: { 'Content-Type': 'application/json' },
});
}

export async function POST(event: APIEvent) {
const body = await event.request.json();

const [newUser] = await db
.insert(usersTable)
.values(body)
.returning();

return new Response(JSON.stringify(newUser), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
}
// src/routes/api/users/[id].ts
export async function GET(event: APIEvent) {
const id = event.params.id;
const user = await db
.select()
.from(usersTable)
.where(eq(usersTable.id, parseInt(id)));

if (!user.length) {
return new Response('Not Found', { status: 404 });
}

return Response.json(user[0]);
}

export async function DELETE(event: APIEvent) {
const id = event.params.id;
await db.delete(usersTable).where(eq(usersTable.id, parseInt(id)));
return new Response(null, { status: 204 });
}

9. 실전 예제​

예제 1: 블로그 — 목록 + 상세 + API​

// src/lib/blog.ts
'use server';

import { cache } from '@solidjs/router';

export interface Post {
id: number;
slug: string;
title: string;
excerpt: string;
content: string;
publishedAt: string;
author: { name: string; avatar: string };
tags: string[];
}

// 목록 조회 (캐시 5분)
export const getPosts = cache(async (tag?: string) => {
const query = db.select().from(postsTable).orderBy(desc(postsTable.publishedAt));
if (tag) query.where(sql`${tag} = ANY(tags)`);
return query;
}, 'posts');

// 단건 조회
export const getPost = cache(async (slug: string) => {
const [post] = await db
.select()
.from(postsTable)
.where(eq(postsTable.slug, slug));
return post ?? null;
}, 'post');
// src/routes/blog/index.tsx
import { createAsync, A } from '@solidjs/router';
import { getPosts } from '~/lib/blog';
import { For, Suspense } from 'solid-js';

export const route = {
preload: () => getPosts(),
};

export default function BlogListPage() {
const posts = createAsync(() => getPosts());

return (
<main class="max-w-3xl mx-auto py-12 px-4">
<h1 class="text-4xl font-bold mb-8">블로그</h1>
<Suspense fallback={<PostsSkeleton />}>
<ul class="space-y-8">
<For each={posts()}>
{(post) => (
<li class="border-b pb-8">
<A href={`/blog/${post.slug}`}>
<h2 class="text-2xl font-semibold hover:underline">{post.title}</h2>
</A>
<time class="text-gray-500 text-sm">{post.publishedAt}</time>
<p class="mt-2 text-gray-700">{post.excerpt}</p>
<div class="mt-3 flex gap-2">
<For each={post.tags}>
{(tag) => (
<span class="bg-blue-100 text-blue-700 px-2 py-1 rounded text-xs">
{tag}
</span>
)}
</For>
</div>
</li>
)}
</For>
</ul>
</Suspense>
</main>
);
}
// src/routes/blog/[slug].tsx
import { createAsync, useParams, A } from '@solidjs/router';
import { getPost } from '~/lib/blog';
import { Show, Suspense } from 'solid-js';

export const route = {
preload: ({ params }) => getPost(params.slug),
};

export default function BlogDetailPage() {
const params = useParams();
const post = createAsync(() => getPost(params.slug));

return (
<Suspense fallback={<ArticleSkeleton />}>
<Show
when={post()}
fallback={
<div class="text-center py-24">
<h1 class="text-2xl">포스트를 찾을 수 없습니다</h1>
<A href="/blog" class="text-blue-600 mt-4 block">블로그 목록으로</A>
</div>
}
>
{(p) => (
<article class="max-w-3xl mx-auto py-12 px-4">
<header class="mb-8">
<h1 class="text-4xl font-bold">{p().title}</h1>
<div class="flex items-center gap-4 mt-4">
<img
src={p().author.avatar}
alt={p().author.name}
class="w-10 h-10 rounded-full"
/>
<div>
<p class="font-medium">{p().author.name}</p>
<time class="text-sm text-gray-500">{p().publishedAt}</time>
</div>
</div>
</header>
<div class="prose" innerHTML={p().content} />
</article>
)}
</Show>
</Suspense>
);
}

예제 2: 인증 흐름 — 로그인/로그아웃​

// src/lib/auth.ts
'use server';

import { cache, action, redirect } from '@solidjs/router';
import { getCookie, setCookie, deleteCookie } from 'vinxi/http';
import { db } from '~/lib/db';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET!;

// 현재 세션 유저 조회
export const getSessionUser = cache(async () => {
const token = getCookie('auth_token');
if (!token) return null;

try {
const payload = jwt.verify(token, JWT_SECRET) as { userId: number };
const [user] = await db
.select({ id: users.id, email: users.email, name: users.name, role: users.role })
.from(users)
.where(eq(users.id, payload.userId));
return user ?? null;
} catch {
return null;
}
}, 'session-user');

// 로그인 action
export const loginAction = action(async (formData: FormData) => {
'use server';
const email = String(formData.get('email'));
const password = String(formData.get('password'));

const [user] = await db
.select()
.from(users)
.where(eq(users.email, email));

if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
return { error: '이메일 또는 비밀번호가 올바르지 않습니다' };
}

const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '7d' });
setCookie('auth_token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7,
path: '/',
});

throw redirect('/dashboard');
}, 'login');

// 로그아웃 action
export const logoutAction = action(async () => {
'use server';
deleteCookie('auth_token');
throw redirect('/login');
}, 'logout');
// src/routes/login.tsx
import { createAsync, useSubmission } from '@solidjs/router';
import { loginAction, getSessionUser } from '~/lib/auth';
import { Show } from 'solid-js';
import { redirect } from '@solidjs/router';

export const route = {
preload: async () => {
const user = await getSessionUser();
if (user) throw redirect('/dashboard');
},
};

export default function LoginPage() {
const submission = useSubmission(loginAction);

return (
<div class="min-h-screen flex items-center justify-center">
<div class="w-full max-w-md bg-white rounded-xl shadow p-8">
<h1 class="text-2xl font-bold mb-6">로그인</h1>

<form action={loginAction} method="post" class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">이메일</label>
<input
name="email"
type="email"
required
class="w-full border rounded-lg px-3 py-2"
placeholder="you@example.com"
/>
</div>
<div>
<label class="block text-sm font-medium mb-1">비밀번호</label>
<input
name="password"
type="password"
required
class="w-full border rounded-lg px-3 py-2"
/>
</div>

<Show when={submission.result?.error}>
<p class="text-red-500 text-sm">{submission.result?.error}</p>
</Show>

<button
type="submit"
disabled={submission.pending}
class="w-full bg-blue-600 text-white rounded-lg py-2 font-medium disabled:opacity-50"
>
{submission.pending ? '로그인 중...' : '로그인'}
</button>
</form>
</div>
</div>
);
}
// src/routes/(app)/dashboard.tsx — 보호된 라우트
import { createAsync } from '@solidjs/router';
import { getSessionUser, logoutAction } from '~/lib/auth';
import { Show } from 'solid-js';
import { redirect } from '@solidjs/router';

export const route = {
preload: async () => {
const user = await getSessionUser();
if (!user) throw redirect('/login');
},
};

export default function DashboardPage() {
const user = createAsync(() => getSessionUser());

return (
<div class="p-8">
<Show when={user()}>
{(u) => (
<div>
<h1 class="text-3xl font-bold">대시보드</h1>
<p class="mt-2 text-gray-600">안녕하세요, {u().name}님!</p>

<form action={logoutAction} method="post" class="mt-6">
<button type="submit" class="bg-red-500 text-white px-4 py-2 rounded">
로그아웃
</button>
</form>
</div>
)}
</Show>
</div>
);
}

10. 고수 팁​

팁 1: 에러 바운더리와 ErrorBoundary​

// src/app.tsx
import { ErrorBoundary } from 'solid-js';

export default function App() {
return (
<ErrorBoundary
fallback={(err, reset) => (
<div class="error-page">
<h1>오류가 발생했습니다</h1>
<pre>{err.message}</pre>
<button onClick={reset}>다시 시도</button>
</div>
)}
>
<Router />
</ErrorBoundary>
);
}

팁 2: Middleware로 인증 처리​

// src/middleware.ts
import { createMiddleware } from '@solidjs/start/middleware';

export default createMiddleware({
onRequest: [
async (event) => {
const url = new URL(event.request.url);
const token = getCookie(event, 'auth_token');

const protectedPaths = ['/dashboard', '/profile', '/settings'];
if (protectedPaths.some(p => url.pathname.startsWith(p))) {
if (!token || !(await verifyToken(token))) {
return new Response(null, {
status: 302,
headers: { Location: '/login' },
});
}
}
},
],
});
// app.config.ts
export default defineConfig({
middleware: './src/middleware.ts',
});

팁 3: createAsync의 초기 데이터 패턴​

// 서버에서 이미 받은 데이터를 클라이언트에서 재사용
const data = createAsync(() => getData(), {
initialValue: props.serverData, // SSR에서 받은 데이터로 초기화
deferStream: true, // 스트리밍 지연
});

팁 4: revalidate로 캐시 무효화​

import { revalidate } from '@solidjs/router';
import { getProducts } from '~/lib/queries';

const deleteProduct = action(async (id: number) => {
'use server';
await db.delete(productsTable).where(eq(productsTable.id, id));
// 캐시 무효화 → 다음 렌더링 시 자동 재조회
revalidate(getProducts.key);
}, 'deleteProduct');

팁 5: 스트리밍 SSR 활용​

// 무거운 컴포넌트는 Suspense로 감싸서 나머지 페이지를 먼저 전송
export default function ProductPage() {
const product = createAsync(() => getProduct(params.slug));
const reviews = createAsync(() => getReviews(params.slug));

return (
<div>
{/* 빠른 데이터 — 먼저 스트리밍 */}
<Suspense fallback={<ProductSkeleton />}>
<ProductDetail product={product()} />
</Suspense>

{/* 느린 데이터 — 나중에 스트리밍 */}
<Suspense fallback={<ReviewsSkeleton />}>
<ReviewList reviews={reviews()} />
</Suspense>
</div>
);
}

정리​

기능SolidStart API설명
라우팅src/routes/ 파일 구조파일 이름 = URL 경로
동적 라우트[param].tsxuseParams()로 접근
레이아웃(group).tsx + <Outlet />URL 영향 없는 레이아웃 그룹
서버 함수"use server"서버 전용 실행, RPC 자동화
폼 처리action() + useSubmission()Progressive Enhancement
데이터 로딩cache() + createAsync()SSR + 캐싱
사전 로딩route.preload라우트 진입 전 데이터 fetch
캐시 무효화revalidate()mutation 후 자동 갱신
배포preset 설정Vercel/CF/Node/Static

다음 장에서는...​

18.7 실전 고수 팁 에서는 Solid.js를 실무에서 능숙하게 사용하기 위한 고급 패턴들을 다룹니다. 구조 분해 할당의 위험성, batch/untrack을 활용한 성능 최적화, TypeScript 통합, 테스팅, 그리고 React에서 Solid.js로 마이그레이션하는 방법까지 배웁니다.