RedwoodJS 팀이 GraphQL을 버리고 Cloudflare에 올인한 이유

RedwoodSDK

RedwoodJS 기억하시나요? React, GraphQL, Prisma를 한 묶음으로 엮어서 풀스택 앱을 뚝딱 만들어주던 프레임워크. 꽤 좋았어요, 진짜로. 그런데 그 팀이 2025년 3월에 v1.0으로 내놓은 건 사실상 완전히 다른 물건이에요. 이름은 RedwoodSDK(패키지명 `rwsdk`). GraphQL? 없어졌어요. Prisma? 걷어냈어요. Node.js 배포? 안 해요. Cloudflare Workers 전용이에요.

"후속작"이라고 부르기엔 바뀐 게 너무 많아요. 아키텍처 자체를 React Server Components 기반으로 갈아엎었고, 배포 대상도 Cloudflare 한 곳으로 좁혔어요. 이전 RedwoodJS가 API/Web 분리에 GraphQL 중심이었다면, RedwoodSDK는 RSC 기반 단일 엔트리에 D1(SQLite) 직접 페칭이에요. ORM도 Prisma에서 Kysely로 바뀌었고, 빌드 도구는 Webpack에서 Vite로. 거의 모든 항목이 달라졌어요.

근데 가장 본질적인 차이는 철학이에요.

"Zero Magic" — 코드 생성기를 꺼버린 프레임워크

RedwoodSDK는 세 가지 원칙을 내세워요. 첫째가 Zero Magic. "작성한 코드가 곧 실행되는 코드"라는 건데, 기존 RedwoodJS를 써봤다면 이게 왜 큰 얘기인지 바로 감이 올 거예요. Generator가 보일러플레이트를 자동 생성하고, 파일 이름 규칙에 따라 동작이 바뀌고, 내부 트랜스파일이 슬쩍슬쩍 일어나던 게 전부 사라졌거든요. 코드 생성도 없고, 파일 이름 규칙도 없고, 눈에 안 보이는 트랜스파일도 없어요.

둘째는 Composability Over Configuration. 설정 파일로 동작을 바꾸는 대신 작은 함수들을 조합하는 방식이에요. 미들웨어, 라우트 핸들러, 인터럽터 — 전부 단순한 함수이고, 배열로 나열하면 그게 앱의 동작이 돼요.

셋째가 Web-First Architecture. `fetch`, `Request`, `Response`, `URL` 같은 웹 표준 API를 그대로 써요. 프레임워크 고유의 래퍼 같은 거 없어요. 브라우저에서 쓰는 그 API 맞아요.

이 세 가지를 관통하는 메시지는 분명해요. "프레임워크가 대신 결정해주던 시대는 끝났다. 개발자한테 선택권을 돌려주겠다." (꽤 과격한 선언이죠.)

Cloudflare 올인 — D1, R2, Durable Objects까지 프레임워크 수준에서

플랫폼 종속이라는 단어가 살짝 불편할 수 있어요. 근데 RedwoodSDK가 Cloudflare에 올인한 덕분에 얻는 것도 꽤 커요.

D1(데이터베이스), R2(파일 스토리지), Durable Objects(실시간), KV(키-값 저장소), Queues(메시지 큐) — 이런 Cloudflare 서비스를 프레임워크 수준에서 자연스럽게 쓸 수 있어요. 별도의 어댑터나 플러그인 없이요. 로컬 개발 서버도 Miniflare가 Cloudflare Workers 런타임을 에뮬레이션해주니까, D1이든 R2든 Durable Objects든 로컬에서 바로 테스트 가능하고요.

프로젝트 구조도 깔끔해졌어요. 기존 RedwoodJS가 `api/`와 `web/` 폴더를 분리했던 것과 달리, RedwoodSDK는 단일 `src/` 폴더 안에 모든 코드가 들어가요. React Server Components 덕분에 서버 코드와 클라이언트 코드가 같은 공간에 공존할 수 있으니까요.

프로젝트 생성은 `npx create-rwsdk my-app` 한 줄이면 되고, 개발 서버는 `http://localhost:5173`에서 시작돼요.

라우팅과 인터럽터 — 배열 순서가 곧 실행 순서

라우팅 시스템이 재밌어요. 파일 기반 라우팅이 아니라 코드로 직접 정의하는 방식인데, 앱의 핵심이 `src/worker.tsx` 파일 하나에요.

```typescript 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` 문법), 와일드카드 패턴, HTTP 메서드별 핸들러 전부 지원하고요. 여기서 Request와 Response가 웹 표준 API라는 점이 중요해요. 프레임워크 고유의 요청/응답 객체가 아니라 브라우저에서 쓰는 그 fetch API의 객체 맞거든요.

