Next.js의 App Router를 기반으로 서버 컴포넌트와 클라이언트 컴포넌트를 적절히 활용하여, 성능과 사용자 경험을 모두 고려한 인터랙티브 UI를 개발했습니다.
SpeakUp 프로젝트는 실시간 인증 상태, 사용자별 데이터, 복잡한 이력 관리 UI 등 여러 상태가 유기적으로 얽혀있는 애플리케이션입니다. 초기 단계부터 명확한 아키텍처 없이 개발을 진행했다면, 컴포넌트 간의 의존성이 복잡해지고 상태 관리가 파편화되어 유지보수가 어려운 '스파게티 코드'가 될 위험이 컸습니다. 따라서 저의 핵심 과제는 확장 가능하고, 테스트하기 쉬우며, 동시에 높은 수준의 사용자 경험을 제공하는 견고한 프론트엔드 아키텍처를 설계하는 것이었습니다.
UI의 렌더링 로직과 상태 관리 로직을 분리하기 위해 MVVM(Model-View-ViewModel) 아키텍처를 도입했습니다. React 환경에서는 이를 커스텀 훅(Custom Hook)을 통해 효과적으로 구현할 수 있었습니다. View(컴포넌트)는 오직 UI를 그리는 역할만 하고, 모든 상태와 비즈니스 로직은 ViewModel(커스텀 훅)이 담당하도록 하여 각 부분의 책임을 명확히 했습니다.
Frontend Architecture
Global State
[그림 1] MVVM 패턴 기반의 프론트엔드 아키텍처
이러한 구조는 컴포넌트를 재사용하기 쉽게 만들고, UI와 독립적으로 비즈니스 로직을 테스트할 수 있게 하여 코드의 품질과 유지보수성을 크게 향상시켰습니다.
사용자 경험의 핵심은 끊김 없는 인증 세션 유지입니다. 이를 위해 `ky` 라이브러리의 hooks 기능을 활용하여 Access Token 만료 시 자동으로 토큰을 재발급하는 인터셉터를 구현했습니다. 이 "똑똑한" API 클라이언트는 401 에러를 감지하면 Refresh Token으로 새로운 Access Token을 요청하고, 원래 실패했던 API 요청을 사용자가 모르게 재시도하여 매끄러운 사용 경험을 제공합니다.
// lib/apiClient.ts - 토큰 자동 재발급 로직const clientApi = ky.create({hooks: {afterResponse: [async (request, options, response) => {// Access Token 만료로 401 에러 발생 시if (response.status === 401 && !isRefreshing) {isRefreshing = true;try {// Refresh Token으로 새 Access Token 요청const { access_token } = await fetchNewToken();setAccessToken(access_token);// 원래 실패했던 요청을 새 토큰으로 재시도request.headers.set("Authorization", `Bearer ${access_token}`);return ky(request);} catch (error) {// 리프레시 실패 시 로그아웃 처리logout();} finally {isRefreshing = false;}}return response;},],},});
또한, 인증(AuthProvider), 테마(ThemeProvider), 전역 알림(ToastProvider) 등 앱 전역에서 필요한 상태는 React Context API 기반의 Provider 패턴으로 설계하여, 어떤 컴포넌트에서도 일관된 상태에 쉽게 접근할 수 있도록 했습니다.
체계적인 아키텍처 설계를 통해 복잡한 상태를 가진 애플리케이션을 안정적으로 구축할 수 있었습니다. 특히 MVVM 패턴과 전역 상태 관리 시스템은 향후 새로운 기능이 추가되더라도 코드 베이스가 복잡해지는 것을 방지하는 견고한 기반이 되었습니다. 자동 토큰 재발급 로직은 사용자가 세션 만료로 인해 겪는 불편함을 최소화하여 서비스의 사용성을 크게 향상시켰습니다. 이 프로젝트를 통해 단순히 기능을 구현하는 것을 넘어, 확장성과 유지보수성을 고려한 아키텍처 설계의 중요성을 깊이 체감할 수 있었습니다.