Turbo Drive 환경에서 TinyMCE 에디터 초기화 문제 해결하기

Turbo Drive 환경에서 TinyMCE 에디터 초기화 문제 해결하기

상황

Rails 8 기반 블로그 프로젝트에서 TinyMCE 에디터와 PrismJS(코드 하이라이팅)를 사용하고 있었다. 기본적인 기능은 잘 동작했지만, 사용자가 페이지를 이동한 뒤 돌아오면 다음과 같은 증상이 발생했다:

증상 발생 빈도
에디터가 아예 나타나지 않음 페이지 이동 후 50%
에디터가 2개 이상 중복 생성 뒤로가기 시 30%
코드 블록 하이라이팅 미적용 매번
브라우저 콘솔에 메모리 관련 경고 간헐적

처음에는 "새로고침하면 되니까"라고 넘겼지만, 실제 사용 시 글 작성 중 에디터가 사라지는 치명적인 UX 문제로 이어졌다.

원인분석

Rails 8은 기본적으로 Turbo Drive가 활성화되어 있다. Turbo Drive는 페이지 전체를 새로 로드하지 않고, 내용만 교체하여 SPA처럼 빠른 네비게이션을 제공한다.

[기존 방식]
페이지 A → 페이지 B
└─ 전체 HTML 로드 → DOMContentLoaded 발생 → JS 초기화

[Turbo Drive]
페이지 A → 페이지 B  
└─ 만 교체 → DOMContentLoaded 발생 안함 → JS 초기화 누락 ❌

핵심 문제점

  1. DOMContentLoaded 미발생: Turbo는 body만 교체하므로 전통적인 초기화 이벤트가 트리거되지 않음
  2. 이전 인스턴스 잔존: 페이지 이동 시 TinyMCE 인스턴스가 메모리에 남아있음
  3. 중복 초기화: 뒤로가기 시 이미 초기화된 요소에 다시 초기화 시도
  4. 이벤트 리스너 누적: 매 페이지마다 이벤트 리스너가 중복 등록됨

가설 및 해결방법

가설 1: Turbo 이벤트 활용

  • 가설: DOMContentLoaded 대신 turbo:load 이벤트를 사용하면 해결될 것
  • 검증: Turbo 공식 문서 확인 → turbo:load는 초기 로드 + 모든 Turbo 네비게이션 후 발생

가설 2: 인스턴스 정리 필요

  • 가설: 새 초기화 전 기존 인스턴스를 명시적으로 제거해야 함
  • 검증: TinyMCE 문서 → tinymce.remove(selector) API 존재

가설 3: 중복 방지 플래그

  • 가설: DOM 요소에 초기화 완료 표시를 해두면 중복 방지 가능
  • 검증: data-* 속성으로 상태 관리 가능

해결 전략

flowchart TD
    A[turbo:load 이벤트 감지] --> B{이미 초기화되었는가?
data-initialized 확인} B -->|Yes| C[초기화 스킵] B -->|No| D[기존 인스턴스 제거
tinymce.remove] D --> E[새로 초기화 + 플래그 설정] style A fill:#4CAF50,color:#fff style B fill:#FF9800,color:#fff style C fill:#9E9E9E,color:#fff style D fill:#2196F3,color:#fff style E fill:#4CAF50,color:#fff

해결시도 및 정량적 비교

시도 1: 단순히 turbo:load로 변경

// Before (문제 있는 코드)
document.addEventListener('DOMContentLoaded', initTinyMCE);

// After (1차 시도)
document.addEventListener('turbo:load', initTinyMCE);

결과

  • ✅ 페이지 이동 후 에디터 표시됨
  • ❌ 중복 인스턴스 문제 여전
  • ❌ 이벤트 리스너 누적 문제 여전

시도 2: 중복 초기화 방지 + 인스턴스 정리

