LLM과 DOM 사이의 누락된 계층
요약
생성형 UI(Generative UI)가 단순 데모를 넘어 인프라로 자리 잡으면서 발생하는 보안, 컴포넌트 일관성, 스트리밍 렌더링 문제를 다룹니다. 모델의 출력을 안전하게 격리하고, 실제 코드 기반의 API를 참조하며, DOM 차이 비교를 통해 UI를 업데이트하는 해결책을 제시합니다.
핵심 포인트
- LLM 생성 HTML의 보안 위협을 방지하기 위한 샌드박스형 iframe 격리 필요
- 모델이 디자인 시스템의 정확한 API를 참조할 수 있는 단일 진실 공급원 구축
- 스트리밍 UI 구현 시 재렌더링 문제를 해결하기 위한 DOM diffing 방식 권장
- 모델의 패턴 매칭이 아닌 실제 코드 기반의 생성 프로세스 구축
모든 진지한 AI 제품은 동일한 기능을 향해 달려가고 있습니다. 즉, 텍스트를 반환하는 대신 UI를 반환하는 것입니다. 대시보드를 요청하면 대시보드를 받고, 양식을 요청하면 작동하는 양식을 받습니다. 직접 렌더링해야 하는 JSON 블롭(JSON blob)이 아니라, 모델이 인터페이스를 직접 생성합니다. 이것이 생성형 UI (Generative UI)이며, 이는 데모용 기술에서 대부분의 팀이 인프라로 감당할 수 있는 속도보다 더 빠르게 기대되는 기능으로 변모하고 있습니다.
하지만 실제로 이를 배포했을 때 무엇이 망가지는지에 대해서는 아무도 충분히 이야기하지 않고 있습니다.
모두가 직면하게 되는 세 가지 문제 (순서대로)
1. 출력물은 신뢰할 수 없지만, 당신은 어쨌든 이를 렌더링하려 합니다.
LLM이 HTML/JS를 생성하면 당신은 이를 DOM에 넣습니다. 이는 CMS 대신 모델이 루프에 포함된 dangerouslySetInnerHTML와 같습니다. 모델은 <script> 태그, 인라인 이벤트 핸들러(inline event handler), 혹은 가서는 안 될 곳으로의 fetch를 환각(hallucinate)할 수 있습니다. 대부분의 팀은 이를... 처리하지 않음으로써, 그리고 시스템 프롬프트(system prompt)가 보안 경계가 되기를 바라며 해결하려 합니다. 하지만 그것은 보안 경계가 아닙니다.
2. 모델은 실제로 당신의 컴포넌트(components)를 알지 못합니다.
LLM에게 당신의 디자인 시스템을 사용하여 버튼을 생성하라고 요청하면, 모델은 존재하지 않는 props를 만들어내거나, 필수적인 접근성 속성(accessibility attributes)을 누락하거나, 훈련 데이터에 있는 내용이라는 이유로 6개 버전 이전의 API를 자신 있게 사용할 것입니다. 생성 시점에 모델이 대조하며 확인할 수 있는 단일 진실 공급원(source of truth)이 없습니다. 모델은 무언가를 조회하는 것이 아니라 패턴 매칭(pattern-matching)을 하고 있는 것입니다.
3. 스트리밍 UI (Streaming UI)는 텍스트 스트리밍과는 다른 문제입니다.
토큰 단위(token-by-token)의 텍스트는 단순히 추가될 뿐입니다. 하지만 토큰 단위의 HTML은 그렇지 않습니다. 절반만 닫힌 태그, 렌더링되기 전에 전체 props 세트가 필요한 컴포넌트, 그리고 매 청크(chunk)마다 스크롤 위치를 초기화하고 포커스를 잃게 만드는 재렌더링(re-render) 등이 발생합니다. 대부분의
격리 (Isolation). 모델의 출력물은 구조적으로 호스트 페이지에 도달할 수 없는 곳에 렌더링되어야 합니다. 즉, 공유된 DOM, 쿠키 접근, 주변 환경의 fetch (ambient fetch)가 없어야 합니다. 자동 생성된 제한적인 콘텐츠 보안 정책 (Content-Security-Policy, CSP)을 가진 샌드박스형 iframe과 호스트로 연결되는 타입이 지정된 메시지 채널 (typed message channel)을 사용하여, 오류와 이벤트가 우연히가 아니라 의도적으로 경계를 넘도록 해야 합니다.
생성 시점의 정답 (Ground truth at generation time). 모델은 기억에 의존하여 사용자의 컴포넌트 API를 추측해서는 안 됩니다. 모델은 실제 API — 현재의 props, 현재의 타입, 현재의 예시 — 를 쿼리할 수 있어야 하며, 이상적으로는 무언가가 렌더링되기 전에 자신의 출력을 동일한 소스와 대조하여 확인해야 합니다. 실제 코드와 동떨어진 수동 관리 문서가 아니라, 실제 코드(actual code)여야 합니다.
재렌더링 (re-render)이 아닌 실제 패치 (A real patch). 라이브 프리뷰에 HTML을 스트리밍할 때는 스크립트, 상태(state), 또는 포커스를 해제하지 않고 DOM 차이 비교 (DOM diffing) 또는 본문 교체 (body-replaced)를 통해 제자리에서 업데이트되어야 합니다. 또한, 잘못된 청크(chunk)가 충돌이 아닌 메시지로 나타날 수 있도록 호스트로 전달되는 크기 조정/오류 채널 (resize/error channel)이 필요합니다.
이것의 엔드 투 엔드 (end to end) 작동 예시
이것은 가설이 아닌, Vielzeug라는 오픈 소스 TypeScript 툴킷에서 제공하는 위 내용의 구체적인 버전입니다:
- MCP 서버가 컴포넌트 라이브러리의 실제 문서, 타입 시그니처(type signatures), 예시를 노출하여, AI 클라이언트가 오래된 학습 데이터의 패턴 매칭 대신 현재의 진실(truth)을 쿼리할 수 있게 합니다.
- 컴포넌트 라이브러리 자체에 검증 도구가 포함되어 있습니다. 생성된 마크업은 신뢰받기 전에, 수동으로 작성된 명세가 아닌 실제 빌드 출력물로부터 생성된 동일한 커스텀 엘리먼트 매니페스트 (Custom Elements Manifest)를 기준으로 확인됩니다.
- 샌드박스형 iframe 런타임이 렌더링을 처리합니다: 엄격하게 자동 생성된 CSP,
allow-scripts만 허용, 타입이 지정된postMessage브리지, 그리고 스트리밍을 위해 특별히 구축된patch()호출을 사용합니다.
// LLM으로부터 토큰을 격리된 라이브 프리뷰로 직접 스트리밍
let accumulated = '';
for await (const chunk of streamUI(userPrompt)) {
...
실제 명세(spec)를 쿼리하고 → 이를 바탕으로 생성하며 → 결과를 검증한 뒤 → 스트리밍되는 동안 당신에게 해를 끼칠 수 없는 곳에 실시간으로 렌더링합니다. 세 가지의 별개 관심사, 세 가지의 별개 조각들이 덕테이프로 대충 붙여진 것이 아니라 서로 연결되어 작동합니다.
이것은 프론트엔드를 대체하는 것이 아니라, 프론트엔드에 연결되는 것입니다
이 점에 대해 솔직하게 말할 가치가 있습니다. 왜냐하면 하이프 사이클(hype cycle)은 그렇지 않기 때문입니다. 생성형 UI (Generative UI)는 당신의 디자인 시스템(design system), 직접 구축한 대시보드(dashboards), 또는 정교하게 조정된 결제 흐름(checkout flow)을 대체하러 오는 것이 아닙니다. 그 누구도 자신의 핵심 내비게이션(navigation), 온보딩(onboarding), 또는 결제 페이지가 방문할 때마다 모델에 의해 다시 생성되기를 원하지 않습니다. 정적(static) 및 동적(dynamic) UI — 즉, 인간에 의해 구축되고, 검토되고, 테스트되고, 버전 관리되는 UI — 는 매번 일관되고, 접근 가능하며, 예측 가능해야 하는 모든 요소에 대해 정확히 그 자리에 머물 것입니다.
생성형 UI가 유용한 부분은 모든 가능한 경우에 대해 직접 구축하기에는 항상 비용이 많이 들었던 영역입니다. 즉, 사용자가 방금 던진 질문에 대한 즉석 차트(ad-hoc chart), 대화에 의해 형성된 일회성 양식(one-off form), 또는 무한한 변형이 존재하여 아무도 템플릿을 작성하지 않은 보고서 같은 것들입니다. 이것은 당신의 앱을 대체하는 것이 아니라, 앱을 보완하는 것입니다.
아키텍처(architecture)는 이러한 분리를 문자 그대로 반영합니다. 위의 예시에서 샌드박스(sandbox)는 평범하고 직접 구축된 페이지 내부의 슬롯(slot) 입니다. 즉, 나머지 정적 UI가 호스팅하는 격리된 컨테이너이며, 정적 UI를 점령하는 것이 아닙니다. 디자인 시스템은 당신이 직접 만든 화면에서 이미 사용 중인 것과 동일한 것입니다. 모델은 단지 동일한 명세(spec)를 따르며, 당신이 일반적인 방식으로 구축한 다른 모든 것 옆의 격리된 공간에서 렌더링되는, 해당 명세에 따른 마크업(markup)의 또 다른 생성자일 뿐입니다. 생성형 UI는 이미 당신이 가지고 있는 앱 내부에서 자리를 잡습니다. 그것은 당신에게 그 앱을 버리라고 요구하지 않습니다.
실제 핵심 요약
이 중 그 어떤 것도 특화된 "AI 프레임워크 (AI framework)"를 필요로 하지 않습니다. 이것은 훌륭한 프론트엔드 인프라가 항상 요구해 왔던 것들 — 타입이 지정된 계약 (typed contracts), 진정한 격리 (real isolation), 예측 가능한 상태 (predictable state) — 을 요구하며, 다만 이를 새로운 종류의 신뢰할 수 없는 입력 (untrusted input)에 적용하고, 그 입력이 실제로 자리를 잡을 가치가 있는 곳에만 적용할 뿐입니다. 생성형 UI (generative UI)를 제대로 구현하는 팀은 프론트엔드를 모델로 교체하는 팀이 아닐 것입니다. 그들은 모델에게 유용하게 쓰일 수 있는 작고, 안전하며, 잘 정의된 슬롯 (slot)을 제공하고 — 나머지 모든 것은 이미 작동 방식을 알고 있는 기존 방식대로 계속 구축해 나가는 팀이 될 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기