배우고, 기록하고, 성장합니다 | GitHub

백엔드 개발자의 프론트 도전기 — AI + 디자인 시스템 + Tailwind로 버티기

@2026-06-11·18 min read
series: AI로 레거시 서비스 리팩토링하기
📚

AI로 레거시 서비스 리팩토링하기

  1. 01AI로 포크된 코드베이스 파악하기 — 리팩토링의 첫 단계
  2. 02AI 협업 환경 세팅 — CLAUDE.md와 스킬로 컨텍스트 유지하기
  3. 03백엔드 리팩토링 — 단일 모놀리식에서 멀티모듈 클린 아키텍처로
  4. 04백엔드 개발자의 프론트 도전기 — AI + 디자인 시스템 + Tailwind로 버티기← 현재
4 / 4

이 글은 한 사내 서비스의 전면 리팩토링 과정을 기록한 시리즈 네 번째(마지막) 글입니다.


나는 프론트엔드 개발자가 아니다

솔직하게 시작하겠다. 나는 백엔드 개발자다. React를 업무에서 제대로 써본 적이 없었고, TypeScript도 깊이 있게 쓴 경험이 없었다.

그런데 이번 리팩토링에서 프론트엔드를 혼자 처음부터 새로 만들었다.

기존 서비스는 Thymeleaf로 서버에서 HTML을 내려주는 구조였는데, 새 프론트는 React Router v7 기반의 SPA로 완전히 바꿨다. 페이지 수로 치면 적지 않고, 관리자 화면, 등록/수정 플로우, 검색, 상세 페이지 등 실질적인 서비스 기능 전부다.

이게 가능했던 건 AI 덕분이다.

ℹ️이 글의 전제

이 작업은 거의 100%를 AI(Claude Code)로 진행했다. 내가 React 문법을 몰라도 의도를 설명하면 코드가 나왔고, 나는 그 코드가 타입에 맞는지, 동작이 올바른지 검토하는 역할을 했다. 아래는 그 협업이 어떻게 가능했는지에 대한 기록이다.


스캐폴딩은 가져다 썼다

프론트 폴더 구조는 우리 팀에서 이미 만들어둔 사내 프론트엔드 스캐폴딩을 기반으로 세팅했다. React Router v7 기반에 FSD(Feature-Sliced Design) 아키텍처가 잡혀있는 스캐폴딩이다.

FSD 아키텍처가 뭔지

FSD(Feature-Sliced Design)는 프론트엔드 코드를 역할별로 레이어로 나누는 아키텍처 방법론이다. 백엔드로 치면 레이어드 아키텍처와 비슷한 개념인데, 프론트엔드 특성에 맞게 구성된 버전이다.

frontend/src/
├── app/          # 라우터, 전역 설정, entry
├── pages/        # 라우트 단위 페이지 컴포넌트
├── widgets/      # 헤더, 푸터, 레이아웃 등 조합 블록
├── features/     # 사용자 인터랙션 단위 기능
├── entities/     # 비즈니스 엔티티 (Post, User 등)
└── shared/       # 공통 유틸, API 클라이언트, UI 컴포넌트

레이어 간 의존 방향이 정해져 있다. pageswidgets, features, entities, shared에 의존할 수 있지만, shared는 위 레이어를 의존하면 안 된다. 이 규칙 덕분에 어디에 뭘 두어야 하는지 기준이 생긴다.

이 구조를 내가 처음부터 설계한 게 아니다. 스캐폴딩을 그냥 가져다 썼다. 처음엔 "이 컴포넌트를 pages에 두면 되는지 widgets에 두면 되는지" 애매할 때마다 AI한테 물어보면서 감을 잡았다.

라우팅은 React Router v7의 파일 기반 라우팅을 그대로 썼다. URL 패턴과 페이지 컴포넌트가 1:1로 대응되니까 라우트 파일만 보면 서비스의 전체 페이지 목록이 파악되는 구조다.


API 연동 — TanStack Query로 서버 상태 관리

백엔드 API를 연동할 때는 TanStack Query를 사용했다.

처음 쓰면 "왜 이렇게까지 해야 하지?" 싶을 수 있다. 직접 fetch 쓰면 안 되나? 된다. 그런데 API 호출 상태 관리(로딩, 에러, 캐싱, 리패치)를 직접 구현하다 보면 TanStack Query가 왜 있는지 금방 이해된다.

API 관련 코드는 shared/api/ 아래에 모아두고, 페이지 컴포넌트는 데이터를 어떻게 가져오는지 신경 쓰지 않아도 되도록 분리했다. 백엔드 API가 바뀌면 query 파일만 수정하면 된다.

