Renderify — LLM이 생성한 JSX를 빌드 없이 브라우저에서 바로 실행하는 런타임 UI 엔진

12 min read
RenderifyLLMJSX브라우저 런타임zero-build
Renderify — LLM이 생성한 JSX를 빌드 없이 브라우저에서 바로 실행하는 런타임 UI 엔진

LLM이 UI 코드를 생성하는 흐름은 이제 낯설지 않다. 그런데 그 코드를 실제로 실행하려면 항상 같은 장벽이 기다린다. Webpack을 돌리거나, Vite 서버를 띄우거나, 최소한 Node 환경에서 빌드 파이프라인을 한 번 거쳐야 한다. Renderify는 그 장벽을 없애는 데 초점을 맞춘 오픈소스 라이브러리다. LLM이 만든 JSX/TSX를 서버 컴파일 없이 브라우저에서 직접 실행한다.


Renderify가 뭔가

LLM 생성 코드를 위한 브라우저 기반 런타임 UI 엔진. 빌드 단계 없이 JSX/TSX를 즉시 실행한다.

비유하면 즉석요리 자판기 같은 구조다. 재료(JSX 코드)를 넣으면 안에서 조리(트랜스파일 + 모듈 해석)가 일어나고, 완성된 결과물(렌더링된 UI)이 바로 나온다. 주방(빌드 서버)을 따로 차릴 필요가 없다.

내부에서 핵심을 담당하는 두 도구가 있다.

역할도구설명
트랜스파일@babel/standaloneJSX/TSX를 브라우저가 이해하는 JS로 변환
모듈 해석JSPM CDNimport { format } from "date-fns/format" 같은 bare import를 CDN URL로 자동 해석

[💡 잠깐! 이 용어는?] bare import: "date-fns/format" 처럼 경로가 아닌 패키지 이름으로 쓰는 import 구문. 브라우저는 기본적으로 이를 해석하지 못하기 때문에 번들러나 import map이 필요하다.


기존 방식과 뭐가 다른가

LLM이 JSX를 생성했을 때 기존에 선택할 수 있는 경로는 크게 세 가지다.

방식실행 환경필요한 준비단점
Webpack/Vite 빌드서버빌드 파이프라인 전체레이턴시, 인프라
Sandpack / CodeSandboxiframe 샌드박스별도 서비스 연동외부 의존성
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: 실행 시간 및 메모리 예산 제한
src/renderify-config.ts
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 코드를 포함한다.

src/runtime-plan-example.ts
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 출력을 그대로 파이프할 때 편리하다.

src/raw-tsx-example.ts
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은 이 스트림을 받아 코드가 완성되기 전부터 점진적으로 프리뷰를 렌더링한다. 비유하면 타이핑하는 동안 실시간으로 조판이 완성되는 워드프로세서 같다.

src/streaming-render.ts
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 버전을 기록한다.

src/module-manifest-example.ts
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개의 훅 포인트를 제공한다. 주요 훅을 보면 전체 흐름이 보인다.

실행 시점용도
beforeLLMLLM 호출 직전프롬프트 수정, 컨텍스트 주입
afterCodeGenLLM 코드 생성 직후코드 검증, 후처리
beforeRender렌더링 직전보안 정책 추가 적용
onError실행 오류 시폴백 UI 제공
onComplete렌더링 완료 시분석, 로깅
src/hooks-example.ts
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/irRuntimePlan JSON 스키마 정의
@renderify/runtime브라우저 트랜스파일 + 실행 엔진
@renderify/security보안 정책 검사 모듈
@renderify/llmLLM 스트리밍 연동 유틸리티
@renderify/cliCLI 도구 (개발/테스트용)

메인 SDK만 설치해도 대부분 동작하지만, 보안 정책을 커스터마이징하거나 LLM 스트림 처리를 세밀하게 제어하려면 개별 패키지를 직접 사용한다.


고려할 점

zero-build 실행이 항상 최선은 아니다. Renderify가 잘 맞는 케이스와 주의할 케이스가 있다.

