webstudio-is/webstudio · 라이선스 핵심부 AGPL-3.0-or-later(애니메이션 컴포넌트 패키지만 별도 EULA) · pnpm 모노레포, packages 37개 + apps/builder 1개 · TypeScript/TSX 소스 약 2,100개 파일 · 빌더 자체는 Remix 2 + React 18, 내보낸 사이트는 기본 React Router 7 · DB는 Postgres + Prisma · 최신 커밋 0b192c7(2026-07-24) · 정리 기준일 2026-07-29)
Webstudio는 브라우저에서 웹사이트를 마우스로 조립하는 편집기(Builder)다. 박스를 끌어다 놓고, 오른쪽 패널에서 색·여백·글꼴을 조절하고, 텍스트를 바로 클릭해 고친다. 여기까지는 흔한 "노코드 웹사이트 빌더"와 다르지 않다.
진짜 차이는 "내가 만든 화면이 어디에, 무슨 형태로 저장되는가"에 있다. Webstudio는 화면을 JSX/HTML 소스 코드로 저장하지 않는다. 대신 "이 페이지에는 어떤 요소가 있고(인스턴스 트리), 어떤 스타일이 어느 요소에 붙어있고(스타일 소스), 어떤 값이 들어있는가(프로퍼티)"를 순수 데이터(JSON)로 저장한다. 화면은 이 데이터를 그때그때 읽어서 그린 결과물일 뿐이다.
그리고 이 데이터는 언제든 webstudio CLI로 실제 Remix/React Router 프로젝트 소스 코드로 뽑아낼 수 있다. 뽑아낸 코드는 100% 내 것이라 Vercel·Netlify·Cloudflare·Docker 등 원하는 곳에 직접 배포한다. 회사가 망하거나 요금이 올라도 내 코드와 내 데이터는 항상 손안에 있다는 것이 핵심 약속이다.
많은 웹사이트 빌더는 완성된 그림 파일(JPG)만 준다 — 보기엔 좋지만 그 안의 선 하나, 색 하나를 다시 손대려면 처음부터 다시 그려야 한다. Webstudio는 대신 레이어가 살아있는 포토샵 원본(PSD) 같은 데이터를 준다. 어떤 도형이 어디 있고 어떤 색을 썼는지 전부 구조로 남아있어서, 나중에 언제든 다시 열어 고치고, 필요하면 그 원본을 "그대로 인쇄 가능한 파일(코드)"로 내보낼 수도 있다.
2026년 현재 "AI에게 말로 시켜서 웹사이트/앱을 만드는" 도구(이른바 바이브 코딩)가 쏟아지고 있다. 이 흐름 속에서 Webstudio가 다시 주목받는 이유는 세 가지로 정리된다.
AI 에이전트가 "버튼 색을 좀 더 진하게" 같은 수정을 할 때, 자유 형식 JSX 파일을 통째로 다시 쓰게 하는 것보다 "이 인스턴스의 이 스타일 속성 값을 바꿔라"는 구조화된 명령을 주는 편이 훨씬 안전하고 정확하다. Webstudio는 저장 형식 자체가 이런 구조화된 데이터라서, AI 에이전트용 조작 인터페이스를 자연스럽게 얹을 수 있다. 실제로 저장소에는 webstudio mcp라는, AI 코딩 에이전트가 표준 프로토콜(MCP)로 직접 프로젝트를 조작하고 스크린샷으로 결과를 눈으로 확인하는 기능이 들어있다(5장에서 자세히).
Webflow는 시각적 웹 빌더 시장을 만든 회사지만 완전한 폐쇄형 SaaS다 — 코드를 통째로 내보낼 수 없고(제한적 export만 가능), 요금은 계속 오르고, 데이터는 Webflow 서버에 갇힌다. Webstudio는 같은 사용 경험을 제공하면서 AGPL 오픈소스 + 완전한 코드 내보내기 + 셀프호스팅을 내세운다. "디자이너용 도구인데 개발자가 코드 소유권을 잃지 않는다"는 조합이 팀 협업 시장에서 반응을 얻고 있다.
단순 데모가 아니라 5년 넘게 다듬어진 실제 SaaS 제품(webstudio.is)과 그 오픈소스 코어가 같은 저장소에서 나온다. 37개 패키지로 나뉜 모노레포, 엄격한 패키지 경계 검사 스크립트, CI에서 포맷·린트·타입체크·시각적 회귀 테스트(Chromatic/Lost Pixel)까지 자동화돼 있다 — "장난감이 아니라 유지되는 제품"이라는 신호가 뚜렷하다.
Webflow·Framer는 강력하지만 폐쇄형·구독형·비공개 인프라다. Onlook·Plasmic 같은 오픈소스/코드 우선 도구도 있지만 접근 방식이 조금씩 다르다(10장에서 표로 비교). Webstudio의 차별점은 "Webflow 수준의 완성된 노코드 UX"와 "완전한 코드 소유권"을 동시에 주장한다는 점이다.
전통적으로 웹사이트를 만드는 방법은 둘 중 하나였다. ① 개발자가 코드를 짠다 — 정확하고 유지보수하기 좋지만 느리고, 디자이너·마케터는 문구 하나 못 고친다. ② 노코드 빌더를 쓴다(Webflow, Wix 등) — 누구나 화면을 고칠 수 있지만, 결과물이 그 회사 서버 안에 갇히고(벤더 락인), 진짜 복잡한 로직이 필요해지면 막힌다.
Webstudio는 이 둘을 한 파이프라인으로 잇는다. 비개발자는 화면에서 마우스로 편집하고, 그 결과는 항상 정돈된 데이터로 쌓인다. 개발자가 필요하면 그 데이터를 진짜 코드로 뽑아 자기 저장소에 넣고, 직접 로직을 더 얹거나 리뷰하고, 원하는 인프라에 배포한다. "누가 화면을 고쳤는가"와 "그 결과가 어떤 코드가 되는가"가 완전히 분리되어 있다는 것이 핵심이다.
더 구체적으로는 세 가지 실무 문제를 겨냥한다.
저장소는 pnpm 워크스페이스 모노레포다. 앱은 딱 하나, apps/builder(웹에서 도는 편집기 겸 SaaS 본체)뿐이고, 나머지는 전부 packages/* 아래 37개 패키지로 쪼개져 있다. 역할별로 묶으면 이렇게 보인다.
이 배치가 말해주는 설계 의도는 하나다. sdk 패키지의 zod 스키마가 "데이터가 어떤 모양이어야 하는가"의 유일한 진실원이고, 그 위에 css-engine(스타일→CSS 변환), react-sdk(트리→React), project-build(트리 조작·검증), 여러 sdk-components-*(실제 컴포넌트 구현)가 각자의 일만 한다. 편집기(apps/builder)와 CLI(packages/cli)는 둘 다 이 공용 패키지들을 가져다 쓰는 "소비자"일 뿐이다 — 그래서 편집기에서 만든 데이터를 CLI가 그대로 코드로 구워낼 수 있다.
scripts/check-package-boundaries.mjs가 CI에서 모든 package.json을 훑어, 공개(오픈소스) 패키지가 private:true인 패키지를 런타임 의존성으로 참조하면 빌드를 실패시킨다. 이 규칙 덕분에 뒤에서 볼 "유료 애니메이션 컴포넌트"가 오픈소스 코어에 몰래 스며드는 일이 구조적으로 막힌다.
Webstudio가 화면 하나를 표현하는 데 쓰는 데이터는 크게 다섯 종류다. 실제 스키마(packages/sdk/src/schema/*.ts, zod로 정의)를 그대로 보면 이렇다.
이 다섯 조각이 조합되는 방식이 핵심이다. 인스턴스는 스타일 값을 직접 들고 있지 않는다. 대신 StyleSourceSelection으로 "나는 이 스타일 소스들을 쓴다"고만 가리키고, 실제 색·크기 값은 StyleDecl에 (styleSourceId, breakpointId, state) 조합 키로 따로 저장된다. 그래서 같은 StyleSource(토큰)를 버튼 100개가 같이 쓰다가, 그 토큰의 StyleDecl 값 하나만 바꾸면 100개가 한 번에 바뀐다 — CSS의 클래스 개념을 데이터 레벨에서 그대로 재현한 것이다.
인스턴스는 "이 옷을 입은 사람"이고, 스타일 소스는 "옷 자체"다. 사람(인스턴스)은 "나는 파란 셔츠를 입는다"고 셔츠(스타일 소스)를 가리키기만 한다. 셔츠의 실제 색상·재질 정보(스타일 선언)는 옷장에 따로 걸려있다. 같은 파란 셔츠를 100명이 나눠 입고 있다가, 옷장에서 그 셔츠의 색을 초록으로 바꾸면 100명이 동시에 옷을 갈아입는 셈이다.
DataSource(변수·파라미터·리소스)는 텍스트나 프로퍼티 값에 고정값 대신 표현식을 바인딩할 수 있게 해준다. 예를 들어 버튼의 children에 {type:"expression", value:"고객이름 + \"님 환영합니다\""}처럼 넣으면, 실제 값이 변수에 따라 바뀐다. 이 표현식들은 CLI 내보내기 시점에 실제 JS/JSX 표현식으로 컴파일된다.
편집기 화면은 사실 두 개의 독립된 브라우저 문서로 이루어져 있다. 바깥쪽(빌더 UI, 패널·툴바)과 안쪽(캔버스, 진짜 컴포넌트가 렌더링되는 iframe)이다. 이 둘은 서로 다른 문서라 직접 함수를 호출할 수 없고, postMessage로만 대화한다. Webstudio는 이 통신을 apps/builder/app/shared/pubsub/create.ts의 토큰 검증형 pub/sub 버스로 감싼다.
// apps/builder/app/shared/pubsub/create.ts (요지)
const token = window.self === window.top
? getRandomToken() // 빌더(최상위 창)에서 토큰 생성
: window.top?.[apiTokenKey]; // 캔버스(iframe)는 부모가 심어둔 토큰을 읽음
// 보낼 때: 액션을 { action, token } 으로 감싼다
parentPostMessageInternal(wrapAction(action), "*");
// 받을 때: 토큰이 안 맞으면 그 즉시 버린다
if (payload.token !== token) throw new Error("Invalid token");
왜 굳이 토큰까지 검증할까? 코드 주석에 이유가 그대로 적혀 있다 — 캔버스 iframe 안에는 사용자가 만든 임의의 HTML 임베드·서드파티 스크립트가 돌 수 있는데, 이런 스크립트가 window.postMessage 자체를 가로채 가짜 메시지를 흘려보내는 공격을 막기 위해서다. 무작위 토큰을 CSRF 토큰처럼 매 세션 발급해 "이 메시지가 진짜 신뢰할 수 있는 상대에게서 왔는가"를 확인한다.
화면에 실제로 적용되는 CSS는 @webstudio-is/css-engine이 만든다. 핵심 아이디어는 같은 스타일 선언은 항상 같은 클래스 이름을 갖게 해시로 이름을 짓는 것이다(packages/css-engine/src/core/atomic.ts).
// core/atomic.ts (요지) — 선언 하나마다 해시로 클래스명을 만든다
const atomicHash = hash(
descendantSuffix + declaration.breakpoint +
declaration.selector + declaration.property +
toValue(declaration.value)
);
const className = `c${atomicHash}`; // "c" 접두어 = 클래스명은 항상 문자로 시작
즉 "이 브레이크포인트에서, 이 상태(:hover 등)에서, 이 속성에 이 값"이라는 조합이 완전히 같으면 해시값도 같고, 그래서 생성되는 클래스도 같다. 다른 요소 10개가 똑같이 color: blue를 쓰면 CSS에는 그 규칙이 딱 한 번만 등장하고, 10개의 요소가 같은 클래스 하나를 공유한다 — 이게 바로 아토믹 CSS(atomic CSS)다. 겹치는 스타일 선언은 merger.ts가 같은 브레이크포인트·셀렉터 그룹으로 묶어 최종 한 줄로 병합한다.
마치 도장(스탬프)을 새기는 것과 같다. "파란 배경 + 굵은 글씨"라는 조합을 처음 쓸 때만 그 조합 전용 도장(클래스)을 새기고, 다음에 똑같은 조합이 필요하면 새 도장을 파지 않고 이미 새겨둔 도장을 재사용한다. 그 결과 최종 CSS 파일에는 실제로 쓰인 조합의 수만큼만 규칙이 남고, 중복이 없다.
캔버스에 그려지는 버튼·이미지·폼 같은 컴포넌트는 전부 파일 두 개가 한 쌍으로 존재한다(packages/sdk-components-react/src/). button.tsx는 진짜 React 구현체이고, button.ws.ts는 그 컴포넌트의 "메타 정보"다.
// button.tsx — 실제 렌더링되는 컴포넌트(빌더 캔버스에서도, 내보낸 사이트에서도 동일하게 쓰인다)
export const Button = forwardRef((props, ref) =>
<button type={props.type ?? "submit"} {...props} ref={ref} />
);
// button.ws.ts — 빌더가 참고하는 "설명서"
export const meta: WsComponentMeta = {
presetStyle: { button }, // 처음 드롭했을 때 기본 스타일
initialProps: ["id","class","type","aria-label"], // 패널에 먼저 보여줄 속성
props, // 이 컴포넌트가 받을 수 있는 프로퍼티 전체 목록
};
메타 파일에는 contentModel도 있다 — "이 컴포넌트 안에는 어떤 종류의 자식만 들어갈 수 있는가"를 정의해, 예컨대 <tr> 안에 아무 컴포넌트나 넣지 못하게 막는다. 모든 컴포넌트 패키지(sdk-components-react, -react-radix, -react-remix, -react-router, 비공개 -animation)의 메타는 sdk-components-registry 패키지가 한곳에 모아, 빌더의 "컴포넌트 추가" 패널과 CLI의 코드 생성기가 똑같은 목록을 참조한다.
webstudio CLI(packages/cli)는 link → sync → build 세 단계로 클라우드의 데이터를 로컬 코드로 바꾼다.
webstudio link — 프로젝트 연결빌더의 "Share" 기능으로 만든 인증 링크를 CLI에 붙여넣으면, 이 프로젝트의 인스턴스 트리·스타일·페이지 데이터에 접근할 권한을 로컬에 저장한다.
webstudio sync — 데이터 내려받기클라우드에 저장된 최신 인스턴스 트리·스타일 소스·프로퍼티·데이터 소스 JSON을 로컬로 내려받는다. 이 시점까지는 아직 "데이터"일 뿐, 실행 가능한 코드가 아니다.
webstudio build — 실제 프로젝트로 컴파일내려받은 데이터를 packages/cli/templates/*의 프레임워크 템플릿(기본 React Router 7, 또는 Remix·정적 사이트(SSG) 등)에 얹어, 페이지마다 실제 JSX 파일과 css-engine이 구운 CSS 파일을 만든다. 이렇게 나온 프로젝트는 npm run build·npm run dev가 되는 평범한 Remix/React Router 저장소다.
템플릿은 배포 대상별로 여러 벌 준비돼 있다 — react-router(기본), react-router-vercel·-netlify·-cloudflare·-docker(각 인프라 특화), ssg·ssg-vercel·ssg-netlify(완전 정적 사이트로 미리 렌더링), 그리고 구버전 호환용 defaults(Remix 2 기반)까지. 어떤 인프라에 배포하든 같은 데이터에서 시작한다는 점이 핵심이다.
webstudio mcp와 시각 검증 루프
가장 최근에 추가된, 이 프로젝트가 지금 왜 화제인지를 잘 보여주는 부분이다. webstudio mcp 명령은 MCP(Model Context Protocol) 서버를 표준입출력으로 띄워, Claude Code 같은 AI 코딩 에이전트가 "인스턴스를 옮겨라", "이 위치에 컴포넌트를 삽입해라" 같은 구조화된 도구 호출로 프로젝트를 직접 편집하게 해준다.
// packages/project-build/src/runtime/instances.ts — MCP 도구 인자 스키마의 실제 예
insertIndex: insertIndexInput.optional().describe(
"Zero-based position in the target parent's children before the moved instance is removed. Omit it or use position: end to append."
),
position: instanceEndPositionInput.optional().describe(
'Use "end" to append deterministically without calculating an insertIndex.'
),
이 .describe() 문구들이 바로 AI 에이전트에게 "이 인자가 뭘 뜻하는지" 알려주는 설명이다. 즉 빌더 화면에서 사람이 하는 조작과 AI가 MCP로 하는 조작이 같은 런타임 API(BuilderRuntimeContext)를 공유한다 — 사람 손이든 AI 도구 호출이든 결과 데이터는 똑같은 검증을 통과해야 한다.
더 나아가 내부 문서(packages/cli/src/docs/mcp-vision.md)에는 "시각 검증 루프"까지 설계돼 있다. AI 에이전트가 스타일을 바꾼 뒤 preview.start로 미리보기 서버를 띄우고, screenshot으로 여러 화면 폭의 스크린샷을 찍고, 기준 이미지와 screenshot.diff로 픽셀 차이·OCR 텍스트 변화까지 비교하게 하는 워크플로다. "AI가 스타일을 고쳤다고 주장하는 것"과 "실제로 화면이 그렇게 바뀐 것"을 눈으로 확인시키는 장치다.
DB는 Prisma로 관리되는 Postgres다(packages/prisma-client/prisma/schema.prisma, 375줄). 흥미로운 점은 인스턴스 트리 등이 세부 관계형 테이블이 아니라, Build 테이블의 각 칼럼에 직렬화된 JSON 문자열 통째로 저장된다는 것이다.
// prisma/schema.prisma — Build 모델 (일부)
model Build {
pages String
breakpoints String @default("[]")
styles String @default("[]")
styleSources String @default("[]")
styleSourceSelections String @default("[]")
props String @default("[]")
dataSources String @default("[]")
instances String @default("[]")
...
}
즉 Prisma는 여기서 "연결·마이그레이션·타입 안전성"을 담당할 뿐, 인스턴스 트리 자체의 관계를 SQL 조인으로 표현하지 않는다. 트리 구조의 진짜 검증·조작은 전부 애플리케이션 레이어(project-build 패키지의 zod 스키마와 런타임 함수들)에서 이뤄진다 — "관계형 DB를 문서 저장소처럼 쓰는" 실용적 타협이다.
| 영역 | 선택 | 비고 |
|---|---|---|
| 편집기 프레임워크 | Remix 2.16 | 웹 SaaS 본체(apps/builder)가 직접 쓰는 메타프레임워크. React 18.3(canary 빌드) 사용 |
| 내보낸 사이트 기본값 | React Router 7 | Remix의 후신. CLI 템플릿의 기본 대상이며, 구버전 호환용 Remix 템플릿(defaults)도 남아있다 |
| 상태 관리 | nanostores | Redux/Zustand 대신 가벼운 atom 기반 스토어. 편집기·CLI·컴포넌트 패키지 여럿이 공유 |
| 편집기 UI 스타일링 | Stitches(@stitches/react) | 편집기 자체(패널·툴바)의 CSS-in-JS. 캔버스 안 사용자 콘텐츠 스타일링과는 별개(그건 css-engine) |
| 스키마·검증 | Zod 4 | 인스턴스·스타일·프로퍼티 등 모든 데이터 모델과 MCP 도구 인자 검증에 사용 |
| DB / ORM | Postgres + Prisma 5 | 연결·마이그레이션 담당. 트리 데이터 자체는 JSON 문자열 칼럼(5-7절) |
| REST 계층 | PostgREST(@webstudio-is/postgrest) | Postgres 위에 REST API를 자동 생성해주는 계층을 감싼 내부 패키지 |
| 포맷·린트 | oxfmt · oxlint | Rust로 짜인 차세대 고속 포맷터·린터(oxc 프로젝트 계열). Prettier/ESLint 대체 |
| 테스트 | Vitest(단위) · Playwright(E2E) | pnpm -r test로 전 패키지 단위 테스트, apps/builder/e2e에 별도 브라우저 E2E |
| 시각 회귀 | Storybook + Chromatic / Lost Pixel | 컴포넌트 스토리 기준 UI 스크린샷 비교로 의도치 않은 화면 변화를 잡음 |
| 패키지 매니저 | pnpm 9.14 워크스페이스 | Node 22 고정(.nvmrc, engines.node) |
라이선스 구조도 스택의 일부로 봐야 한다. 저장소 안의 모든 기능은 AGPL-3.0-or-later다(README에 명시). 단 하나 예외는 packages/sdk-components-animation인데, 이는 git submodule로 연결된 별도 비공개 저장소이고 Webstudio, Inc.의 EULA(최종사용자 라이선스 계약)에 동의해야 쓸 수 있는 유료 애니메이션 컴포넌트다. 즉 "오픈소스 코어 + 선택적 유료 부속품"이 한 모노레포 안에 공존하되, 4장의 패키지 경계 검사로 서로 섞이지 않게 막아둔 구조다.
| 항목 | 요구사항 |
|---|---|
| Node.js | 정확히 22(.nvmrc=22, engines.node="22"). 다른 버전은 워크스페이스 스크립트가 어긋날 수 있다 |
| 패키지 매니저 | pnpm 9.14.x 고정(packageManager 필드). "yarn은 지원하지 않는다"는 안내가 engines에 박혀 있다 |
| 저장소 클론 | 서브모듈(sdk-components-animation)은 비공개 저장소라 일반 클론으로는 내용이 비어있다. ./submodules.sh가 있지만 접근 권한이 없으면 실패한다 |
| 로컬 편집기 실행 | pnpm install → pnpm dev(내부적으로 apps/builder/dev/run-local.sh 실행). Postgres 연결 문자열(DATABASE_URL) 등 환경변수 필요 |
| CLI만 쓰기 | 웹 앱 전체를 안 띄우고 내보내기만 하려면 npm install -g webstudio로 CLI만 설치해도 된다(별도 Node 22 권장) |
| CI 환경 | GitHub Actions, ubuntu-24.04-arm 러너에서 포맷·린트·타입체크·패키지 경계·생성 문서 검사·Vitest·Playwright를 전부 수행 |
· 유료 애니메이션 컴포넌트 서브모듈은 접근 권한이 있는 사람만 내려받을 수 있다. 공개 저장소만 클론하면 이 부분은 비어있는 채로 남는다 — 하지만 오픈소스 코어 기능에는 영향이 없다(패키지 경계 규칙 덕분에 의존이 분리돼 있음).
· 편집기(apps/builder) 전체를 로컬에서 띄우려면 Postgres·인증(OAuth) 설정 등이 필요해 진입장벽이 있다. 스키마·CSS 엔진·컴포넌트 메타 같은 핵심 설계를 공부하는 목적이라면 packages/sdk, packages/css-engine만 읽어도 충분하다.
① webstudio.is(또는 셀프호스팅한 빌더)에서 화면을 마우스로 조립한다. 텍스트·이미지·컴포넌트를 끌어다 놓고, 스타일 패널로 색·간격을 조절한다. 저장은 자동으로 인스턴스 트리 데이터로 쌓인다.
② 배포 준비가 되면 프로젝트에서 "Share" 링크를 만든다.
③ 로컬(또는 CI)에서 webstudio link(링크 붙여넣기) → webstudio sync(데이터 내려받기) → webstudio build(React Router/Remix 프로젝트로 컴파일)를 실행한다.
④ 결과물은 평범한 프론트엔드 저장소이므로 그대로 Vercel·Netlify·Cloudflare Pages·Docker 등 원하는 곳에 배포한다.
① 로컬에서 webstudio mcp로 MCP 서버를 띄우고, Claude Code 같은 AI 코딩 에이전트를 그 서버에 연결한다.
② 사람이 "히어로 섹션 배경을 더 어둡게 하고 버튼을 크게 해줘" 같은 자연어 지시를 내리면, 에이전트가 MCP 도구(인스턴스 이동·스타일 값 변경 등)를 구조화된 호출로 실행한다.
③ 에이전트는 preview.start로 미리보기를 띄우고 screenshot으로 여러 화면 폭을 캡처해, 실제로 의도한 대로 바뀌었는지 스스로 확인한 뒤 마무리한다.
④ 이후 흐름은 A와 동일하게 sync·build·배포로 이어진다.
블로그·소개 페이지처럼 서버 로직이 필요 없으면 ssg·ssg-vercel·ssg-netlify 템플릿으로 빌드해, 완전히 정적인 HTML/CSS/JS 묶음만 내보낸다. 이 경우 배포 대상은 그냥 정적 파일 호스팅이면 충분하다.
저장소 안 거의 모든 package.json의 version 필드는 "0.0.0" 또는 "0.0.0-webstudio-version"처럼 플레이스홀더다. 진짜 배포 버전은 local:version-snapshot 스크립트가 배포 직전에 임의의 프리패치 버전을 채워 넣고 레지스트리에 publish한 뒤, local:dangerously-undo-version-snapshot으로 다시 되돌리는 방식으로 관리된다. 릴리스 자체도 날짜 기반 브랜치(release-DD-MM-YYYY.staging)로 만들어진다. "이 저장소의 버전이 몇이냐"는 질문에 package.json만 봐서는 답이 안 나온다 — npm 레지스트리나 GitHub 릴리스를 봐야 한다.
편집기 자체(apps/builder, 즉 webstudio.is가 돌아가는 그 SaaS 앱)는 지금도 Remix 2.16 + React 18로 짜여 있다. 하지만 CLI로 내보낸(export한) 사이트는 기본적으로 React Router 7(Remix의 후신) 템플릿을 쓴다. "Webstudio가 무슨 프레임워크냐"고 물으면 "편집기냐, 내가 받는 결과물이냐"에 따라 답이 다르다는 점을 헷갈리기 쉽다.
Build 테이블의 instances·styles·props 등은 전부 직렬화된 JSON 문자열 칼럼이다(5-7절). Prisma가 있다고 해서 "인스턴스마다 행이 있고 부모-자식 관계가 외래키로 걸려있을 것"이라 짐작하면 소스를 읽을 때 헤매게 된다. 실제 트리 무결성 검증은 애플리케이션 코드(zod 스키마 + project-build의 런타임 함수)가 담당한다.
sdk-components-animation은 git submodule로 연결된 비공개 저장소다. 접근 권한 없이 클론하면 해당 폴더가 텅 비어 있거나 체크아웃이 실패하는데, 이는 저장소 자체의 결함이 아니라 "유료 부속 컴포넌트는 원래 공개돼 있지 않다"는 설계다. CI도 이 부분은 시크릿이 있을 때만 체크아웃한다(.github/actions/submodules-checkout).
"핵심부는 AGPL, 애니메이션 컴포넌트만 별도 EULA"라는 README 문구는 실제 LICENSE 파일(AGPL 전문)과 서브모듈 구조·패키지 경계 검사 스크립트로 실제 코드에서도 그대로 확인된다. 겉으로만 오픈소스라고 홍보하고 실제로는 핵심 기능을 잠가둔 "오픈코어 눈속임"과는 다르다.
| 도구 | 소스코드 소유권 | 셀프호스팅 | 편집 대상 |
|---|---|---|---|
| Webstudio | 완전 내보내기(Remix/React Router 코드) + AGPL 오픈소스 | 가능(편집기·내보낸 사이트 모두) | 인스턴스 트리(데이터) → 코드로 컴파일 |
| Webflow | 제한적 내보내기(정적 HTML/CSS만, 로직 없음) | 불가(완전 SaaS) | Webflow 자체 포맷, 비공개 |
| Framer | 내보내기 매우 제한적 | 불가(완전 SaaS) | Framer 자체 포맷, 비공개 |
| Onlook | 실제 React/Next.js 소스 코드를 직접 편집(오픈소스) | 가능 | 기존 코드베이스 자체를 비주얼로 열어 수정 |
| Plasmic | 여러 프레임워크로 코드 내보내기 지원(일부 오픈소스) | 부분 가능 | 독자적인 컴포넌트 트리 데이터 |
가장 중요한 축은 "편집 대상이 코드냐 데이터냐"다. Onlook은 이미 존재하는 React 코드베이스를 열어 그 자리에서 시각적으로 고치는 쪽(코드 우선)이고, Webstudio는 애초에 코드가 아닌 데이터로 저장했다가 필요할 때 코드를 생성해내는 쪽(데이터 우선)이다. 데이터 우선 쪽은 "비개발자가 안전하게 편집"하기 유리하고, 코드 우선 쪽은 "이미 복잡한 기존 코드베이스에 손대기" 유리하다. Webflow·Framer는 이 축에서 아예 코드를 온전히 내주지 않는다는 점이 근본적으로 다르다.
Webflow·Framer가 "완성된 요리만 파는 레스토랑"이라면, Webstudio는 "손질된 재료(데이터)와 레시피(스키마)를 함께 주는 밀키트"에 가깝다. 요리는 대신 해주지만, 재료 목록과 조리법을 그대로 가져가 내 주방(내 서버)에서 다시 만들 수 있다. Onlook은 반대로 "이미 완성된 남의 요리를 가져와서, 그 요리 자체를 계속 고쳐 먹는" 쪽에 가깝다.
Webstudio를 한 문장으로 다시 정리하면: 편집 결과를 코드가 아니라 데이터로 저장해두고, 필요할 때 실제 Remix/React Router 코드로 컴파일해 어디에나 배포할 수 있는 오픈소스 비주얼 웹 빌더다. 이 구조 덕분에 "비개발자가 화면을 안전하게 고치는 편의성"과 "개발자가 코드 소유권을 잃지 않는 통제력"을 동시에 얻는다. 최근에는 여기에 AI 에이전트가 MCP로 직접 편집하고 스크린샷으로 검증하는 기능까지 더해지며, "AI가 화면을 만드는 시대의 백엔드"로서의 성격도 강해지고 있다.