이 패턴도 처음엔 낯설었다. AI에게 "백엔드 DTO 구조 보고 fetch 코드 짜줘"라고 하면 타입 정의부터 쿼리 훅까지 한 번에 나왔다. 나는 타입이 맞는지, 응답 변환이 올바른지 검토하는 역할을 했다.


디자인은 디자인 시스템 먼저, 없으면 Tailwind

프론트엔드팀에서 만들어둔 사내 디자인 시스템이 있었다. 버튼, 인풋, 테이블, 모달 같은 컴포넌트들이 정의되어 있다.

작업 기준은 간단하게 정했다:

스타일 적용 우선순위

버튼 필요하면 디자인 시스템 Button 쓰고, 레이아웃 간격은 Tailwind 유틸리티 클래스로 잡고, 그렇게 해도 안 되는 패턴만 직접 만들었다.

이 우선순위 자체도 CLAUDE.md에 명시해뒀다. 그러면 AI가 코드를 생성할 때도 커스텀 CSS 클래스를 남발하는 대신 디자인 시스템 컴포넌트나 Tailwind 클래스를 먼저 제안한다.

shared/ui에 쌓인 것들

디자인 시스템에 없는 패턴이 반복되면 shared/ui로 올렸다. 실제로 만들어진 컴포넌트들을 보면:

shared/ui/
├── Pagination.tsx      # 디자인 시스템 PaginationWrapper 래핑, 반응형 처리
├── SearchBar.tsx       # 검색 인풋 + 버튼 조합
├── MoreLink.tsx        # "더보기" 링크 패턴
├── SectionHeading.tsx  # 섹션 타이틀 패턴
├── Panel.tsx           # 카드 형태 패널
├── MetaList.tsx        # 메타 정보(날짜, 카테고리 등) 나열
├── TagInput.tsx        # 태그 입력 컴포넌트
└── ...

예를 들어 Pagination은 디자인 시스템에 PaginationWrapper가 있는데 반응형 처리가 필요해서 한 번 감싸뒀고, SearchBar는 검색 인풋과 버튼을 묶은 우리 서비스 패턴이라 직접 만들었다.

"이 패턴이 세 군데 이상 반복되면 공통 컴포넌트로 올리자"는 원칙을 계속 지켰다. AI도 코드를 작성하다가 "이 패턴 이미 다른 페이지에서도 쓰고 있는데 공통으로 빼는 게 어떻겠냐"고 제안하는 경우가 많았다.


Thymeleaf에서 React로 — 페이지를 옮긴 과정

가장 반복적이고 물량이 많았던 작업은 기존 Thymeleaf 템플릿을 React 컴포넌트로 옮기는 것이었다.

기존엔 서버에서 데이터를 내려받아 HTML을 렌더링하는 방식이었고, 새로 만든 화면은 클라이언트가 API를 호출해서 화면을 그리는 구조다. 서버 렌더링에서 클라이언트 렌더링으로 바뀌면서, 필터링이나 페이지네이션, 모달 같은 인터랙션을 서버 요청 없이 처리할 수 있게 됐다.

이 전환 작업에서 AI가 압도적으로 도움이 됐다. 기존 HTML 템플릿을 보여주고 "이거 React 컴포넌트로 바꿔줘, 디자인 시스템 쓰고 Tailwind로 스타일 잡아줘"라고 하면 초안이 나왔다. 그대로 쓸 수 있는 건 아니고 손을 봐야 했지만, 빈 파일에서 시작하는 것과 초안이 있는 것과는 속도가 완전히 달랐다.


코드리뷰 — 프론트엔드 개발자의 시선

기능을 다 만들고 나서 프론트엔드 개발자에게 코드리뷰를 요청했다.

백엔드 개발자가 혼자 만든 React 코드라 걱정이 있었다. 동작은 하는데 프론트엔드 관점에서 이상한 구석이 분명 있을 것이라고.

역시 있었다. 리뷰에서 나온 피드백들을 보면:

  • 상태 관리 위치가 이상한 경우: props를 너무 깊게 내려주거나, 반대로 끌어올려야 할 상태가 낮은 곳에 있는 경우
  • 불필요한 리렌더링: 의존성 배열 관리가 엉성하거나, 메모이제이션이 빠진 곳
  • 컴포넌트 분리 기준: 하나의 컴포넌트가 너무 많은 역할을 하는 경우 (백엔드식으로 생각하면 서비스에 로직이 다 몰린 것과 비슷한 상황)
  • 접근성 이슈: aria-label, role 같은 속성이 빠진 곳
⚠️이건 AI도 잡아주지 못한다