상황Renderify 적합도이유
LLM 채팅 UI 프리뷰높음실시간 렌더링이 핵심
내부 개발 도구높음빌드 인프라 없이 빠른 프로토타이핑
사용자 제공 코드 실행중간보안 프로파일 신중히 선택
프로덕션 SaaS 핵심 경로낮음CDN 의존성, 런타임 트랜스파일 성능
대규모 컴포넌트 트리낮음브라우저 트랜스파일 오버헤드

ShadowRealm sandbox mode는 격리 수준을 높여주지만, 브라우저 지원이 아직 완전하지 않다. 프로덕션 배포 전에 타겟 브라우저의 ShadowRealm 지원 여부를 확인해야 한다.


정리

  • Renderify는 @babel/standalone + JSPM CDN으로 빌드 서버 없이 브라우저에서 JSX/TSX를 실행한다
  • strict, balanced, relaxed 3가지 보안 프로파일로 LLM 생성 코드의 실행 범위를 제어한다
  • renderPromptStream으로 LLM 스트림을 받아 점진적 프리뷰 렌더링이 가능하다
  • 프로덕션 환경에서는 autoPinLatestModuleManifest: false + 사전 pinned manifest를 사용해야 안전하다
  • 10개 훅 포인트로 코드 생성부터 렌더링 완료까지 파이프라인 전체를 제어할 수 있다
  • LLM 채팅 UI, 내부 개발 도구, 실시간 프리뷰 에디터에 적합하고, 프로덕션 핵심 경로에는 신중히 판단해야 한다

참고:

관심 있을 만한 포스트

신입사원을 에이스로 — Netflix가 LLM Post-Training을 대규모 엔지니어링으로 만든 과정

Pre-training이 LLM에 넓은 언어 능력을 주지만, post-training이 실제 의도와 도메인 제약에 맞추는 단계. Netflix의 스케일링 접근법.

LLMPost-Training

AI 코딩의 맹점 — Artifacts 없이 에이전트는 기억을 잃는다

PRD, ADR, TDD가 AI 코딩 워크플로우에서 왜 선택이 아닌 필수인지, 실전 구조와 함께 살펴본다.

AI 코딩Artifacts

Next-Translate 3.0 — Turbopack과 App Router를 위한 i18n 재건

1년간 공백 후 돌아온 Next-Translate 3.0이 Turbopack 지원, 비동기 params, App Router 안정화를 한 번에 처리하는 방법.

Next.jsi18n

V8 WasmGC 투기적 최적화 — 가상 메서드를 인라인으로 만드는 법

V8이 WasmGC의 가상 메서드 디스패치에 투기적 인라이닝을 도입해 Dart와 Java 앱에서 최대 8% 성능을 끌어낸 방법.

V8WebAssembly

Vinext — Vite 위에서 Next.js를 1주일 만에 다시 만든 이야기

Cloudflare가 AI와 함께 단 일주일, $1,100의 API 비용으로 Next.js 호환 프레임워크를 Vite 위에 구축한 과정.

VinextNext.js

Tsonic — TypeScript를 네이티브 바이너리로 컴파일하는 실험

TypeScript → C# → NativeAOT 파이프라인으로 네이티브 실행 파일을 만드는 Tsonic. 어떻게 동작하고, 어떤 한계가 있는지 살펴봤다.

TypeScriptNativeAOT

VS Code 팀의 AI 에이전트 병렬화 — 월간 릴리스를 주간으로 만든 워크플로우

VS Code 팀이 월간 릴리스에서 주간 릴리스로 전환한 비결. 에이전트 세션 병렬화, 자동화 파이프라인, 품질 게이트 설계 전반을 공개했다.

VS CodeAI

React Compiler의 한계 — 뭘 최적화하고 뭘 못 하는가

React Compiler가 자동 메모이제이션으로 해결하는 것과 해결하지 못하는 것. 컴파일러 기반 UI 프레임워크의 능력 경계를 정리했다.

ReactReact Compiler