브라우저 확장 프로그램에 AI를 넣고 싶다는 요구는 많은데, 막상 구현에 들어가면 다들 비슷한 데서 막힙니다. 모델은 어디서 로드해야 하는지, Manifest V3 서비스 워커는 왜 자꾸 죽는지, 페이지 DOM 접근은 어떤 런타임에서 해야 하는지, UI와 모델 상태는 누가 들고 있어야 하는지 같은 문제입니다. Hugging Face가 공개한 Transformers.js 기반 Chrome Extension 가이드는 이 부분을 꽤 현실적으로 정리해둬서, 웹 확장 쪽 개발자라면 한 번 참고할 만합니다.
웹앱에서는 보통 브라우저 안에서 모델을 돌린다고 하면 그냥 프런트엔드 문제처럼 보입니다. 하지만 크롬 확장 프로그램은 다릅니다. MV3는 런타임이 쪼개져 있습니다. background service worker, side panel 또는 popup UI, content script가 서로 다른 권한과 생명주기를 가집니다. 그래서 "모델을 브라우저에서 돌린다"는 말만으로는 설계가 안 끝납니다. 어느 컨텍스트가 모델을 소유하고, 어느 컨텍스트가 페이지를 읽고, 어느 컨텍스트가 사용자 인터랙션을 처리할지 먼저 나눠야 합니다.
원문 가이드는 이 점을 아주 분명하게 잡습니다. 무거운 오케스트레이션과 모델 실행은 background가 맡고, side panel은 상호작용 UI를 맡고, content script는 DOM 추출과 강조 같은 페이지 작업만 맡습니다. 이 분리가 핵심입니다. 여기서 어긋나면 같은 모델을 여러 번 로드하거나, 탭마다 상태가 꼬이거나, 서비스 워커 재시작 때 대화 기록이 날아가는 문제가 생깁니다.
가이드에서 제안한 구조를 실무 용어로 바꾸면 이렇습니다. background service worker는 컨트롤 플레인입니다. 모델 초기화, 에이전트 상태, 메시지 히스토리, 도구 실행, 공용 캐시를 모두 여기서 관리합니다. side panel은 채팅 UI, 다운로드 진행 표시, 설정 토글 같은 프레젠테이션 레이어입니다. content script는 현재 페이지를 읽거나 하이라이트를 그리는 얇은 브리지 역할을 합니다.
왜 이 구조가 좋은가 하면, 모델을 한 번만 올리면 여러 탭과 UI가 공유할 수 있기 때문입니다. 글에서는 Gemma 4 E2B 기반 텍스트 생성 모델과 MiniLM 임베딩 모델을 분리해 background에서 돌리는 예를 보여줍니다. reasoning이나 도구 선택은 생성 모델이 맡고, semantic search는 임베딩 모델이 맡는 식입니다. 이 역할 분리가 있으면 기능 추가가 쉬워집니다. 나중에 페이지 요약, 히스토리 검색, 탭 추천 같은 기능을 붙일 때 기존 모델 호스트를 재활용할 수 있습니다.
확장 프로그램에서 상태가 꼬이는 가장 흔한 원인은 메시지 계약이 느슨한 겁니다. UI에서 background로 아무 객체나 던지고, background에서 content script를 직접 건드리기 시작하면 디버깅이 급격히 어려워집니다. 원문 가이드는 enum 기반의 명시적인 메시지 타입을 사용합니다. 예를 들어 모델 초기화, 채팅 생성, 메시지 조회, 페이지 데이터 추출, 하이라이트 지우기 같은 작업이 각각 타입으로 구분됩니다.
이게 중요한 이유는 로컬 AI 확장이 점점 "미니 앱 플랫폼"처럼 변하기 때문입니다. 처음엔 채팅 하나로 시작해도 금방 툴 호출, 페이지 컨텍스트, 히스토리 저장, 다운로드 진행, 설정 패널이 붙습니다. 이때 메시지 타입과 데이터 계약이 명확하지 않으면, 어디서 오류가 났는지 추적하기가 힘들어집니다. 특히 서비스 워커가 재시작되는 MV3 환경에서는 복구 가능한 상태와 매번 다시 받아와야 하는 상태를 구분해야 합니다.
Transformers.js를 쓸 때 많은 개발자가 제일 먼저 보는 건 WebGPU 속도입니다. 물론 중요합니다. 하지만 확장 프로그램에서는 속도만큼이나 중요한 게 로딩 생명주기입니다. 모델 파일이 언제 내려받아지는지, 어느 origin 캐시에 붙는지, 초기화가 중복 실행되는지, 서비스 워커가 내려갔다 올라오면 어떤 상태를 다시 준비해야 하는지가 더 큰 문제일 때가 많습니다.
가이드는 CHECK_MODELS와 INITIALIZE_MODELS 같은 흐름으로 이 문제를 다룹니다. 먼저 캐시 상태를 확인하고, 필요한 다운로드 용량을 사용자에게 보여주고, 실제 초기화를 진행한 뒤 진행률을 UI에 보냅니다. 이 패턴은 실무에서도 꽤 쓸 만합니다. 사용자는 "왜 갑자기 확장이 느려졌지"보다 "지금 모델 800MB를 받는 중이구나"를 알면 덜 답답해합니다. 반대로 이 안내가 없으면 바로 삭제합니다.
또 하나 기억할 점은 서비스 워커의 휘발성입니다. 확장 프로그램의 background는 서버가 아닙니다. 언제든 suspend되고 다시 살아날 수 있습니다. 그래서 모델 인스턴스를 영구 프로세스처럼 다루면 안 됩니다. "없으면 재초기화"와 "필요한 상태만 복원"을 기본 전략으로 잡아야 합니다.
가이드에서는 sidePanel, storage, scripting, tabs, 그리고 http(s) 전역 host_permissions 같은 권한 구성이 나옵니다. 여기서 중요한 건 권한을 최소화하라는 원칙입니다. 로컬 AI 확장은 사용자가 특히 민감하게 봅니다. "모든 웹사이트 읽기 권한"을 요청하면서 왜 필요한지 설명하지 못하면 설치 전환율이 떨어지고, 크롬 웹스토어 심사도 불리해집니다.
실무적으로는 기능 중심으로 권한을 역산하는 게 좋습니다. 전체 웹사이트에서 텍스트 추출이 필요한지, 특정 도메인만 지원해도 되는지, 저장소 권한이 단순 설정용인지 히스토리 보관용인지 구분해야 합니다. 그리고 로컬 추론이라는 사실을 제품 메시지에서 분명히 해야 합니다. 사용자 입장에서는 "내 페이지 내용이 서버로 안 나가나"가 가장 궁금한 질문입니다. 이걸 초기에 명확히 못 박아야 합니다.
이 구조는 브라우저 안에서 빠르게 반응하는 개인 비서형 기능에 잘 맞습니다. 예를 들어 현재 페이지 요약, 페이지 내 Q&A, 탭 히스토리 검색, 양식 자동완성 보조, 리서치 보조 같은 기능입니다. 반대로 대규모 서버 연산이 필요한 복잡한 멀티에이전트 작업은 확장 프로그램 단독 구조만으로는 한계가 있습니다. 이때는 로컬 경량 모델과 서버 기반 고성능 모델의 하이브리드가 더 현실적입니다.
또한 크롬 확장에서는 "100점짜리 모델"보다 "즉시 반응하는 80점 모델"이 더 나은 경우가 많습니다. 사용자 경험이 짧은 인터랙션에 묶여 있기 때문입니다. Gemma 4 E2B와 같은 온디바이스 친화 모델을 택한 배경도 이해할 만합니다. 여기서 배울 점은, 확장 프로그램의 AI는 모델 벤치마크보다 배포 환경 적합성이 먼저라는 겁니다.
크롬 확장 프로그램에서 AI를 안정적으로 돌리려면, 결국 런타임 경계를 잘 나누는 수밖에 없습니다. background는 상태와 모델을, UI는 상호작용을, content script는 페이지 액세스를 맡는 구조가 현재로서는 가장 현실적입니다. 여기에 메시지 계약, 캐시 전략, 권한 최소화, 서비스 워커 복구 전략까지 붙어야 실제 제품이 됩니다.
당장 적용할 체크리스트로 마무리하겠습니다.
참고 소스: Hugging Face Blog, How to Use Transformers.js in a Chrome Extension (2026-04-23)