AI는 코드가 동작하는지는 잘 봐주는데, 프론트엔드 관행과 팀 컨벤션 관점의 리뷰는 결국 사람이 해야 한다. 동작하는 코드와 좋은 프론트엔드 코드는 다른 문제였다.

리뷰에서 나온 피드백 중 앞으로도 적용해야 할 기준들은 바로 CLAUDE.md 규칙 파일에 추가했다. 예를 들어 "컴포넌트 내 비즈니스 로직은 커스텀 훅으로 분리한다" 같은 기준이 여기서 나왔다. 이렇게 하면 다음 작업부터는 AI가 이 기준을 반영해서 코드를 제안한다.

협업 품질을 끌어올리는 사이클

↻ 반복할수록 AI 협업 품질이 올라간다

커밋 훅 — 린터를 강제로 돌리다

프론트엔드 파일을 수정하고 커밋할 때 린트를 깜빡하는 경우가 생겼다. 코드리뷰에서 "린트 오류 있어요"가 나오면 민망하다.

그래서 git pre-commit 훅을 만들었다. 프론트엔드 파일이 스테이징된 커밋에서는 자동으로 린터가 돌도록:

SH
#!/bin/sh
FRONTEND_STAGED=$(git diff --cached --name-only | grep "^frontend/")
 
if [ -z "$FRONTEND_STAGED" ]; then
  exit 0
fi
 
echo "🔍 프론트엔드 파일 변경 감지 — 린터 실행 중..."
 
cd frontend && pnpm lint
 
if [ $? -ne 0 ]; then
  echo "❌ 린트 오류가 있습니다. 커밋이 취소됩니다."
  echo "   pnpm lint:fix 로 자동 수정 후 다시 커밋하세요."
  exit 1
fi

프론트엔드 파일이 없는 커밋(백엔드만 변경)에서는 그냥 통과한다. 프론트 파일이 있을 때만 pnpm lint를 실행하고, 오류가 있으면 커밋을 막는다.

프론트엔드 린터는 oxlint + eslint 조합이다. 린트 오류가 있으면 pnpm lint:fix로 자동 수정 가능한 것들은 고치고, 나머지는 직접 수정한 뒤 커밋하면 된다.


백엔드 개발자로서 솔직한 소감

프론트를 혼자 다 만들면서 느낀 것 몇 가지:

잘 된 것. 기능 구현 속도는 생각보다 빨랐다. AI가 React, TypeScript, Tailwind 문법을 다 알고 있으니까 내가 문법을 몰라도 의도를 설명하면 코드가 나왔다. TypeScript 타입 에러도 "이 에러 왜 나는 거야?"라고 물어보면 바로 설명해줬다.

어려웠던 것. UI 감각은 AI도 완전히 대신해주지 못한다. 레이아웃이 어색한지, 간격이 이상한지, 모바일에서 깨지는지는 결국 눈으로 확인해야 한다. 브라우저에서 계속 보면서 조금씩 고쳤다. 특히 반응형 처리가 생각보다 손이 많이 갔다.

예상치 못하게 도움이 됐던 것. 컴포넌트 구조를 잡을 때 AI가 "이 상태는 어디서 관리하는 게 맞는지" 같은 질문에 답해줬다. 어디서 어디로 props를 내려줘야 하는지, 상태를 어느 레벨에서 두어야 하는지 — 백엔드 개발자 입장에서 낯선 개념들인데, 그때그때 물어보면서 해결했다.

💡가장 도움됐던 것

프론트엔드 팀이 만들어둔 디자인 시스템과 스캐폴딩이 있었기 때문에 가능했다. 디자인 기준이 없었거나 컴포넌트 라이브러리가 없었다면 AI만으로는 한계가 있었을 것이다. 좋은 도구가 AI를 더 잘 쓸 수 있게 해준 셈이다.


마무리

4편에 걸쳐서 이번 리팩토링 과정을 정리했다.

요약하면:

  1. 포크된 코드베이스 파악 — AI로 빠르게 살릴 것과 정리할 것을 구분했다
  2. CLAUDE.md와 스킬 세팅 — 컨텍스트를 파일로 유지하면 AI 협업이 일관된다
  3. 멀티모듈 클린 아키텍처 — 계층을 물리적으로 분리하면 빌드 도구가 규칙을 강제한다
  4. 백엔드 개발자의 프론트 — 좋은 스캐폴딩 + 디자인 시스템 + AI면 가능하다

AI 도구를 쓰는 게 단순히 "코드를 빨리 짜주는 것" 이상이었다. 파악, 삭제, 설계, 구현, 검토 전 과정에서 협업 상대 역할을 했다. 그리고 그 협업을 잘 만들려면 AI를 어떻게 세팅하느냐가 결과의 절반을 결정하는 것 같다.