-
Monaco Editor "TextModel got disposed" 에러 완벽 해결 가이드실제 경험과 인사이트를 AI와 함께 정리한 글 2025. 10. 27. 14:46
작성일: 2025년 10월 27일
카테고리: React, Monaco Editor, 디버깅
난이도: 중급
TL;DR
- 문제:
TextModel got disposed before DiffEditorWidget model got reset에러 발생 - 원인: @monaco-editor/react의 DiffEditor가 props 변경 시 모델을 재생성하면서 dispose 충돌 발생
- 해결:
keepCurrentOriginalModel={true}+keepCurrentModifiedModel={true}props 추가 (단 2줄!) - 결과: 복잡한 cleanup 로직 없이 깔끔하게 해결, 코드 45% 감소
들어가며
imprun.dev는 Kubernetes 기반 서버리스 Cloud Function 플랫폼입니다. 웹 콘솔에서 함수 배포 히스토리 비교 기능을 구현하면서 Monaco Editor의 DiffEditor를 사용했는데, 다음과 같은 에러가 지속적으로 발생했습니다:
Uncaught Error: TextModel got disposed before DiffEditorWidget model got reset이 에러는:
- ❌ Dialog(모달)를 닫을 때마다 발생
- ❌ 파일이나 버전을 변경할 때 발생
- ❌ 사용자 경험을 크게 해침 (콘솔 에러 폭탄)
- ❌ 메모리 누수 가능성 존재
문제 상황: 언제, 왜 발생하나?
🔍 에러 발생 시나리오
// FunctionHistoryModal.tsx function FunctionHistoryModal({ open, onOpenChange }) { const [selectedFile, setSelectedFile] = useState<string | null>(null) return ( <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent> <DiffEditor original={previousVersion?.files[selectedFile] || ''} modified={currentVersion?.files[selectedFile] || ''} language="typescript" theme="vs-dark" /> </DialogContent> </Dialog> ) }에러 발생 타이밍:
- 사용자가 파일을 선택 →
selectedFile변경 → DiffEditor props 변경 → 에러! - 사용자가 다른 버전 선택 →
previousVersion/currentVersion변경 → 에러! - 사용자가 모달 닫기 → DiffEditor unmount → 에러!
원인 분석: Monaco Editor의 내부 동작
📚 Monaco Editor의 Model 관리
Monaco Editor는 내부적으로
TextModel을 사용하여 코드를 관리합니다:DiffEditor ├─ OriginalEditor │ └─ TextModel (original) └─ ModifiedEditor └─ TextModel (modified)문제의 핵심:
@monaco-editor/react의 DiffEditor는 props가 변경되면 기존 모델을 dispose하고 새 모델을 생성합니다- 하지만 dispose 순서가 비동기적으로 처리되면서 race condition 발생:
1. TextModel.dispose() 시작 2. DiffEditorWidget이 아직 모델을 참조 중 3. TextModel이 먼저 dispose 완료 4. DiffEditorWidget이 dispose된 모델 접근 시도 → 💥 에러!
🔬 실제 에러 스택 트레이스
Error: TextModel got disposed before DiffEditorWidget model got reset at lR.value (monaco-editor.js:388:50203) at P._deliver (monaco-editor.js:7:2719) at tl.dispose (monaco-editor.js:243:68815) at commitHookEffectListUnmount (react-dom.js:9449:160)React의 unmount phase에서 Monaco Editor의 내부 dispose 메커니즘과 충돌하는 것을 확인할 수 있습니다.
시도한 해결 방법들
❌ 시도 1: 수동 cleanup (실패)
useEffect(() => { return () => { if (editorRef.current) { const editor = editorRef.current const originalModel = editor.getOriginalEditor().getModel() const modifiedModel = editor.getModifiedEditor().getModel() // 모델 분리 editor.getOriginalEditor().setModel(null) editor.getModifiedEditor().setModel(null) // Editor dispose editor.dispose() // 모델 dispose originalModel?.dispose() modifiedModel?.dispose() } } }, [])결과: ❌ 여전히 에러 발생
- dispose 순서를 보장해도 @monaco-editor/react 내부에서 이미 dispose 시도
❌ 시도 2: setValue()로 수동 갱신 (실패)
useEffect(() => { if (!editorRef.current) return const originalModel = editor.getOriginalEditor().getModel() const modifiedModel = editor.getModifiedEditor().getModel() // props 변경 시 setValue()로 내용만 갱신 (모델 재생성 방지) if (originalModel) originalModel.setValue(original) if (modifiedModel) modifiedModel.setValue(modified) }, [original, modified])결과: ❌ 여전히 에러 발생
- DiffEditor가 props 변경을 감지하면 setValue() 이전에 모델 재생성 시도
❌ 시도 3: Props 고정 + 수동 갱신 (실패)
// 초기값을 ref로 저장하여 DiffEditor props는 절대 변경하지 않음 const initialOriginalRef = useRef(original) const initialModifiedRef = useRef(modified) <DiffEditor original={initialOriginalRef.current} // 고정! modified={initialModifiedRef.current} // 고정! /> // useEffect에서 setValue()로만 갱신 useEffect(() => { updateEditorContent(original, modified) }, [original, modified])결과: ❌ 여전히 에러 발생
- 150줄의 복잡한 코드, 여전히 unmount 시 충돌
✅ 최종 해결: keepCurrentModel Props
🎯 공식 Props 발견!
@monaco-editor/react 문서를 자세히 읽다가, 공식적으로 제공하는 props를 발견했습니다:
<DiffEditor original={original} modified={modified} keepCurrentOriginalModel={true} // ← 핵심! keepCurrentModifiedModel={true} // ← 핵심! />이 props의 역할:
keepCurrentOriginalModel={true}: props 변경 시 기존 original 모델 유지, setValue()로 내용만 갱신keepCurrentModifiedModel={true}: props 변경 시 기존 modified 모델 유지, setValue()로 내용만 갱신- 모델 재생성 완전 방지 → dispose 충돌 근본적으로 해결
📝 최종 구현 코드
/** * Code Diff Viewer Component * * keepCurrentOriginalModel, keepCurrentModifiedModel props로 * props 변경 시 모델 재생성 방지 */ import { useEffect, useRef, memo } from "react" import { DiffEditor } from "@monaco-editor/react" import type { editor } from "monaco-editor" interface CodeDiffViewerProps { original: string modified: string language: string visible: boolean } export const CodeDiffViewer = memo(function CodeDiffViewer({ original, modified, language, visible, }: CodeDiffViewerProps) { const editorRef = useRef<editor.IStandaloneDiffEditor | null>(null) const handleEditorDidMount = (editor: editor.IStandaloneDiffEditor) => { editorRef.current = editor } useEffect(() => { return () => { if (editorRef.current) { try { editorRef.current.dispose() } catch (error) { console.debug('DiffEditor cleanup:', error) } editorRef.current = null } } }, []) if (!visible) { return null } return ( <div className="flex-1 border rounded-lg overflow-hidden"> <DiffEditor height="100%" language={language} original={original} modified={modified} theme="vs-dark" onMount={handleEditorDidMount} keepCurrentOriginalModel={true} // ← 핵심 해결! keepCurrentModifiedModel={true} // ← 핵심 해결! options={{ readOnly: true, renderSideBySide: true, minimap: { enabled: false }, fontSize: 13, automaticLayout: true, }} /> </div> ) })코드 개선 효과:
- ✅ 150줄 → 85줄 (45% 감소)
- ✅ 복잡한 cleanup 로직 제거
- ✅ 공식 지원 기능 사용 (유지보수성 향상)
- ✅ 에러 완전 해결
결과 및 검증
🧪 테스트 시나리오
1. 함수 히스토리 모달 열기 2. 다양한 버전 빠르게 전환 (20회) 3. 다양한 파일 빠르게 전환 (20회) 4. 모달 닫기 5. 1-4를 20회 반복✅ Before vs After
항목 Before After 콘솔 에러 ❌ 매번 발생 ✅ 완전히 사라짐 코드 라인 수 150줄 85줄 (-45%) 복잡도 높음 (ref, useEffect 다수) 낮음 (props 2개 추가) 메모리 누수 ⚠️ 가능성 존재 ✅ 없음 유지보수성 낮음 (수동 관리) 높음 (공식 API)
핵심 교훈
💡 문제 해결 과정
복잡한 해결책부터 시도하지 말 것
- 150줄의 수동 cleanup 코드 작성
- 실제로는 props 2개로 해결 가능
공식 문서를 끝까지 읽을 것
keepCurrentOriginalModelprops는 문서 하단에 있었음- 초기에 발견했다면 몇 시간 절약 가능
라이브러리 내부 동작 이해의 중요성
- Monaco Editor의 모델 관리 메커니즘 이해
- React lifecycle과의 상호작용 파악
🎯 일반화된 해결 전략
Monaco Editor와 같은 복잡한 외부 라이브러리 사용 시:
공식 문서 정독
- 특히 "Advanced Usage", "Performance", "Troubleshooting" 섹션
Props 탐색
- TypeScript 타입 정의 파일 확인
- JSDoc 주석 읽기
GitHub Issues 검색
- 동일한 문제를 겪은 사람들의 해결책
- Maintainer의 권장 방법
최소한의 코드로 시작
- 복잡한 해결책은 마지막 수단
- 공식 API가 있는지 먼저 확인
참고 자료
마치며
Monaco Editor의 "TextModel got disposed" 에러는 많은 개발자들이 겪는 문제입니다. 복잡한 수동 관리 대신, 공식적으로 제공하는 props를 활용하면 간단하게 해결할 수 있습니다.
핵심은 단 2줄입니다:
keepCurrentOriginalModel={true} keepCurrentModifiedModel={true}이 글이 같은 문제로 고민하는 개발자들에게 도움이 되길 바랍니다! 🚀
imprun.dev 팀
Kubernetes 기반 서버리스 Cloud Function 플랫폼'실제 경험과 인사이트를 AI와 함께 정리한 글' 카테고리의 다른 글
Cilium 환경에서 API Gateway 배포 시 hostNetwork가 필요한 이유 (0) 2025.10.29 Kong에서 APISIX로의 험난한 여정: Cilium 환경에서의 시행착오 (0) 2025.10.29 Kubernetes Gateway API 실전 가이드: Kong Ingress에서 표준 API로 전환하기 (1) 2025.10.27 Kubernetes에서 특권 포트 피하기: NodePort + iptables 포워딩 패턴 (0) 2025.10.27 Kubernetes Gateway API 실전 가이드: Kong Ingress에서 표준 API로 전환하기 (0) 2025.10.27 - 문제: