Renderify — LLM이 생성한 JSX를 빌드 없이 브라우저에서 바로 실행하는 런타임 UI 엔진
LLM이 UI 코드를 생성하는 흐름은 이제 낯설지 않다. 그런데 그 코드를 실제로 실행하려면 항상 같은 장벽이 기다린다. Webpack을 돌리거나, Vite 서버를 띄우거나, 최소한 Node 환경에서 빌드 파이프라인을 한 번 거쳐야 한다. Renderify는 그 장벽을 없애는 데 초점을 맞춘 오픈소스 라이브러리다. LLM이 만든 JSX/TSX를 서버 컴파일 없이 브라우저에서 직접 실행한다.
Renderify가 뭔가
LLM 생성 코드를 위한 브라우저 기반 런타임 UI 엔진. 빌드 단계 없이 JSX/TSX를 즉시 실행한다.
비유하면 즉석요리 자판기 같은 구조다. 재료(JSX 코드)를 넣으면 안에서 조리(트랜스파일 + 모듈 해석)가 일어나고, 완성된 결과물(렌더링된 UI)이 바로 나온다. 주방(빌드 서버)을 따로 차릴 필요가 없다.
내부에서 핵심을 담당하는 두 도구가 있다.
| 역할 | 도구 | 설명 |
|---|---|---|
| 트랜스파일 | @babel/standalone | JSX/TSX를 브라우저가 이해하는 JS로 변환 |
| 모듈 해석 | JSPM CDN | import { format } from "date-fns/format" 같은 bare import를 CDN URL로 자동 해석 |
[💡 잠깐! 이 용어는?]
bare import: "date-fns/format" 처럼 경로가 아닌 패키지 이름으로 쓰는 import 구문. 브라우저는 기본적으로 이를 해석하지 못하기 때문에 번들러나 import map이 필요하다.
기존 방식과 뭐가 다른가
LLM이 JSX를 생성했을 때 기존에 선택할 수 있는 경로는 크게 세 가지다.
| 방식 | 실행 환경 | 필요한 준비 | 단점 |
|---|---|---|---|
| Webpack/Vite 빌드 | 서버 | 빌드 파이프라인 전체 | 레이턴시, 인프라 |
| Sandpack / CodeSandbox | iframe 샌드박스 | 별도 서비스 연동 | 외부 의존성 |
| eval() 직접 실행 | 브라우저 | 없음 | 보안 위협 |
| Renderify | 브라우저 | 없음 | — |
기존 zero-build 접근법의 가장 큰 문제는 보안이었다. eval로 임의 코드를 실행하면 XSS, 무한 루프, 악성 모듈 로드 등 다양한 공격 경로가 열린다. Renderify는 이 문제를 보안 정책 레이어로 해결한다.
보안 모델
Renderify는 코드를 실행하기 전에 정책 검사를 먼저 수행한다. 3가지 프로파일이 사전 정의되어 있다.
| 프로파일 | 용도 | 허용 범위 |
|---|---|---|
strict | 신뢰할 수 없는 LLM 출력 | 제한적 태그, 화이트리스트 모듈만 |
balanced | 일반적인 챗봇/어시스턴트 UI | 적절한 수준의 태그와 모듈 |
relaxed | 신뢰된 내부 도구 | 대부분 허용 |
[💡 잠깐! 이 용어는?] ShadowRealm: TC39 제안으로 표준화된 JavaScript 격리 환경. 별도의 전역 객체를 가지는 JS 실행 공간으로, 메인 컨텍스트와 격리된 코드 실행이 가능하다.
보안 정책에는 4가지 제어 축이 있다.
- blocked tags:
<script>,<iframe>등 위험한 HTML 태그 차단 - module allowlists: 허용된 패키지 목록 외 import 차단
- tree depth limits: 컴포넌트 트리의 최대 깊이 제한
- execution budgets: 실행 시간 및 메모리 예산 제한
import { renderPlanInBrowser } from "renderify";
await renderPlanInBrowser(plan, {
target: "#mount",
security: {
profile: "balanced",
moduleAllowlist: ["react", "date-fns", "lodash"],
blockedTags: ["script", "iframe", "object"],
maxTreeDepth: 20,
executionBudget: { timeMs: 3000, memoryMB: 50 }
}
});입력 형식: 두 가지 경로
Renderify는 두 가지 입력 방식을 지원한다.
RuntimePlan (JSON)
LLM이 구조화된 JSON 형태로 UI 명세를 전달하는 방식이다. 코드가 아닌 데이터 구조로 컴포넌트 트리를 정의하고, source 필드에 실제 실행할 TSX 코드를 포함한다.
import { renderPlanInBrowser } from "renderify";
await renderPlanInBrowser({
id: "demo",
version: 1,
root: { type: "text", value: "Loading..." },
source: {
language: "tsx",
code: `import { format } from "date-fns/format";
export default () => <section>Today: {format(new Date(), "yyyy-MM-dd")}</section>;`
}
}, { target: "#mount" });[💡 잠깐! 이 용어는?]
RuntimePlan: Renderify의 IR(Intermediate Representation) 형식. LLM이 생성한 코드와 메타데이터를 구조화하여 전달하는 JSON 스키마로, @renderify/ir 패키지가 담당한다.
Raw TSX/JSX
JSON 없이 TSX 코드 문자열을 직접 전달하는 방식이다. LLM 출력을 그대로 파이프할 때 편리하다.
import { renderTSXInBrowser } from "renderify";
const tsxCode = `
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
`;
await renderTSXInBrowser(tsxCode, { target: "#mount" });스트리밍 렌더링
LLM은 코드를 한 번에 내놓지 않는다. 토큰 단위로 스트리밍한다. Renderify의 renderPromptStream은 이 스트림을 받아 코드가 완성되기 전부터 점진적으로 프리뷰를 렌더링한다. 비유하면 타이핑하는 동안 실시간으로 조판이 완성되는 워드프로세서 같다.
import { renderPromptStream } from "renderify";
const stream = getLLMStream(prompt);
await renderPromptStream(stream, {
target: "#preview",
onPartialRender: (partial) => {
console.log("partial UI available:", partial.isValid);
},
onComplete: (finalPlan) => {
console.log("rendering complete");
}
});채팅 UI, 코드 어시스턴트, 실시간 프리뷰 에디터 같은 케이스에서 UX 차이가 크다. 스트림이 끝나길 기다렸다가 한 번에 렌더링하면 체감 레이턴시가 높아지지만, 스트리밍 렌더로 점진적으로 보여주면 사용자가 결과를 더 빨리 확인한다.
모듈 해석: Auto-Pin-Latest
bare import를 처리하는 방식이 흥미롭다. 개발 중에는 autoPinLatestModuleManifest: true로 설정하면, Renderify가 JSPM을 통해 각 패키지의 최신 버전을 자동으로 해석하고 moduleManifest에 pinned 버전을 기록한다.
import { renderPlanInBrowser } from "renderify";
// 개발 환경: 자동으로 최신 버전 해석 + 기록
await renderPlanInBrowser(plan, {
target: "#mount",
moduleResolution: {
autoPinLatestModuleManifest: true
}
});
// 프로덕션 환경: 사전 확정된 버전만 사용
await renderPlanInBrowser(plan, {
target: "#mount",
moduleResolution: {
autoPinLatestModuleManifest: false,
moduleManifest: {
"date-fns": "https://cdn.jspm.io/npm:date-fns@3.6.0/+esm",
"react": "https://cdn.jspm.io/npm:react@18.3.1/+esm"
}
}
});프로덕션에서 autoPinLatestModuleManifest: false로 설정하는 것은 중요하다. CDN에서 실시간으로 최신 버전을 가져오다가 breaking change가 있는 버전이 올라오면 배포 없이 런타임에서 오류가 터진다. 버전을 명시적으로 pin하면 그 위험을 막는다.
훅 시스템
Renderify는 실행 파이프라인에 10개의 훅 포인트를 제공한다. 주요 훅을 보면 전체 흐름이 보인다.
| 훅 | 실행 시점 | 용도 |
|---|---|---|
beforeLLM | LLM 호출 직전 | 프롬프트 수정, 컨텍스트 주입 |
afterCodeGen | LLM 코드 생성 직후 | 코드 검증, 후처리 |
beforeRender | 렌더링 직전 | 보안 정책 추가 적용 |
onError | 실행 오류 시 | 폴백 UI 제공 |
onComplete | 렌더링 완료 시 | 분석, 로깅 |
import { renderPlanInBrowser } from "renderify";
await renderPlanInBrowser(plan, {
target: "#mount",
hooks: {
afterCodeGen: async (code) => {
const sanitized = await sanitizeCode(code);
return sanitized;
},
beforeRender: (plan) => {
console.log("about to render:", plan.id);
return plan;
},
onError: (error) => {
return { fallback: <div>렌더링 실패: {error.message}</div> };
}
}
});패키지 구조
Renderify는 관심사별로 분리된 monorepo 구조다.
| 패키지 | 역할 |
|---|---|
renderify | 메인 SDK. 대부분의 진입점 |
@renderify/ir | RuntimePlan JSON 스키마 정의 |
@renderify/runtime | 브라우저 트랜스파일 + 실행 엔진 |
@renderify/security | 보안 정책 검사 모듈 |
@renderify/llm | LLM 스트리밍 연동 유틸리티 |
@renderify/cli | CLI 도구 (개발/테스트용) |
메인 SDK만 설치해도 대부분 동작하지만, 보안 정책을 커스터마이징하거나 LLM 스트림 처리를 세밀하게 제어하려면 개별 패키지를 직접 사용한다.
고려할 점
zero-build 실행이 항상 최선은 아니다. Renderify가 잘 맞는 케이스와 주의할 케이스가 있다.
| 상황 | Renderify 적합도 | 이유 |
|---|---|---|
| LLM 채팅 UI 프리뷰 | 높음 | 실시간 렌더링이 핵심 |
| 내부 개발 도구 | 높음 | 빌드 인프라 없이 빠른 프로토타이핑 |
| 사용자 제공 코드 실행 | 중간 | 보안 프로파일 신중히 선택 |
| 프로덕션 SaaS 핵심 경로 | 낮음 | CDN 의존성, 런타임 트랜스파일 성능 |
| 대규모 컴포넌트 트리 | 낮음 | 브라우저 트랜스파일 오버헤드 |
ShadowRealm sandbox mode는 격리 수준을 높여주지만, 브라우저 지원이 아직 완전하지 않다. 프로덕션 배포 전에 타겟 브라우저의 ShadowRealm 지원 여부를 확인해야 한다.
정리
- Renderify는
@babel/standalone+ JSPM CDN으로 빌드 서버 없이 브라우저에서 JSX/TSX를 실행한다 strict,balanced,relaxed3가지 보안 프로파일로 LLM 생성 코드의 실행 범위를 제어한다renderPromptStream으로 LLM 스트림을 받아 점진적 프리뷰 렌더링이 가능하다- 프로덕션 환경에서는
autoPinLatestModuleManifest: false+ 사전 pinned manifest를 사용해야 안전하다 - 10개 훅 포인트로 코드 생성부터 렌더링 완료까지 파이프라인 전체를 제어할 수 있다
- LLM 채팅 UI, 내부 개발 도구, 실시간 프리뷰 에디터에 적합하고, 프로덕션 핵심 경로에는 신중히 판단해야 한다
참고:
- Renderify GitHub: https://github.com/renderify/renderify
- JSPM CDN: https://jspm.org/
- @babel/standalone: https://babeljs.io/docs/babel-standalone
- ShadowRealm TC39 Proposal: https://github.com/tc39/proposal-shadowrealm
관심 있을 만한 포스트
신입사원을 에이스로 — Netflix가 LLM Post-Training을 대규모 엔지니어링으로 만든 과정
Pre-training이 LLM에 넓은 언어 능력을 주지만, post-training이 실제 의도와 도메인 제약에 맞추는 단계. Netflix의 스케일링 접근법.
AI 코딩의 맹점 — Artifacts 없이 에이전트는 기억을 잃는다
PRD, ADR, TDD가 AI 코딩 워크플로우에서 왜 선택이 아닌 필수인지, 실전 구조와 함께 살펴본다.
Next-Translate 3.0 — Turbopack과 App Router를 위한 i18n 재건
1년간 공백 후 돌아온 Next-Translate 3.0이 Turbopack 지원, 비동기 params, App Router 안정화를 한 번에 처리하는 방법.
V8 WasmGC 투기적 최적화 — 가상 메서드를 인라인으로 만드는 법
V8이 WasmGC의 가상 메서드 디스패치에 투기적 인라이닝을 도입해 Dart와 Java 앱에서 최대 8% 성능을 끌어낸 방법.
Vinext — Vite 위에서 Next.js를 1주일 만에 다시 만든 이야기
Cloudflare가 AI와 함께 단 일주일, $1,100의 API 비용으로 Next.js 호환 프레임워크를 Vite 위에 구축한 과정.
Tsonic — TypeScript를 네이티브 바이너리로 컴파일하는 실험
TypeScript → C# → NativeAOT 파이프라인으로 네이티브 실행 파일을 만드는 Tsonic. 어떻게 동작하고, 어떤 한계가 있는지 살펴봤다.
VS Code 팀의 AI 에이전트 병렬화 — 월간 릴리스를 주간으로 만든 워크플로우
VS Code 팀이 월간 릴리스에서 주간 릴리스로 전환한 비결. 에이전트 세션 병렬화, 자동화 파이프라인, 품질 게이트 설계 전반을 공개했다.
React Compiler의 한계 — 뭘 최적화하고 뭘 못 하는가
React Compiler가 자동 메모이제이션으로 해결하는 것과 해결하지 못하는 것. 컴파일러 기반 UI 프레임워크의 능력 경계를 정리했다.