← /blog # Migration 2026.01.20 11 min

ASP.NET 레거시를 6주에 Next.js로 — 기능 삭제 없이

흰 화면 깜빡임 없는 SPA로 옮기되 기존 동작은 100% 유지. 페이지 단위 우선순위와 위험 관리, 그리고 마이그레이션의 사회학.

마이그레이션의 신뢰는 "기존 기능이 하나도 사라지지 않았다"에서 시작한다. 새 기술의 장점을 보여주기 전에, 먼저 기존 동작을 증명해야 한다.

시연 첫인상 — 흰 화면이 깜빡인다

Daqda에서 장지(묘지) 위치를 시각화하는 서비스 The Prestige를 담당하고 있었다. 연세 세브란스 같은 대형 고객사 시연을 앞두고, 기존 ASP.NET 서비스의 문제가 선명해졌다. 필터를 하나 바꿀 때마다 페이지 전체가 새로고침되면서 흰 화면이 깜빡였다. step별로 다른 페이지로 이동해야 했고, 뒤로가기를 누르면 사용자가 선택한 필터가 날아갔다.

서버 사이드 렌더링 기반 MPA 구조의 본질적 한계였다. 패치할 수 있는 문제가 아니라, 아키텍처를 바꿔야 하는 문제였다. Next.js 기반 SPA로 전면 재구축하기로 결정했다. 시간은 6주.

페이지 단위 우선순위

8개 페이지를 한꺼번에 옮기면 6주 안에 끝나지 않는다. 페이지별로 우선순위를 매겼다.

  • P0 — 시연에 반드시 필요한 페이지: 지도 뷰, 장지 상세, 필터
  • P1 — 시연에 보여주면 좋은 페이지: 통계 대시보드, 관리자 설정
  • P2 — 시연 이후 마이그레이션: 레거시 보고서, 기타 유틸리티

P0을 먼저 완성하고 시연에서 검증한 뒤, P1과 P2를 순차적으로 진행했다. 이 순서 덕분에 6주 중 4주 시점에 시연이 가능했다.

프론트엔드 주도 API 설계

레거시 ASP.NET의 API 응답은 프론트엔드 요구에 맞지 않았다. 서버 사이드 렌더링을 전제로 설계된 데이터 구조였기 때문이다. 백엔드에 API 응답 형식을 직접 제안하고 협의했다.

typescript types/api.ts
// FE가 필요한 데이터 구조를 인터페이스로 정의하고 BE에 전달
interface GraveSiteResponse {
  id: number;
  name: string;
  location: {
    lat: number;
    lng: number;
    address: string;
  };
  filters: {
    type: GraveType;
    status: GraveStatus;
    capacity: number;
  };
  // ASP.NET 시절에는 이 데이터가 3개 엔드포인트에 분산되어 있었다
}

프론트엔드가 필요한 형태를 인터페이스로 정의해서 백엔드에 전달하니, 불필요한 API 호출이 줄고 데이터 변환 로직도 사라졌다. 프론트엔드 개발자가 API 설계에 참여하는 것은 마이그레이션에서 특히 중요하다.

Kakao Map + Recoil + LocalStorage

지도 기반 서비스의 핵심은 필터 변경 시 지도가 새로고침되지 않는 것이다. Recoil로 필터 상태를 관리하고, Kakao Map API의 마커를 클라이언트 사이드에서 실시간으로 업데이트했다. 기존 ASP.NET에서는 필터를 바꾸면 새 페이지로 이동했는데, SPA에서는 같은 화면에서 마커만 바뀐다.

typescript store/filterState.ts
import { atom } from 'recoil';
import { recoilPersist } from 'recoil-persist';

const { persistAtom } = recoilPersist({
  key: 'prestige-filters',
  storage: typeof window !== 'undefined' ? localStorage : undefined,
});

export const graveFilterState = atom<GraveFilter>({
  key: 'graveFilter',
  default: { type: 'all', status: 'available' },
  effects_UNSTABLE: [persistAtom],
});

recoil-persist로 필터 상태를 localStorage에 저장했다. 이전에는 사용자가 뒤로가기를 누르면 선택한 필터가 날아가서 전화로 CS를 해야 했는데, 이 문제가 완전히 사라졌다.

배포 — 10분에서 2분으로

기존 배포 과정은 이랬다. 로컬에서 빌드 → 빌드 파일을 Windows Server 접근 권한이 있는 사람에게 전달 → 그 사람이 서버에 수동 업로드. 10분이 걸렸고, 접근 권한 문제로 빌드 파일을 넘겨야 하는 비효율적인 중간 단계가 있었다.

Vercel 기반 CI/CD를 구축했다. git push하면 자동으로 빌드, 린팅 검사, 배포가 실행된다. 배포 시간은 2분으로, 80% 단축되었다.

마이그레이션의 사회학

코드 마이그레이션보다 어려운 것은 사람을 설득하는 것이다. CTO와 백엔드 개발자에게 "왜 새로 만들어야 하는가"를 납득시키려면, 기존 시스템의 문제를 감정이 아니라 사실로 보여줘야 한다. 시연 중 흰 화면이 깜빡이는 것을 직접 보여주고, 페이지 로딩 시간을 측정해서 비교한 것이 결정적이었다.

마이그레이션 완료 후 알파 테스터들로부터 "훨씬 좋은 경험"이라는 피드백을 받았다. 가장 큰 차이는 필터를 바꿀 때 흰 화면이 없어진 것이었다.

결과

  • 8개 페이지, 설계 포함 6주 완료
  • 기능 삭제 0건 — 기존 동작 100% 유지
  • 배포 시간 10분 → 2분 (80% 단축)
  • 필터 변경 시 전체 새로고침 제거 → SPA 내 실시간 반영
  • LocalStorage 기반 상태 유지로 CS 전화 문의 감소
  • 알파 테스터 긍정 피드백 확보