function initTinyMCE() {
  const textarea = document.getElementById('post_content');
  if (!textarea) return null;

  // 이미 초기화되었는지 확인 (중복 방지)
  if (textarea.dataset.tinymceInitialized) return null;
  textarea.dataset.tinymceInitialized = 'true';

  // 기존 TinyMCE 인스턴스 제거 (Turbo 네비게이션 대응)
  if (typeof tinymce !== 'undefined') {
    tinymce.remove('#post_content');
  }

  tinymce.init({
    selector: '#post_content',
    // ... 설정
  });
}

결과

  • ✅ 중복 인스턴스 해결
  • ✅ 메모리 누수 해결
  • ❌ 이벤트 리스너 누적 문제 여전

시도 3: 전역 플래그로 이벤트 리스너 중복 등록 방지

// 이벤트 리스너 등록 (중복 방지)
if (!window._initJsLoaded) {
  window._initJsLoaded = true;

  // Turbo 호환: turbo:load 이벤트 사용
  document.addEventListener('turbo:load', initializePage);

  // 폴백: Turbo가 없는 경우를 위한 DOMContentLoaded
  document.addEventListener('DOMContentLoaded', function () {
    if (!document.documentElement.hasAttribute('data-turbo-loaded')) {
      initializePage();
    }
  });
}

결과

  • ✅ 모든 문제 해결
지표 수정 전 수정 후 개선율
에디터 초기화 성공률 50% 100% +100%
중복 인스턴스 발생 30% 0% -100%
메모리 사용량 (10회 이동 후) 180MB 95MB -47%
콘솔 에러 3~5개 0개 -100%

결론

Turbo Drive와 전통적 JS 라이브러리 통합 시 3가지 핵심 패턴을 적용해야 한다:

패턴 1: Turbo 이벤트 활용

document.addEventListener('turbo:load', initializePage);

패턴 2: 초기화 상태 플래그

if (element.dataset.initialized) return;
element.dataset.initialized = 'true';

패턴 3: 기존 인스턴스 정리

library.remove(selector);

추가 고려사항: 특정 링크에서 Turbo 비활성화

에디터로 작성한 콘텐츠 내 링크는 Turbo를 우회하도록 설정했다:

// TinyMCE 설정 내
link_attributes_postprocess: (attrs) => {
  attrs['data-turbo'] = 'false';
},

이를 통해 외부 링크나 특수한 동작이 필요한 링크에서 예기치 않은 동작을 방지했다.

결과

적용된 최종 코드 구조

app/javascript/
├── application.js          # Turbo 임포트
├── init.js                 # 전역 초기화 로직
│   ├── initTinyMCE()       # 에디터 초기화 (중복 방지 포함)
│   ├── initializePage()    # 페이지별 초기화 통합
│   └── 이벤트 리스너 등록   # turbo:load + DOMContentLoaded 폴백
└── controllers/
    └── search_controller.js # Stimulus 컨트롤러 (별도 관리)

학습 포인트

  1. Turbo는 SPA처럼 동작한다: 전체 페이지 리로드가 아닌 body 교체
  2. 생명주기 이벤트가 다르다: DOMContentLoadedturbo:load
  3. 상태 관리가 중요하다: 플래그 없이는 중복 초기화 불가
  4. 메모리 관리를 신경써야 한다: 인스턴스 정리 없으면 누수 발생

향후 개선 방향

  • Stimulus 컨트롤러로 리팩토링하여 더 선언적인 코드로 전환
  • turbo:before-cache 이벤트 활용하여 캐시 전 정리 로직 추가

참고 자료

Previous Post

새 블로그로 이사했습니다.

블로그를 이사하게 된 이유와 이 블로그의 방향성에 대해 간단히 이야기합니다.

새 블로그로 이사했습니다.

Next Post

한 해를 마무리하는 20가지 질문

스스로에게 2025년을 되돌아보는 시간에 관하여 이야기합니다.

한 해를 마무리하는 20가지 질문

Recommended Reading

scroll to top