2025년 7월 25일. FrontEnd 업계의 동향과 인사이트를 얻기 위해 Toss 컨퍼런스에 참가했다. 참여한 세션에서 얻은 내용을 간단하게 정리했다. 8월 중으로 각 세션에 대한 영상이 공개될 예정이라고 하니 오프라인으로 참여하지 못했다면 영상으로 확인하면 좋을 것 같다.
60개 React Native 패키지, 하나의 프레임워크로
- 김희철님
웹 세션은 아니였지만, 관련해서 얻을 수 있는 지식이 있을 것이라고 판단하여 참석했다.
“로그도 제품이다”
수동 로깅의 문제점
- 로그 개발 & QA 반복하며 비효율적인 시간을 가진다.
- 사람이 직접해야되기 때문에 누락 등이 발생할 수 있다.
로그 자동화 목표
- 화면 내 모든 클릭과 노출 로그를 자동화한다.
- 모든 플랫폼에서 동일한 시점에 남긴다.
구현 컨셉
토스 디자인 시스템 컴포넌트 하나하나를 로그의 기준으로 삼는다.
IOS/Android에서 화면에 보이는 모든 컴포넌트들이 로그를 찍을 수 있도록 했다.
→ 화면이 변경되더라도 자동 로그가 찍히고, 코드 생산성이 대폭 향상된다
예를 들어서, A라는 페이지를 유저가 접속했을 때 해당 화면에 있는 모든 디자인 시스템 기반 컴포넌트들이 로그를 찍는다고 한다. 화면에 나타났을 떄, 클릭했을 때 등 모든 로그를 베이스인 디자인 시스템을 사용했을 떄 찍힐 수 있게 하면서 자동화에 성공한 것이 핵심이다.
Native ESM에 올라탄 마이크로 프론트엔드
- 박건영님, 장재영님
웹뷰를 주로 사용하는 토스에서 모듈을 어떻게 효율적으로 다룰지에 대한 고민을 볼 수 있는 세션이었다.
문제 상황
한 번 배포에 PR이 수십 개씩 → 롤백 어렵고, 영향도 크고, 빌드 오래 걸림
페이지 수가 많아 (450개 이상) 빌드 시간 30분, 배포까지 5분 이상 소요
개발 환경도 무거워지고 유지보수 난이도 증가
해결 방법: MFE + Native ESM
- 모놀리식 앱 → 도메인 기준으로 MFE로 분리
- 앱 간 통합은 JS 런타임에서 처리
- 번들 없이 브라우저가 ESM을 직접 import해서 로드
핵심 기술 포인트 : Import Map
<script type="importmap">
{
"imports": {
"hello": "/apps/hello-v1.js"
}
}
</script>
- import “hello”라고 쓰면 /apps/hello-v1.js를 가져오도록 맵핑
- 이걸 기반으로 각 MFE 앱을 조립
동적 조립
- 각 앱 주소(A, B, C)를 importMap에 script로 넣어 SSR처럼 렌더링
- 페이지 이동 시에도 브라우저가 각 JS를 동적으로 로드
라우팅 처리
- 기존 라우팅 라이브러리는 단일 앱 기준
- Toss에서는 자체 라우팅 라이브러리를 만들어서 페이지 이동 처리
실제 구현
번들 주소 조립
- HTML 파일 하나를 생성해서 라우팅 테이블과 Import Map을 함께 내려줌
- 클라이언트는 이 HTML을 받아 각 앱을 조립
- 예:
/mfa/stock
을 요청하면/stock-v1.js
를 로드하고, 라우팅 테이블 기반으로 페이지 렌더링
업데이트된 서비스만 개별 배포
- 예:
trading-v2
를 배포할 경우, 웹서버에서 새롭게 조립된 importMap을 전달 - 유저는
trading
앱만 새로운 버전으로 경험 - 나머지 앱은 기존 버전 유지
카나리 배포
- 새 버전(
trading-v2.js
)을 10% 유저에게만 배포 - 쿠키 값을 기반으로 어떤 버전을 볼지 결정
- 앱마다 카나리 비율은 영향도에 따라 다르게 설정
프리뷰 배포
- 정식 배포 전에 브랜치 단위로 프리뷰 환경 구성
- 디자인/QA 검증용
- 사내 사용자가 실제와 유사한 환경에서 테스트 가능
로컬 개발 환경
- 로컬 개발 시에는 importMap에서 특정 앱만 localhost로 오버라이드
- 예를 들어,
trading
앱만 로컬에서 개발 중이면 아래처럼 설정
{
"@mfa/trading": "http://localhost:8004.js"
}
- 나머지 앱들은 실제 서버에서 가져옴 → 전체 시스템을 로컬에서 실행하지 않아도 됨
번들 사이즈 최적화
- Shared Modules 도입
- 공통 라이브러리들을 하나의 번들로 묶음
- 각 앱에서는 이 공통 모듈을 import
- 중복 로딩 방지 + 초기 로딩 성능 개선
브라우저 호환성
- 일부 브라우저에서
importMap
또는ESM
지원 안 되는 경우 존재 - →
SystemJS
를 fallback 용도로 활용 - 모던 브라우저 우선, 비호환 환경에서는 대체 방식 적용
질문과 답변
앱 A에서 앱 B로 넘어갔을 떄 앱 A에 대한 상태를 어떻게 처리하고 있는지?
=> 전역상태나 tanstackQuery의 캐시는 전역적으로 개발해놨기 떄문에 앱A에서 앱B로 접근하더라도 앱 A에대한 상태를 계속 유지할 수 있도록 개발했다. (내부 독자구축)
언제나, 누구에게나, 평등하게 빠른 웹 (실패기)
- 강현구님
사용자 환경은 다양하다.
느린 네트워크, 성능이 낮은 기기 등에서 웹 서비스는 느려질 수밖에 없다.
Toss는 이런 상황에서도 누구나 빠르게 사용할 수 있는 웹을 만들기 위한 다양한 시도를 했다.
SSR 도입: 네트워크 변수 줄이기
- 토스뱅크는 SSR(Server-Side Rendering) 방식 사용
- 클라이언트가 아닌 서버에서 HTML을 렌더링해서 초기 로딩 속도를 확보하려고 시도
CSR, SSR의 한계 → 네이티브 Preload
- CSR이나 SSR 방식만으로는 네트워크 지연을 0으로 만들 수 없었다
- 그래서 앱(natvie)에서 미리 CSS/JS를 preload하는 전략을 시도
진입점 선정: 어디서 preload 할 것인가?
- 많은 사용자가 반드시 거치는 진입점은 Toss 메인 홈 화면
- Toss 앱이 열릴 때, 사용자에게 토스뱅크 리소스를 미리 preload 시킴
Preload 전략: 누가 preload 대상인가?
- 최근 5일 이내에 토스뱅크에 진입한 사용자만 preload 대상
ETag
기반 캐싱 사용 → 이미 최신 리소스를 가진 사용자는 다시 preload하지 않음
왜 Fade Out 되었나 (실패 원인)
- 배포 빈도 문제
- 하루에도 수차례 배포가 발생 → preload 버전과 실제 앱 버전이 맞지 않음
- 효과가 제한적
- 배포 후 실제로 preload 효과를 보는 유저 수가 극히 적었음
- 비용 부담
- 모든 잠재적 유저에게 preload를 시도 →
CDN 요청 급증 → 월 5,000~6,000달러 추가 발생
- 모든 잠재적 유저에게 preload를 시도 →
결국, 유지비와 실효성의 문제로 전략을 중단하게 됨.
Q. CSS, JS 최적화를 시도하지는 않았는가?
A. 맞다.
Toss는 애니메이션 등 UI 표현이 많은 편이라,
CSS/JS 최적화에는 많은 리소스를 들이고 있음.
하지만 preload 전략은 그걸 넘어서 네트워크 레벨에서의 속도 개선을 노린 별도 시도였음.
더 나은 UX를 위한 프론트엔드 전략
- 강근우님, 한정원님
좋은 UX란?
UI 구성, 로딩 속도, 화면 안정성이 핵심. 이 중에서도 특히 로딩과 안정성 개선에 집중한 사례를 공유함.
로딩 속도 개선 - SSR을 잘 활용하기
Toss는 Next.js 기반의 SSR 웹뷰를 사용하고 있으며, 로딩 과정을 아래 3단계로 나누어 이해함:
- 네트워크 구간
- 스크립트 실행 구간
- 데이터 패칭 구간
SSR을 잘 활용하면 HTML이 사용자에게 빠르게 전달되어, JS 로딩 전에도 화면을 확인할 수 있음.
과거에는 SSR을 도입하고도 적절하게 활용하지 못해 큰 효과를 보지 못함.
SSR 최적화: 어떤 영역을 먼저 보여줄까?
예시: 계좌 상세 페이지
→ 상단의 금액 영역은 SSR로 렌더링하고, 거래 내역은 CSR로 처리
비즈니스적 이유
- 사용자가 가장 먼저 확인하고 싶어하는 정보는 금액
- 빠르고 정확하게 보여주는 것이 중요
기술적 이유
- 거래 내역 API 응답 시간이 길고, 데이터도 커서 HTML이 비대해짐
- 모든 데이터를 SSR로 포함시키면 오히려 렌더링이 늦어질 수 있음
결과적으로 첫 화면 로딩 속도가 개선되었고, 사용자는 더 빠르게 핵심 정보를 확인할 수 있게 됨.
클라이언트 캐싱 전략
거래 내역은 여전히 클라이언트 측에서 API 호출을 통해 렌더링됨.
이를 보완하기 위해 렌더링 시 데이터를 로컬스토리지에 저장하고,
다음 진입 시에는 저장된 데이터를 활용해 즉시 화면을 구성함.
자바스크립트 평가 시간 단축 (번들 사이즈 다이어트)
도구: Webpack Bundle Analyzer
최적화 방식
- 미사용 의존성 제거
- 용량이 큰 라이브러리는 더 가벼운 라이브러리로 교체
- 동일한 라이브러리의 여러 버전을 통합
참고: SLASH 21. JavaScript Bundle Diet 발표 내용
API 호출 Waterfall 문제
function Component() {
const data1 = useSuspenseQuery();
const data2 = useSuspenseQuery();
}
위처럼 작성하면 API가 순차적으로 호출되어 전체 렌더링이 늦어지는 문제가 발생함.
서비스 단에서 발생하는 API 워터풀 현상을 파악하고, 병렬 호출 구조로 개선할 필요가 있음.
화면 안정성 개선 사례
금액 애니메이션 문제
- SSR로 내려온 금액이 애니메이션 시작과 함께 사라지는 이슈 → 깜빡이는 것처럼 보임
- 디자이너와 협업해 0원부터 카지노처럼 숫자가 증가하는 방식으로 변경
송금 버튼 깜빡임
- 초기엔 disabled 상태였다가 조건 충족 시 활성화 → 깜빡이는 것처럼 느껴짐
- 송금 가능한 유저가 더 많다는 점을 반영해 기본값을 enabled로 설정
광고 배너로 인한 Layout Shift
- 클라이언트에서 API 호출 후 렌더링되면서 다른 UI 요소들이 밀리는 현상 발생
- 기술적으로 해결 어려운 부분은 디자인 측면에서 해결
토스뱅크 LCP 개선 사례
API 통합
HTTP/1 환경에서는 Promise.all로 병렬 호출해도 한 도메인당 5~6개 요청 제한 존재.
Toss는 40개 이상의 API를 호출 중이었고, 이로 인해 대기 시간 증가.
→ 네트워크 waterfall 분석을 통해 병목 구간을 확인
→ 백엔드에서 여러 API를 통합하여 한번에 내려주는 방식으로 변경
→ 100ms 이상 지연되는 API들을 중심으로 통합 → 결과적으로 99.9% 개선
SSR/CSR 혼합 전략
위쪽 영역은 SSR로 처리하고, 스크롤해야 보이는 하단 영역은 CSR로 처리
요청 우선순위 조정 등을 통해 LCP 기준 56초 → 2.53초로 약 50% 개선
변경 이력 관리
캘린더에 LCP 관련 개선 사항과 변경 이력을 기록
LCP 성능에 영향을 주는 주요 변경점을 추적하고,
자동화된 알림 시스템을 통해 매주 LCP 성능을 모니터링함
100년 가는 프론트엔드 코드, SDK
- 박성현님, 최진영님
안정성: 테스트 & 로그
- 안정적인 SDK를 위해 철저한 테스트 체계와 로깅 시스템 구축이 필수
- 다양한 환경에서 발생할 수 있는 문제를 사전에 감지하거나, 발생 후 빠르게 추적 가능하게 함
확장성: 레고 블록처럼 유연하게
처음엔 다양한 고객 요구사항을 빠르게 대응하기 위해 비용이 싼 코드로 구현했지만,
점점 확장성과 유지보수 문제가 커졌음. (내부에 조건문으로 각 고객사마다 다른 요구사항을 처리)
→ 해결 방법: 레고 블록 구조
- SDK를 조립형 구조로 재설계
- 고객사마다 필요한 부분만 갈아끼울 수 있는 구조로 만들었음
표준 SDK vs 커스텀 SDK
- 표준 SDK: 대부분의 일반 고객사를 위한 기본형
- 커스텀 SDK: 특정 고객사 요구사항에 맞춰 일부 레이어만 변경한 버전
예: 공통 SDK 구조는 유지한 채, 특정 고객만 필요한 인증 방식이나 로깅 방식만 별도로 구성
이렇게 모듈화된 구조로 유지보수 효율성과 확장성을 동시에 확보
알림부터 대응까지
- 최강혁님
장애는 피할 수 없다.
하지만 구조적으로 대응할 수는 있다.
Toss 프론트엔드는 MSA 구조로, 장애가 발생했을 때 문제를 인지하는 것조차 어려운 상황이 종종 발생함.
장애 대응 프로세스
- 장애를 인지
- 문제를 시스템과 사람에게 알림
- 문제 대응 및 복구
- 근본 원인 해결
장애 인지의 어려움
- Sentry로 프론트엔드 에러 수집 → Slack 알림
- Grafana는 서버/인프라 관측 → Slack으로만 알림 전달 → 사람이 직접 담당자 호출해야 함
문제는 장애 알림이 모두 플랫폼팀으로 몰린다는 점
→ 서비스는 커졌지만, 장애 대응은 스케일업되지 못함
해결 방향: 자동화와 분산
- 장애를 자동 분류하고
- 분류된 장애를 자동으로 적절한 담당자에게 전달하는 시스템 구축
Sentry 알림 구조 개선
기존 구조
- Sentry → Slack (알림만 뜨고 담당자가 누군지 명확하지 않음)
개선된 구조
- Sentry → Opsgenie (우선순위에 따라 알림)
- 코드 오너 → 대리인 → 플랫폼팀
- Opsgenie 알림과 동시에 Slack 알림도 발송
- terraform으로 알림 대상자 설정을 자동화해, 수동 변경 부담 제거
Alert 관리 체계 (3단계)
- 응답 여부 확인 – 알림이 제대로 전달됐는가
- 해결 여부 확인 – 일시적 해결인지, 지속적 문제인지
- 개발자 조치 여부 – 실제로 원인을 파악하고 수정했는가
Alert 과잉의 문제
- 무해하지만 반복되는 알림 → 무시되기 시작
- Alert 신뢰도를 지키기 위해 중요도 낮은 반복 알림을 제외
예외 처리 전략
- 중요하지 않은 에러는 필터링하거나 공통 처리를 통해 제거
- 하지만, 반복되는 에러가 완전히 무시되지 않도록 최근 7일간 가장 많이 발생한 에러 목록을 Slack으로 전송
시스템에 직접 영향이 없더라도 반복되는 에러는 개발자의 주의를 요함
너무 빡빡한 대응이 되지 않도록 유연한 기준도 마련
누구나 작성하는 E2E 테스트를 향하여: 접근성에서 찾은 답
- 강민우님
E2E 테스트 유지보수 실패 경험
- 테스트를 위한 코드(
data-testid
)가 실제 서비스 코드에 과도하게 침투 - 기능 수정 시마다 API 모킹도 전부 손봐야 함
- 테스트가 너무 거대해져 CI/CD 속도가 현저히 느려짐
이를 극복하기 위한 시도
- CI/CD에서 E2E 테스트를 제거하고, 별도의 레포지토리에서 테스트 실행
- 하지만 이건 근본적인 해결이 아니었고, 강제성도 떨어져 원래 상태로 복귀하게 됨
고민: 어떻게 하면 E2E 테스트를 “잘” 작성할 수 있을까?
- 모든 FE 개발자가 작성하는 게 맞는가?
- 혹시 누구나 쉽게 작성할 수 있게 만들 수는 없을까?
Cake: 노코드 기반 E2E 테스트 플랫폼
핵심 철학
- E2E 테스트는 개발자뿐 아니라 비개발자도 쉽게 작성할 수 있어야 한다
- 노코드 기반 인터페이스를 통해 접근 장벽을 낮춘다
모델 기반 테스트 작성
- 논리적인 사용자 행동 흐름을 모델로 표현
- 전통적인 코드 방식이 아닌 시각적이고 추상적인 모델로 테스트 시나리오를 설계
- 모든 테스트 상황을 시각적으로 볼 수 있어 누락된 케이스 파악이 쉬움
노코드 테스트 시나리오 구성 방식
Chrome Extension 활용
- 요소를 직접 클릭하며 테스트 시나리오를 쌓을 수 있음
- ‘재생’ 버튼을 누르면 시나리오대로 브라우저가 자동 실행
- 시나리오를 시각적으로 구성하고 개별 실행도 가능
- 접근성을 지킨 요소인지 자동으로 확인할 수 있음
Chrome Extension 내부의
debug
메서드를 활용해 요소 하이라이트 등 UX 보조 기능도 제공
기술 스택 및 도전
Chrome DevTools Protocol (CDP)
- Chrome과의 직접 통신을 통해 브라우저 조작
- CDP는 공식 문서와 예제가 부족해, 직접 실험하며 익혀야 했음
- Protocol Monitor를 통해 CDP 명령 실행 상태를 추적 가능
Puppeteer
- CDP의 저수준 명령어들을 고수준 API로 사용할 수 있게 해줌
- 특정 브라우저 탭에 접근해 동작 제어 가능
- Chrome Extension과도 연동 가능
요약
- E2E 테스트는 누구나 작성할 수 있어야 한다는 철학 아래, 접근성을 기준으로 노코드 도구를 개발
- Chrome Extension + CDP + Puppeteer 조합을 통해 테스트 시나리오를 시각화하고 실행
- 접근성과 자동화라는 두 가지 키워드가 개발자 경험과 테스트 품질을 동시에 향상시킴
세션을 다 듣고난 후기
-
전체적으로 서비스 개발 과도기를 지나 구현단계에서 벗어나 성능이나 로그, 안정화를 강화하고 있다는 느낌을 받았다.
-
개발자가 본격적으로 많아지기 시작했었던 타이밍에 개발을 시작했다면 많은 개발자들이 현재 구현단계를 지나서 성능이나 테스트, UX 단계에 많은 관심이 있었을 것 같다. 따라서 이번 컨퍼런스는 많은 개발자들에게 인사이트를 주거나 도전해보고 싶었지만 못하고 있었던 부분들을 다시 시작하게 해주는 계기가 되지 않았나 싶다.
-
사용자 로그를 파악해서 사용성을 분석해보고 싶었는데, 디자인 시스템에 붙여서 테스트하는건 생각하지 못했던 접근 방식이었던 것 같다. 현재 우리 서비스에 적용하기에는 적합하지는 않지만 재밌었다.
-
최근에 Module Federation을 적용해보면서 Micro FrontEnd를 경험해보고 있는데 웹뷰환경이 기반인 토스에서는 어떤 고민들에 의해서 적용하고 있고, Web의 Native한 ESModule을 이용해서 Mapping하고 적용하는 부분이 흥미롭게 들렸다. 특히 라우팅 관련이나 전역 상태관련해서 어떻게 처리했을지 궁금한데 세부 코드단까지는 공개가 되지 않았기 때문에 기회가 된다면 직접 구현해보고 싶다.
-
역시 프론트엔드계의 끝판왕 토스답게 UX를 위해서 다양한 전략을 고민하고 적용해가는 것들이 굉장히 재밌게 들렸다. 현재 회사에서 UX가 좋지 않은 부분이 상당수 존재한다. 계획을 세우고 디테일을 챙겨보자.
-
E2E의 고민을 끝내기 위해서 Chrome Extension으로 만든 것이 인상깊었다. 나도 회사에서 Chrome Extension으로 프로덕트에서 생산성을 올리기 위한 도전을 한 적이 있는데 상당히 재밌게 들렸고, 양질의 레퍼런스 자료를 찾을 수 없다는 부분에 핵공감했다. 신기한게 수천, 수만개의 Chrome Extension이 개발되었을터인데 참고할 수 있는 자료가 많지 않았고 공식문서만을 보면서 직접 실험을 할 수 밖에 없었다. 조만간 Chrome Extension으로 또 재밌는걸 만들어봐야지.