에듀넷 티-클리어 클라우드 전환 및 서비스 재구조화
🔎 프로젝트 개요
📅 프로젝트 기간: 2024.08 ~ 2025.02
💡프로젝트 소개
초중고 학생 대상 온라인 학습 콘텐츠 플랫폼으로, 자체 CMS 기반 학습 관리 시스템의 클라우드 전환 및 현대화 프로젝트입니다.
레거시 기반(Java, Spring MVC, JSP)으로 운영되던 공공 교육 서비스를 Vue.js 3 기반의 SPA 아키텍처와 MSA 기반 백엔드 구조로 전환하여 클라우드 환경에 맞춘 구조 재설계 및 마이그레이션을 수행했습니다.
⚙️ Skills
- Frontend: Vue.js 3, Pinia, Vite
- Backend: Java, Spring Boot, MyBatis, MySQL, MSA
🤔 왜 Vite를 선택했는가?
빌드 도구로는 Webpack과 Vite가 주요 선택지였지만, Vite를 선택한 이유는 다음과 같습니다.
빠른 개발 서버 및 HMR(Hot Module Replacement)
- Vite는 ES Modules 기반의 네이티브 브라우저 로딩을 활용하여 개발 서버 구동 속도가 Webpack 대비 10~100배 빠름
- HMR 속도 또한 월등하여 코드 변경 시 즉각적인 피드백 가능
- 대규모 프로젝트에서도 빠른 개발 경험 제공
낮은 설정 복잡도
- Webpack은 설정 파일(webpack.config.js)의 복잡도가 높고 러닝커브가 큼
- Vite는 기본 설정만으로도 대부분의 요구사항 충족, 추가 설정이 필요한 경우에도 간결한 구조
- 팀원들의 빌드 도구 학습 시간 단축
최적화된 프로덕션 빌드
- Rollup 기반 번들링으로 Tree Shaking, Code Splitting 등 최적화 자동 적용
- ES Modules를 기본으로 지원하여 모던 브라우저 환경에 최적화
- 빌드 속도도 Webpack 대비 빠름
플러그인 생태계
- Vue 공식 플러그인(@vitejs/plugin-vue) 완벽 지원
- 필요한 플러그인 대부분이 제공되며, 설정이 간단함
이러한 이유로 Vite를 선택하여 개발 생산성과 빌드 성능을 동시에 확보할 수 있었습니다.
🧩 담당 역할
- Vue.js 3 SPA 프론트엔드 아키텍처 설계 주도
- Vue Router + Pinia 기반 통합 인증/권한 제어 시스템 설계 및 구현
- 조건부 권한 처리 및 역할별 조건부 렌더링 개발
- Composables를 통한 서버 자원 상태 관리 및 로딩 공통화
- 번들 최적화 및 CSS 구조 개선 주도
- MSA 환경에서의 API 요청 흐름 설계 및 중복 요청 관리
🧨 문제 인식
레거시 아키텍처의 한계
- JSP 기반 서버 사이드 렌더링 방식으로 인한 느린 페이지 전환
- 모놀리식 구조로 인한 확장성 부족
- 클라우드 환경에 적합하지 않은 구조
인증 및 권한 관리의 복잡성
- 관리자, 교사, 학생 등 다양한 사용자 유형에 따른 접근 권한 분기 필요
- 각 화면별로 구현된 인증 체크 로직으로 인한 일관성 부족 및 유지보수 어려움
- 조건부 권한 처리(예: 특정 역할 + 특정 조건 만족 시 접근 가능)의 복잡성
번들 크기 과다
- 다수 외부 라이브러리 사용 및 의존성 최적화 미흡으로 초기 번들 크기 1.5MB 도달
- 초기 로딩 지연 및 페이지 프리징 발생
- 사용자 경험 저하
CSS 구조 문제
- Legacy CSS 파일 이관으로 단일 Global CSS 파일에 모든 스타일 선언 (빌드 시 index.css 크기 1MB)
- 페이지와 무관한 스타일까지 초기 로드로 렌더링 지연 발생
- 스타일 중복 및 유지보수 어려움
MSA 전환에 따른 API 관리 복잡도
- 모놀리식에서 MSA로 전환하면서 API 요청 수 증가
- 중복 요청 및 불필요한 네트워크 트래픽 발생 우려
- 여러 백엔드 서비스와의 통신 관리 필요
🛠️ 해결 방안
🧩 Vue.js 3 SPA 아키텍처 설계
CSR 기반 라우팅 처리
- Vue Router를 활용한 클라이언트 사이드 라우팅 구현
- 페이지 전환 시 전체 새로고침 없이 필요한 컴포넌트만 렌더링
- 빠른 페이지 전환 및 사용자 경험 향상
컴포넌트 기반 구조
- Atomic Design 패턴 적용으로 재사용 가능한 컴포넌트 구조 설계
- 페이지별 독립적인 모듈화로 유지보수성 향상
🧩 인증 기반의 통합 접근 제어 시스템 구축
에듀넷 사용자 서비스는 다양한 사용자 유형(관리자, 교사, 학생)에 따라 접근 권한이 분기되며, 로그인 여부 및 권한에 따라 진입 가능한 화면이 구분되어야 했습니다. 초기에는 각 화면에서 개별적으로 접근 제어를 구현하거나, 인증 체크 로직이 일관되지 않게 분산돼 있어 유지보수가 어렵고 사용자 흐름에 혼란을 주는 문제가 있었습니다.
Vue Router meta 기반 전역 접근 제어
각 라우트에 로그인 필요 여부 및 역할 정보를 정의하고, Navigation Guard를 활용하여 통합 접근 제어를 구현했습니다.
// router/index.js const routes = [ { path: '/admin', component: AdminLayout, meta: { requiresAuth: true, roles: ['ADMIN'], }, children: [ { path: 'users', component: UserManagement, meta: { requiresAuth: true, roles: ['ADMIN'], }, }, ], }, { path: '/teacher', component: TeacherLayout, meta: { requiresAuth: true, roles: ['TEACHER', 'ADMIN'], }, }, { path: '/student', component: StudentLayout, meta: { requiresAuth: true, roles: ['STUDENT'], }, }, ];
Navigation Guard를 통한 조건부 권한 처리
페이지 이동 시 전역
beforeEach 가드에서 인증 및 권한을 검사하여, 비로그인 사용자는 로그인 페이지로, 권한 부족 사용자는 이전 페이지나 공용 화면으로 리다이렉트하는 방식으로 라우팅 흐름을 일관화했습니다.// router/index.js router.beforeEach(async (to, from, next) => { const authStore = useAuthStore(); // 인증이 필요한 페이지인지 확인 if (to.meta.requiresAuth) { // 로그인 여부 확인 if (!authStore.isAuthenticated) { return next({ path: '/login', query: { redirect: to.fullPath }, }); } // 역할 권한 확인 const requiredRoles = to.meta.roles; if (requiredRoles && requiredRoles.length > 0) { const hasPermission = requiredRoles.some(role => authStore.userRoles.includes(role) ); if (!hasPermission) { return next({ path: '/unauthorized', query: { from: to.fullPath }, }); } } // 조건부 권한 처리 if (to.meta.condition) { const conditionMet = await to.meta.condition(authStore.user); if (!conditionMet) { return next(from.fullPath || '/'); } } } next(); });
Pinia를 활용한 인증 정보 중앙 관리
사용자 정보는 인증 서버에서 받아온 값을 Pinia 상태 관리 스토어에 저장하여 접근성과 유지성을 높였습니다.
// stores/auth.js import { defineStore } from 'pinia'; export const useAuthStore = defineStore('auth', { state: () => ({ user: null, userRoles: [], }), getters: { isAuthenticated: (state) => !!state.user, hasRole: (state) => (role) => state.userRoles.includes(role), hasAnyRole: (state) => (roles) => roles.some(role => state.userRoles.includes(role)), }, actions: { async login(credentials) { const response = await authApi.login(credentials); this.user = response.user; this.userRoles = response.roles; }, async fetchUser() { const response = await authApi.getUser(); this.user = response.user; this.userRoles = response.roles; }, logout() { this.user = null; this.userRoles = []; }, }, });
커스텀 디렉티브 v-role을 통한 UI 레벨 권한 제어
페이지 내 특정 UI 영역에 대해서는 커스텀 디렉티브인
v-role을 활용하여, 특정 역할에만 노출되는 콘텐츠를 조건부 렌더링할 수 있도록 구현했습니다.// directives/role.js import { useAuthStore } from '@/stores/auth'; export const vRole = { mounted(el, binding) { const authStore = useAuthStore(); const requiredRoles = Array.isArray(binding.value) ? binding.value : [binding.value]; const hasPermission = requiredRoles.some(role => authStore.userRoles.includes(role) ); if (!hasPermission) { el.style.display = 'none'; // 또는 el.remove()로 DOM에서 완전히 제거 } }, };
사용 예시
<template> <div> <!-- 관리자에게만 보이는 버튼 --> <button v-role="'ADMIN'" @click="deleteUser">사용자 삭제</button> <!-- 교사 또는 관리자에게만 보이는 섹션 --> <section v-role="['TEACHER', 'ADMIN']"> <h2>성적 관리</h2> <!-- ... --> </section> </div> </template>
효과
- 중복된 인증/권한 로직 제거 및 유지보수성 향상
- 신규 화면 추가 시 meta 설정만으로 제어 가능한 구조 확립
- 조건부 권한 처리를 체계적으로 관리
- 불필요한 네트워크 요청 감소 및 사용자별 맞춤형 인터페이스 제공
🧩 번들 크기 최적화 (67% 감소)
rollup-plugin-visualizer를 활용한 번들 분석
번들 시각화 도구를 활용하여 모듈별 번들 비중을 분석하고, 경량화 우선순위를 도출했습니다.
// vite.config.js import { visualizer } from 'rollup-plugin-visualizer'; export default defineConfig({ plugins: [ vue(), visualizer({ open: true, gzipSize: true, brotliSize: true, }), ], });
의존성 최적화
분석 결과를 기반으로 라이브러리를 최적화했습니다.
ES Module 라이브러리 교체
lodash→lodash-es(Tree shaking 지원)
- 기존 코드 수정이 많이 필요했지만, 사전에 번들 최적화를 고려했어야 한다는 교훈 획득
// Before import _ from 'lodash'; _.debounce(fn, 300); // After import { debounce } from 'lodash-es'; debounce(fn, 300);
선택적 import
- CK Editor, Swiper 등 대용량 라이브러리는 필요한 모듈만 import
// Before import Swiper from 'swiper'; // After import { Navigation, Pagination } from 'swiper/modules';
Code Splitting 및 Chunk 분할
컴포넌트 Lazy Loading
- Modal, Popup 등 기능성 컴포넌트는 Lazy Loading 처리
// router/index.js const routes = [ { path: '/admin/user-modal', component: () => import('@/components/modals/UserModal.vue'), }, ];
대용량 라이브러리 Chunk 분리
- CK Editor 등 대용량 라이브러리는
manualChunks를 통해 별도 Chunk로 분리
// vite.config.js export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { 'ckeditor': ['@ckeditor/ckeditor5-vue', '@ckeditor/ckeditor5-build-classic'], 'swiper': ['swiper'], 'vendor': ['vue', 'vue-router', 'pinia'], }, }, }, }, });
효과
- 번들 크기 67% 감소: 1.5MB → 500KB
- 초기 로딩 속도 유의미한 개선을 통한 사용자 경험 향상
- 팀원들과 공통된 최적화 전략 수립 및 기준 공유
- 번들 최적화는 초기 설계 단계부터 고려해야 한다는 인사이트 획득
🧩 CSS 구조 최적화 (50% 감소)
디자인팀과 협업을 통한 개선 전략 수립
기존 CSS 크기(1MB)를 수치화하여 문제점을 분석하고, 디자인팀과 협의하여 개선 전략을 수립했습니다.
Critical CSS + Module CSS 분리 전략
Critical CSS 추출
- 기본 HTML 태그 스타일
- 레이아웃에서 공통으로 사용하는 class
- 모든 페이지에서 필요한 최소한의 스타일
페이지별 Module CSS 분리
- 특정 페이지에서만 사용하는 스타일은 해당 페이지 컴포넌트에 포함
- Vue의
<style scoped>활용
<!-- GlobalLayout.vue --> <style> /* Critical CSS - 전역 로드 */ @import '@/assets/styles/critical.css'; </style> <!-- SpecificPage.vue --> <style scoped> /* 페이지별 스타일 - 필요 시에만 로드 */ .specific-layout { /* ... */ } </style>
PostCSS를 활용한 불필요한 CSS 제거
실제 사용되는 스타일을 판별하는 과정이 어려웠기 때문에, 별도 프로젝트로 CSS Sanitize 기능을 개발했습니다.
// postcss-css-sanitize (별도 프로젝트) import purgecss from '@fullhuman/postcss-purgecss'; export default { plugins: [ purgecss({ content: ['./src/**/*.vue', './src/**/*.js'], safelist: [ // 동적으로 생성되는 클래스명 /^swiper-/, /^ck-/, // 조건부 렌더링 클래스 'is-active', 'is-disabled', ], }), ], };
Safelist 관리의 어려움
중복적인 화면이 많아 선별 과정이 복잡했고, safelist에 추가하지 않아서 화면이 깨지는 사이드 이펙트가 빈번하게 발생했습니다. 이를 해결하기 위해:
- 화면별로 체계적인 테스트 진행
- 깨진 스타일을 발견할 때마다 safelist에 추가
- 동적 클래스명 패턴을 정규표현식으로 관리
CSS Minify 및 최적화
// vite.config.js export default defineConfig({ css: { postcss: { plugins: [ autoprefixer(), cssnano({ preset: ['default', { discardComments: { removeAll: true }, }], }), ], }, }, });
효과
- CSS 빌드 크기 50% 감소: 1MB → 500KB
- 초기 렌더링 속도 개선
- 페이지별 스타일 관리 분리로 유지보수성 향상
- 디자인팀과 협업 기반 지속 가능한 CSS 관리 체계 구축
🧩 MSA 환경에서의 API 요청 관리
AbortController를 통한 중복 요청 관리
MSA로 전환하면서 API 요청 수가 증가함에 따라, 중복 요청 및 불필요한 네트워크 트래픽을 관리할 필요가 있었습니다.
// composables/useApiRequest.js import { ref } from 'vue'; import { useLoadingStore } from '@/stores/loading'; export function useApiRequest() { const loadingStore = useLoadingStore(); const data = ref(null); const error = ref(null); const abortController = ref(null); const execute = async (apiCall) => { // 이전 요청이 진행 중이면 취소 if (abortController.value) { abortController.value.abort(); } abortController.value = new AbortController(); loadingStore.startLoading(); error.value = null; try { data.value = await apiCall(abortController.value.signal); return data.value; } catch (e) { if (e.name === 'AbortError') { console.log('Request cancelled'); } else { error.value = e; throw e; } } finally { loadingStore.stopLoading(); abortController.value = null; } }; const cancel = () => { if (abortController.value) { abortController.value.abort(); } }; return { data, error, execute, cancel }; }
사용 예시
// 컴포넌트에서 사용 const { data, error, execute, cancel } = useApiRequest(); const searchUsers = async (keyword) => { await execute((signal) => api.searchUsers(keyword, { signal })); }; // 컴포넌트 언마운트 시 요청 취소 onUnmounted(() => { cancel(); });
관리자 Starter 프로젝트와의 통합 및 확장
이전에 구현했던 관리자 Starter 프로젝트의 API 관리 기능을 도입하고, MSA 환경에 맞게 확장했습니다.
- 중복 요청 취소 로직 추가
- 여러 백엔드 서비스별 API 클라이언트 구성 관리
- 에러 핸들링 및 재시도 로직 통합
효과
- 중복 요청으로 인한 불필요한 네트워크 트래픽 감소
- 사용자 경험 개선 (빠른 검색 입력 시 최신 요청만 처리)
- 컴포넌트 복잡도 감소 (로딩, 에러 처리 로직 분리)
🚀 주요 성과
SPA 아키텍처 전환 성공
- 레거시 JSP 기반 시스템을 Vue.js 3 SPA로 성공적으로 전환
- 페이지 전환 속도 대폭 향상 및 사용자 경험 개선
- 클라우드 환경에 최적화된 구조 확립
통합 인증/권한 제어 시스템 구축
- 중복된 인증/권한 로직 제거 및 유지보수성 향상
- 신규 화면 추가 시 meta 설정만으로 제어 가능한 구조 확립
- 조건부 권한 처리 체계화
번들 크기 67% 감소
- 1.5MB → 500KB로 대폭 경량화
- 초기 로딩 속도 유의미한 개선
- 팀원들과 공통된 최적화 전략 수립
CSS 구조 50% 개선
- 1MB → 500KB로 감소
- Critical CSS + Module CSS 분리로 초기 렌더링 속도 개선
- 디자인팀과 협업 기반 지속 가능한 CSS 관리 체계 구축
MSA 환경에서의 효율적인 API 관리
- AbortController를 통한 중복 요청 관리로 네트워크 트래픽 최적화
- 재사용 가능한 API 관리 composable 구축
🔧 프로젝트 진행 시 어려웠던 점과 해결 과정
💡 번들 최적화 과정에서의 기술 부채
문제 상황
lodash를 lodash-es로 교체하는 과정에서 기존 코드의 수정이 많이 필요했습니다. 프로젝트 초기부터 Tree Shaking을 고려한 라이브러리 선택이 이루어지지 않아, 후반부에 대규모 리팩토링이 필요했습니다.
// 전체 코드베이스에서 수정 필요 // Before (100+ 파일) import _ from 'lodash'; _.debounce(fn, 300); _.throttle(fn, 500); _.cloneDeep(obj); // After import { debounce, throttle, cloneDeep } from 'lodash-es'; debounce(fn, 300); throttle(fn, 500); cloneDeep(obj);
교훈
- 프로젝트 초기 단계부터 번들 최적화를 고려한 의존성 선택 필요
- Tree Shaking을 지원하는 ES Module 라이브러리 우선 선택
- 정기적인 번들 크기 모니터링 및 리뷰 프로세스 도입
해결 과정
- 팀원들과 역할을 분담하여 단계적으로 코드 수정
- ESLint 규칙 추가로 향후 lodash 사용 방지
// .eslintrc.js module.exports = { rules: { 'no-restricted-imports': ['error', { paths: [{ name: 'lodash', message: 'Please use lodash-es instead for better tree-shaking.', }], }], }, };
💡 CSS Sanitize 과정의 복잡성
Safelist 관리의 어려움
PostCSS를 활용한 CSS Sanitize 과정에서, 실제 사용되는 스타일을 판별하는 것이 매우 어려웠습니다. 특히 중복적인 화면이 많고 동적으로 생성되는 클래스명이 많아, safelist에 추가하지 않아서 화면이 깨지는 사이드 이펙트가 빈번하게 발생했습니다.
문제 사례
/* 동적으로 생성되는 클래스 - safelist 누락 시 제거됨 */ .is-active { } .has-error { } .swiper-button-next { } .ck-editor__editable { }
해결 과정
- 체계적인 테스트 프로세스 수립
- 모든 주요 화면에 대한 시각적 회귀 테스트 진행
- 깨진 스타일 발견 시 즉시 safelist에 추가
- 동적 클래스명 패턴 정규표현식 관리
// postcss.config.js safelist: [ // 라이브러리 관련 /^swiper-/, /^ck-/, // 상태 관련 /^is-/, /^has-/, // 동적 생성 클래스 /^status-/, /^level-/, ]
- 별도 CSS Sanitize 프로젝트 개발
- 메인 프로젝트와 분리하여 안전하게 테스트 가능
- 단계적으로 적용하여 리스크 최소화
교훈
- CSS 정리 작업은 예상보다 많은 시간과 노력이 필요
- 동적 클래스명 사용 시 명확한 네이밍 컨벤션 필요
- 시각적 회귀 테스트 도구(Percy, Chromatic 등) 도입 검토 필요
💡 조건부 권한 처리의 복잡성
문제 상황
단순히 "관리자만", "교사만" 접근 가능한 것이 아니라, 복합적인 조건이 필요한 경우가 많았습니다.
- 예: "본인이 작성한 글이거나 관리자인 경우"
- 예: "특정 기간 내에만 접근 가능하며 학생 역할인 경우"
해결 과정
Router meta에
condition 함수를 추가하여 유연한 조건부 권한 처리를 구현했습니다.// router/index.js { path: '/post/:id/edit', component: PostEdit, meta: { requiresAuth: true, roles: ['STUDENT', 'TEACHER', 'ADMIN'], condition: async (user, to) => { // 관리자는 무조건 허용 if (user.roles.includes('ADMIN')) { return true; } // 본인이 작성한 글인지 확인 const postId = to.params.id; const post = await api.getPost(postId); return post.authorId === user.id; }, }, }
효과
- 복잡한 권한 로직을 선언적으로 관리 가능
- 비즈니스 로직이 Router 설정에 명확히 드러남
- 유지보수성 향상
🤔 아쉬운 점 및 개선 방향
웹 성능 모니터링 체계 부재
현재 상황
번들 최적화와 CSS 구조 개선을 통해 초기 로딩 속도를 개선했지만, 실제 사용자 환경에서의 성능 지표를 지속적으로 모니터링하는 체계가 부족했습니다.
개선 방향
Real User Monitoring (RUM) 도입
- Google Analytics, Sentry 등을 활용한 실사용자 성능 데이터 수집
- Core Web Vitals (LCP, FID, CLS) 지속적 모니터링
- 성능 저하 알림 설정
Performance Budget 설정
- 번들 크기, 초기 로딩 시간 등에 대한 명확한 목표 수치 설정
- CI/CD 파이프라인에 성능 체크 통합
// performance-budget.json { "budgets": [ { "path": "dist/index.js", "maxSize": "600kb", "gzip": true }, { "path": "dist/index.css", "maxSize": "350kb", "gzip": true } ] }
기대 효과
- 성능 저하를 조기에 발견하고 대응 가능
- 데이터 기반의 성능 개선 의사결정
- 팀 전체의 성능 인식 향상
E2E 테스트 부족
현재 상황
번들 최적화, CSS 구조 개선, 권한 로직 변경 등 많은 구조적 변경이 있었지만, 이를 검증할 수 있는 자동화된 E2E 테스트가 부족했습니다. 수동 테스트에 의존하다 보니 놓치는 사이드 이펙트가 발생했습니다.
개선 방향
Playwright/Cypress 기반 E2E 테스트 도입
- 주요 사용자 플로우에 대한 E2E 테스트 작성
- 권한별 접근 제어 시나리오 자동화 테스트
- CI/CD 파이프라인 통합
// e2e/auth.spec.js test('학생은 관리자 페이지에 접근할 수 없다', async ({ page }) => { await page.goto('/login'); await page.fill('[name="username"]', 'student@example.com'); await page.fill('[name="password"]', 'password'); await page.click('button[type="submit"]'); await page.goto('/admin'); await expect(page).toHaveURL('/unauthorized'); });
시각적 회귀 테스트 도입
- Percy, Chromatic 등을 활용한 시각적 회귀 테스트
- CSS 변경 시 의도하지 않은 UI 깨짐 방지
기대 효과
- 구조 변경 시 안정성 확보
- 수동 테스트 시간 대폭 감소
- 리팩토링에 대한 자신감 향상
SSR(Server-Side Rendering) 도입 검토
현재 상황
CSR 방식으로 전환하여 빠른 페이지 전환을 구현했지만, 초기 로딩 시 빈 화면이 보이는 문제와 SEO 최적화 부족이 아쉬운 점으로 남아있습니다.
개선 방향
Nuxt.js 도입 검토
- SSR을 통한 초기 로딩 속도 개선
- SEO 최적화
- Code Splitting 자동화
Hybrid Rendering 전략
- 공개 페이지(메인, 소개 등): SSR
- 인증 필요 페이지: CSR
- 정적 콘텐츠: SSG (Static Site Generation)
기대 효과
- 초기 로딩 경험 개선
- 검색 엔진 노출 증대
- 사용자 유입 증가
📚 기술적 성장
대규모 레거시 마이그레이션 경험
- JSP 기반 모놀리식 시스템을 Vue.js SPA + MSA로 전환하는 대규모 프로젝트 경험
- 점진적 마이그레이션 전략 수립 및 실행 역량 향상
프론트엔드 아키텍처 설계 역량
- SPA 아키텍처 설계 및 구축 주도 경험
- 인증/권한 시스템 설계 등 복잡한 비즈니스 로직을 체계적으로 구조화하는 역량 습득
성능 최적화 전문성
- 번들 분석 및 최적화 실무 경험
- Tree Shaking, Code Splitting, Lazy Loading 등 다양한 최적화 기법 적용
- CSS 구조 개선 및 Critical CSS 전략 수립
협업 및 문제 해결 능력
- 디자인팀과 협업하여 CSS 구조 개선
- 번들 최적화 과정에서 팀원들과 우선순위 조율 및 역할 분담 경험
- 기술 부채 해소 과정에서의 의사결정 경험
MSA 환경에서의 프론트엔드 개발
- MSA 환경에서 증가한 API 요청을 효율적으로 관리하는 전략 수립
- AbortController를 활용한 중복 요청 관리 등 실무 노하우 습득