RedwoodSDK: Cloudflare를 위한 React 프레임워크
예전에 RedwoodJS로 풀스택 웹 앱 만들기라는 글에서 RedwoodJS를 소개해드린 적이 있는데요. React, GraphQL, Prisma를 하나로 묶어서 풀스택 앱을 빠르게 만들 수 있게 해주는 프레임워크였죠. 그 글 마지막에 잠깐 언급했던 RedwoodSDK가 2025년 3월 드디어 v1.0으로 정식 출시되었습니다.
RedwoodSDK는 기존 RedwoodJS의 후속작이라기보다는 완전히 새로운 프레임워크에 가깝습니다. GraphQL과 Prisma 중심이었던 기존 접근 방식을 버리고, React Server Components와 Cloudflare Workers를 기반으로 처음부터 다시 만들었거든요.
이번 글에서는 RedwoodSDK가 어떤 프레임워크인지, 기존 RedwoodJS와 무엇이 다른지, 그리고 실제로 프로젝트를 만들어보면서 핵심 기능을 살펴보겠습니다.
RedwoodSDK란?
RedwoodSDK(패키지명: rwsdk)는 스스로 “Cloudflare를 위한 React 프레임워크”라고 소개하고 있어요.
Cloudflare Workers 위에서 React Server Components를 활용해 풀스택 웹 앱을 만드는 프레임워크인데요.
기존 RedwoodJS와 비교하면 거의 모든 것이 바뀌었습니다.
| 항목 | RedwoodJS | RedwoodSDK |
|---|---|---|
| 아키텍처 | API/Web 분리, GraphQL 중심 | RSC 기반, 단일 엔트리 |
| 배포 대상 | Node.js 호스트 (Vercel, Netlify 등) | Cloudflare Workers 전용 |
| 라우팅 | JSX 기반 Routes.js | 코드 기반 defineApp() + route() |
| 데이터 계층 | GraphQL + Prisma + PostgreSQL | RSC 직접 페칭 + D1 (SQLite) |
| ORM | Prisma | Kysely (경량 쿼리 빌더) |
| 빌드 도구 | Webpack → Vite | Vite 플러그인 |
| 철학 | 코드 생성, 컨벤션 기반 | ”Zero Magic” — 작성한 코드가 곧 실행 코드 |
가장 눈에 띄는 변화는 Cloudflare Workers 전용이라는 점입니다. 기존 RedwoodJS는 어디든 배포할 수 있었지만 RedwoodSDK는 Cloudflare 플랫폼에 올인했어요. 덕분에 D1(데이터베이스), R2(파일 스토리지), Durable Objects(실시간), KV(키-값 저장소), Queues(메시지 큐) 같은 Cloudflare 서비스를 프레임워크 수준에서 자연스럽게 사용할 수 있습니다.
세 가지 핵심 철학
RedwoodSDK는 세 가지 철학 위에 만들어졌습니다.
첫 번째는 Zero Magic입니다. “작성한 코드가 곧 실행되는 코드”라는 원칙인데요. 기존 RedwoodJS에서는 Generator가 코드를 자동 생성해주고, 파일 이름 규칙에 따라 동작이 달라지고, 내부적으로 트랜스파일이 일어나는 부분이 많았습니다. RedwoodSDK에서는 이런 “마법” 같은 동작이 전부 사라졌어요. 코드 생성도 없고, 파일 이름 규칙도 없고, 눈에 보이지 않는 트랜스파일도 없습니다.
두 번째는 Composability Over Configuration입니다. 설정 파일로 동작을 바꾸는 대신 작은 단위의 함수를 조합해서 원하는 동작을 만들어내는 방식이에요. 미들웨어, 라우트 핸들러, 인터럽터 같은 것들이 모두 단순한 함수이고, 이것들을 배열로 나열하면 그게 곧 앱의 동작이 됩니다.
세 번째는 Web-First Architecture입니다.
fetch, Request, Response, URL 같은 웹 표준 API를 그대로 사용합니다.
프레임워크가 별도의 래퍼를 제공하는 대신 브라우저와 서버에서 동일하게 동작하는 표준 API에 의존하죠.
프로젝트 생성
새 프로젝트를 만들어볼까요?
npx create-rwsdk my-app
프로젝트가 생성되면 의존성을 설치하고 개발 서버를 시작합니다.
cd my-app
npm install
npm run dev
개발 서버는 http://localhost:5173에서 시작됩니다.
내부적으로 Miniflare가 Cloudflare Workers 런타임을 로컬에서 에뮬레이션하기 때문에 D1, R2, Durable Objects 같은 Cloudflare 서비스도 별도 설치 없이 로컬에서 바로 테스트할 수 있어요.
생성된 프로젝트 구조를 살펴보겠습니다.
my-app/
├── public/ # 정적 자산
├── src/
│ ├── app/ # 애플리케이션 코드 (페이지, 컴포넌트)
│ ├── client.tsx # 클라이언트 엔트리 포인트
│ └── worker.tsx # 메인 엔트리 포인트 (defineApp)
├── types/ # TypeScript 타입 정의
├── package.json
├── tsconfig.json
├── vite.config.mts # Vite 설정
└── wrangler.jsonc # Cloudflare Workers 설정
기존 RedwoodJS가 api/와 web/ 폴더를 분리했던 것과 달리 RedwoodSDK는 단일 src/ 폴더 안에 모든 코드가 들어갑니다.
React Server Components 덕분에 서버 코드와 클라이언트 코드가 같은 공간에 공존할 수 있거든요.
엔트리 포인트
앱의 핵심은 src/worker.tsx 파일입니다.
import { defineApp } from "rwsdk/worker";
import { route, render } from "rwsdk/router";
import { Document } from "./app/Document";
import { Home } from "./app/pages/Home";
export default defineApp([render(Document, [route("/", Home)])]);
defineApp() 함수에 배열을 전달하는데, 이 배열이 요청을 처리하는 파이프라인 역할을 합니다.
render() 함수는 Document 컴포넌트로 페이지를 감싸고, 그 안에서 route()로 정의한 경로에 따라 적절한 페이지 컴포넌트를 렌더링해요.
파일 기반 라우팅이 아니라 코드로 직접 라우트를 정의하는 방식이라서 라우팅 구조가 한눈에 보입니다. 마법처럼 파일 이름에서 라우트가 생겨나는 게 아니라 개발자가 명시적으로 “이 경로에는 이 컴포넌트”라고 지정하는 거죠.
라우팅 시스템
라우팅을 좀 더 자세히 살펴볼까요?
정적 라우트는 단순히 경로 문자열과 컴포넌트를 매핑합니다.
route("/about", AboutPage);
동적 파라미터가 필요하면 :param 문법을 사용합니다.
route("/users/:id", ({ params }) => {
return <UserProfile userId={params.id} />;
});
와일드카드 패턴도 지원해요.
route("/files/*", ({ params }) => {
const filePath = params.$0; // 나머지 경로를 캡처
return <FileViewer path={filePath} />;
});
HTTP 메서드별로 다른 핸들러를 지정하는 것도 가능합니다. 이 기능은 API 엔드포인트를 만들 때 유용하죠.
route("/api/posts", {
GET: async () => {
const posts = await db.selectFrom("posts").selectAll().execute();
return new Response(JSON.stringify(posts), {
headers: { "Content-Type": "application/json" },
});
},
POST: async ({ request }) => {
const body = await request.json();
await db.insertInto("posts").values(body).execute();
return new Response("Created", { status: 201 });
},
});
여기서 Request와 Response가 웹 표준 API라는 점에 주목해주세요.
프레임워크 고유의 요청/응답 객체가 아니라 브라우저에서 쓰는 그 fetch API의 Request와 Response입니다.
미들웨어와 인터럽터
RedwoodSDK에서는 미들웨어를 “인터럽터(interrupter)“라고 부릅니다.
defineApp() 배열에 함수를 넣으면 그게 곧 미들웨어가 되는데요.
function requireAuth({ request, ctx }) {
if (!ctx.user) {
return new Response("Unauthorized", { status: 401 });
}
// undefined를 반환하면 다음 핸들러로 넘어감
}
export default defineApp([
requireAuth,
render(Document, [
route("/dashboard", DashboardPage),
route("/settings", SettingsPage),
]),
]);
인터럽터 함수가 Response를 반환하면 파이프라인이 거기서 멈추고 그 응답을 클라이언트에 보냅니다.
undefined를 반환하면(아무것도 반환하지 않으면) 다음 단계로 넘어가요.
이런 단순한 규칙 하나로 인증, 로깅, 헤더 설정 같은 공통 로직을 처리할 수 있습니다.
특정 라우트 그룹에만 미들웨어를 적용하는 것도 쉽습니다.
export default defineApp([
render(Document, [
route("/", HomePage), // 인증 불필요
route("/about", AboutPage), // 인증 불필요
requireAuth, // 여기서부터 인증 필요
route("/dashboard", Dashboard),
route("/settings", Settings),
]),
]);
배열에서의 순서가 곧 실행 순서이니까 requireAuth 아래에 있는 라우트만 인증이 적용됩니다.
설정 파일에서 패턴을 매칭하는 것보다 훨씬 직관적이죠.
React Server Components와 데이터 페칭
RedwoodSDK의 데이터 페칭은 React Server Components(RSC)를 기반으로 합니다. 서버 컴포넌트는 서버에서 실행되기 때문에 데이터베이스에 직접 접근할 수 있어요.
import { db } from "../db";
async function PostList() {
const posts = await db
.selectFrom("posts")
.selectAll()
.orderBy("createdAt", "desc")
.execute();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
);
}
기존 RedwoodJS에서는 Cell이라는 독자적인 패턴으로 GraphQL 쿼리를 보내고 로딩/에러/성공 상태를 선언적으로 처리했는데요.
RedwoodSDK에서는 그냥 async 컴포넌트에서 데이터베이스를 바로 조회합니다. GraphQL 계층이 완전히 사라진 거죠.
로딩 상태를 처리하고 싶다면 React의 Suspense를 사용합니다.
import { Suspense } from "react";
function PostsPage() {
return (
<Suspense fallback={<div>로딩 중...</div>}>
<PostList />
</Suspense>
);
}
데이터를 변경하는 작업은 Server Functions를 사용합니다.
RedwoodSDK는 serverQuery와 serverAction 두 가지를 제공하는데요.
import { serverAction } from "rwsdk/server";
const createPost = serverAction(async (formData) => {
const title = formData.get("title");
const body = formData.get("body");
await db.insertInto("posts").values({ title, body }).execute();
});
serverQuery는 GET 요청으로 데이터를 조회할 때, serverAction은 POST 요청으로 데이터를 변경할 때 사용합니다.
serverAction이 실행되면 페이지가 자동으로 다시 렌더링되어 변경사항이 반영돼요.
데이터베이스: D1과 Kysely
RedwoodSDK는 데이터베이스로 Cloudflare D1을 사용합니다. D1은 Cloudflare의 엣지에서 실행되는 SQLite 기반 데이터베이스인데요.
기존 RedwoodJS가 Prisma ORM을 사용했다면 RedwoodSDK는 Kysely라는 경량 쿼리 빌더를 사용합니다. Kysely는 TypeScript 타입 안전성을 제공하면서도 SQL에 가까운 문법을 유지해서 SQL을 아는 개발자에게 친숙해요.
// Prisma (RedwoodJS)
const posts = await db.post.findMany({
where: { published: true },
orderBy: { createdAt: "desc" },
});
// Kysely (RedwoodSDK)
const posts = await db
.selectFrom("posts")
.selectAll()
.where("published", "=", true)
.orderBy("createdAt", "desc")
.execute();
Prisma는 자체 스키마 언어와 마이그레이션 시스템을 갖추고 있어서 기능이 풍부하지만 그만큼 무겁습니다. Kysely는 SQL 문법을 거의 그대로 TypeScript로 옮겨놓은 것에 가까워서 훨씬 가볍고 예측 가능하죠. “Zero Magic”이라는 RedwoodSDK의 철학과 잘 맞는 선택입니다.
타입 안전한 라우팅
RedwoodSDK는 linkFor() 함수로 타입 안전한 링크를 생성할 수 있습니다.
import { linkFor } from "rwsdk/router";
const link = linkFor<App>();
// TypeScript가 파라미터를 검증
const href = link("/users/:id", { id: 42 });
// → "/users/42"
// 잘못된 파라미터는 컴파일 에러
const bad = link("/users/:id", { name: "dale" });
// ❌ TypeScript 에러!
라우트 정의에서 타입 정보를 추출하기 때문에 라우트가 변경되면 컴파일 타임에 잘못된 링크를 잡아낼 수 있어요.
기존 RedwoodJS의 routes.post({ id: 42 })와 비슷한 개념이지만 웹 표준 URL 패턴을 그대로 사용한다는 점이 다릅니다.
인증
RedwoodSDK는 Passkey 기반의 인증 애드온을 제공합니다. 비밀번호 없이 생체 인증이나 보안 키로 로그인하는 최신 웹 표준 방식이에요.
좀 더 커스텀한 인증이 필요하다면 Cloudflare Durable Objects를 활용한 세션 관리도 가능합니다.
import { defineDurableSession } from "rwsdk/auth";
const session = defineDurableSession({
cookie: {
name: "session",
secure: true,
httpOnly: true,
},
});
세션 데이터를 Durable Objects에 저장하기 때문에 별도의 세션 스토어(Redis 같은)를 설정할 필요가 없어요. Cloudflare 인프라 안에서 모든 것이 해결되는 구조입니다.
배포
배포는 정말 간단합니다.
npm run release
이 명령어 하나로 Wrangler CLI가 앱을 Cloudflare Workers에 배포합니다. 빌드부터 업로드까지 전부 자동으로 처리되고, 전 세계 300개 이상의 엣지 로케이션에서 바로 실행돼요.
처음 배포할 때는 Cloudflare 대시보드에서 Workers 도메인을 만들어야 할 수 있지만 그 이후로는 npm run release만 실행하면 됩니다.
CI/CD 파이프라인에 넣기에도 부담이 없는 단순한 구조예요.
RedwoodJS vs RedwoodSDK, 어떤 걸 선택할까?
같은 팀에서 만들었지만 두 프레임워크의 성격은 완전히 다릅니다.
기존 RedwoodJS는 “모든 결정을 프레임워크가 대신 해주는” 스타일이었어요. React, GraphQL, Prisma라는 기술 스택이 미리 정해져 있고, Generator가 보일러플레이트를 자동 생성해주니까 빠르게 프로토타입을 만들 수 있었죠. 하지만 그만큼 프레임워크의 규칙에 묶이는 부분도 있었습니다.
RedwoodSDK는 반대로 “개발자에게 선택권을 돌려주는” 스타일입니다. 코드 생성도 없고, 파일 이름 규칙도 없고, GraphQL도 필수가 아닙니다. 대신 React Server Components와 웹 표준 API라는 단순한 기반 위에서 개발자가 직접 구조를 만들어나가요.
Cloudflare 생태계에서 React 앱을 만들고 싶다면 RedwoodSDK가 좋은 선택입니다. 서버리스 엣지 컴퓨팅의 성능 이점을 살리면서 RSC로 깔끔한 풀스택 개발 경험을 얻을 수 있으니까요. 반면에 Node.js 호스팅이 필요하거나 GraphQL 기반 아키텍처를 선호한다면 기존 RedwoodJS가 여전히 유효합니다.
마치며
RedwoodSDK는 RedwoodJS 팀이 “프레임워크를 처음부터 다시 만든다면?”이라는 질문에 대한 답이라고 할 수 있어요. GraphQL 계층을 걷어내고 React Server Components를 도입하고, Cloudflare Workers에 올인한 결과물인데요.
“Zero Magic”이라는 철학처럼 눈에 보이는 코드가 곧 실행되는 코드라는 점이 매력적이고, 웹 표준 API에 기반한 설계도 장기적으로 올바른 방향이라고 생각합니다. 다만 Cloudflare 전용이라는 점은 플랫폼 종속이라는 트레이드오프가 있으니 프로젝트 요구사항에 맞는지 확인해볼 필요가 있겠죠.
Cloudflare 기반으로 풀스택 React 앱을 만들어보고 싶다면 RedwoodSDK로 시작해보세요. Vite 기반이라 개발 서버도 빠르고, Cloudflare Workers의 엣지 컴퓨팅 이점도 자연스럽게 누릴 수 있습니다.
This work is licensed under
CC BY 4.0