미들웨어는 "인터럽터"라고 부르는데, 동작 원리가 심플해요. `defineApp()` 배열에 함수를 넣으면 그게 미들웨어. 함수가 `Response`를 반환하면 파이프라인이 거기서 멈추고, `undefined`를 반환하면 다음 단계로 넘어가요. 끝.

```typescript export default defineApp([ render(Document, [ route("/", HomePage), // 인증 불필요 route("/about", AboutPage), // 인증 불필요 requireAuth, // 여기서부터 인증 필요 route("/dashboard", Dashboard), route("/settings", Settings), ]), ]); ```

배열에서의 순서가 곧 실행 순서. `requireAuth` 아래에 있는 라우트만 인증이 적용되니까, 설정 파일에서 패턴 매칭하는 것보다 훨씬 직관적이죠. (솔직히 이런 단순한 설계가 가장 와닿더라고요.)

GraphQL 계층이 사라지고 남은 것 — RSC + Kysely

데이터 페칭은 React Server Components 기반이에요. 서버 컴포넌트는 서버에서 실행되니까 데이터베이스에 직접 접근할 수 있거든요. 기존 RedwoodJS의 Cell 패턴 — GraphQL 쿼리 보내고 로딩/에러/성공 상태 선언적으로 처리하던 것 — 이 전부 사라졌어요. 그냥 async 컴포넌트에서 DB를 바로 조회하면 돼요. 로딩 상태는 React의 `Suspense`로 처리하고요.

데이터 변경은 `serverQuery`(GET 조회)와 `serverAction`(POST 변경) 두 가지로 나뉘어요. `serverAction`이 실행되면 페이지가 자동으로 다시 렌더링되어 변경사항이 반영돼요.

데이터베이스 쪽도 바뀌었어요. Cloudflare D1 — 엣지에서 돌아가는 SQLite 기반 DB — 을 쓰고, ORM은 Kysely라는 경량 쿼리 빌더예요. Prisma가 자체 스키마 언어와 마이그레이션 시스템까지 갖춘 풀 세트였다면, Kysely는 SQL 문법을 거의 그대로 TypeScript로 옮겨놓은 거에 가까워요.

```typescript // 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(); ```

SQL 아는 사람이면 Kysely 쪽이 훨씬 익숙할 거예요. TypeScript 타입 안전성은 제공하면서도 SQL에 가까운 문법을 유지하니까요. "Zero Magic" 철학과 잘 맞는 선택이에요.

타입 안전 라우팅, Passkey 인증, 그리고 배포 한 줄

자잘하지만 쓸모 있는 기능도 있어요. `linkFor()` 함수로 타입 안전한 링크를 생성할 수 있는데, 라우트 정의에서 타입 정보를 추출하기 때문에 잘못된 파라미터는 컴파일 타임에 잡아줘요. `link("/users/:id", { name: "dale" })` 같은 건 TypeScript 에러.

인증은 Passkey 기반 애드온을 제공해요. 비밀번호 없이 생체 인증이나 보안 키로 로그인하는 방식이죠. 세션 관리가 필요하면 Cloudflare Durable Objects를 활용할 수 있어서, Redis 같은 별도 세션 스토어를 셋업할 필요가 없어요. Cloudflare 인프라 안에서 다 해결되는 구조.

배포는요? `npm run release` 한 줄이에요. Wrangler CLI가 빌드부터 업로드까지 전부 자동으로 처리하고, 전 세계 300개 이상의 엣지 로케이션에서 바로 실행돼요. CI/CD 파이프라인에 넣기에도 부담 없는 단순한 구조고요.

Cloudflare 종속이라는 트레이드오프

솔직히 말하면 이건 양면이 있어요. 기존 RedwoodJS는 어디든 배포할 수 있었어요. Vercel, Netlify, 자체 서버 — 상관없었죠. RedwoodSDK는 Cloudflare Workers 전용이에요. 플랫폼 종속.

근데 그 종속 덕분에 D1, R2, Durable Objects, KV, Queues 같은 서비스를 프레임워크 수준에서 자연스럽게 쓸 수 있게 된 거기도 하고요. 서버리스 엣지 컴퓨팅의 성능 이점을 살리면서 RSC로 깔끔한 풀스택 개발 경험을 얻을 수 있다는 건 분명한 장점이에요.

RedwoodSDK는 RedwoodJS 팀이 "프레임워크를 처음부터 다시 만든다면?"이라는 질문에 내놓은 답이에요. GraphQL 계층을 걷어내고 RSC를 도입하고 Cloudflare에 올인한 결과물. 웹 표준 API에 기반한 설계도, Vite 기반이라 빠른 개발 서버도, 300개 이상 엣지 로케이션 배포도 — 다 좋아요.

다만 Node.js 호스팅이 필요하거나 GraphQL 기반 아키텍처를 선호한다면? 기존 RedwoodJS가 여전히 유효해요. 둘 다 같은 팀이 만들었지만, 완전히 다른 베팅을 한 거예요.