AI가 코드를 고치는 시대, 프론트엔드 구조는 어떻게 달라져야 할까?

2026-07-30

이전 글에서 AI가 코드를 읽고 수정하는 시대의 아키텍처를 문맥 크기와 모듈 경계의 관점에서 살펴봤다. 그 글의 결론은 질문 하나로 압축됐다.

AI가 특정 기능을 수정할 때 해당 모듈과 소수의 파일만 읽고도 수정과 검증을 안전하게 끝낼 수 있는가.

글을 쓰고 나니 다음 질문이 생겼다. 백엔드는 기능별 모듈로 나누면 되겠는데 프론트엔드는 어떻게 해야 할까. React, Vue, Angular 같은 프레임워크는 사람이 복잡한 화면을 만들고 구성 요소를 재사용하기 쉽게 발전해왔다. AI가 코드 작성과 유지보수의 상당 부분을 담당하는 환경에서도 이 방식이 최선일까.

처음에는 프레임워크 자체가 문제일 수 있다고 생각했다. 컴포넌트를 공통화하고, hook이나 composable을 만들고, 전역 상태를 두고, 재사용을 늘리는 방식이 AI에게는 오히려 읽어야 할 범위를 넓힐 수 있기 때문이다.

관련 연구와 반대 사례를 찾아보니 프레임워크 자체를 문제로 보는 가설은 유지하기 어려웠다. 프레임워크만 바꿔서는 일관된 답을 얻을 수 없었다.

내가 내린 결론은 기능 하나를 바꿀 때 읽어야 할 문맥을 줄이고, 그 경계를 검사 가능한 규칙으로 지켜야 한다는 것이다.

다만 두 목표를 동시에 맞추기는 어렵다. 읽어야 할 문맥을 줄이려면 경계와 규칙이 필요하고, 규칙을 늘리면 AI가 동시에 만족해야 하는 조건도 늘어난다. 두 축을 판단할 때 어떤 근거를 썼고, 그래서 무엇을 하기로 했는지 아래에 적었다.


프레임워크만으로는 답이 나오지 않았다#

프론트엔드 프로젝트는 시간이 지나면 흔히 기술 계층별로 정리된다.

src/
├── components/
├── hooks-or-composables/
├── services/
├── stores/
├── utils/
└── views/

사람에게는 익숙하다. 컴포넌트를 찾으려면 components, API 호출은 services, 상태는 stores를 보면 된다.

하나의 기능을 수정하는 입장에서 보면 얘기가 달라진다. 문서 업로드 기능을 고치기 위해 다음 파일을 돌아다녀야 할 수 있다.

views/DocumentUploadPage
components/FileDropZone
hooks-or-composables/useDocumentUpload.ts
services/document.service.ts
stores/document.ts
utils/file.ts
types/document.ts

기술 계층별로는 정돈되어 있지만 기능은 저장소 전체에 흩어진다. 전역 store나 공통 hook·composable에까지 연결되어 있다면, 변경 영향도를 확인하기 위해 다른 기능도 읽어야 한다.

이 구조를 강제하는 프레임워크는 없다. 여러 프레임워크에서 자주 선택하는 관습일 뿐이다. 문제는 프레임워크 자체보다 기능 하나를 고칠 때 몇 개의 경계를 가로질러야 하느냐에 있다.

프레임워크를 아예 걷어내면 어떨까. 이 방향을 지지하는 것처럼 보이는 결과가 있다.

DesignBench는 React, Vue, Angular와 Vanilla HTML/CSS를 포함한 900개 샘플에서 UI 생성, 편집, 복구를 평가했다. 모델들은 대체로 Vanilla HTML/CSS에서 더 높은 시각적 유사도와 컴파일 성공률을 보였다. 결과가 프레임워크 순서대로 나빠지지는 않았다. 논문은 Vanilla가 가장 좋고 React와 Vue가 비슷하게 뒤따르며 Angular가 유독 어렵다고 정리했다. 생성 과제에서 Vanilla는 컴파일이 사실상 항상 성공했지만, Angular는 성능이 높은 모델들조차 약 69~76%에 머물렀다.

실제로 컴포넌트 기반으로 구현한 비율도 낮았다. 논문이 보고한 비율은 React 0.24%, Vue 5%, Angular 19%다. 화면에 같은 항목이 반복되어도 컴포넌트와 반복 렌더링을 쓰지 않고 마크업을 복사하는 사례가 많았다.

이 숫자를 읽을 때는 실험의 성격을 같이 봐야 한다. 생성 과제는 목업을 코드로 옮기고, 편집과 복구 과제는 기존 코드와 시각 정보, 자연어 지시를 함께 사용한다. 장기 유지보수와 재사용을 평가한 벤치마크는 아니므로 낮은 컴포넌트 사용률을 제품 아키텍처 전반으로 일반화할 수는 없다. 다만 반복 UI가 있는 사례에서도 모델이 재사용 가능한 구조를 거의 선택하지 않았다는 점은 확인됐다.

반대 방향의 결과도 있다. ICLR 2026에 발표된 The Matthew Effect of AI Programming Assistants는 대중적인 언어와 프레임워크일수록 AI가 더 높은 성공률을 보이는 경향을 보고했다. 저자들은 언어별 알고리즘 과제와 같은 17개 요구사항을 여섯 개 풀스택 조합으로 구현하는 과제를 평가했다. 실험 과정에서는 코드를 직접 고치거나 해결 방향을 알려주지 않고, 오류 메시지만 그대로 모델에 다시 전달했다. 그런 다음 완료 여부와 반복 시도 횟수를 비교했다. React + Express + Prisma나 Vue + Spring Boot 조합보다 Svelte + FastAPI, SolidJS + Actix Web처럼 덜 보편적인 조합에서 실패와 재시도가 늘었다.

이 논문은 프론트엔드와 백엔드가 결합된 스택을 비교했으니, 프론트엔드 프레임워크의 효과만 떼어낼 수는 없다. 보편적인 생태계에서 성공률이 높다는 상관관계는 확인했지만, 학습 데이터 노출량과 문서·도구 생태계 중 어느 요인이 원인인지도 분리하지 않았다.

적어도 문법이 단순하거나 코드가 짧다는 이유만으로 AI 친화적이라고 단정할 수는 없다. 해당 생태계가 얼마나 널리 쓰이는지와 AI 성능 사이의 관계도 함께 봐야 한다.

두 결과에서는 프레임워크별 성능 차이가 나타났지만, 유리한 방향은 과제에 따라 달랐다. 두 연구 모두 프레임워크와 소스 아키텍처의 상대적인 영향을 비교하지 않았으므로, 현재 근거로는 React, Vue, Angular, Svelte, Lit 중 하나를 AI 친화적인 프레임워크로 확정하기 어렵다.

그래서 이 글은 프레임워크 선택 자체보다, 그 위에 어떤 구조를 만들 것인지 다룬다.


첫 번째 축: 읽어야 할 문맥#

Web-Bench는 프론트엔드 프로젝트 50개에 각각 20개의 연속된 기능 작업을 수행하게 했다. 뒤의 작업은 앞에서 만든 저장소 상태에 의존한다. 화면 하나를 만드는 것으로 끝나지 않고, 이미 동작하는 기능을 보존하면서 계속 변경해야 한다.

Web-Agent에서 Best-of-5 방식으로 집계했을 때 가장 높은 점수를 기록한 Claude 3.7 Sonnet(thinking)의 Pass@1도 25.11%였다. 네 개 중 하나 정도만 첫 번째 시도에서 통과했다. 화면을 만드는 일과 저장소를 계속 고치는 일은 난이도가 다르다.

2026년 7월 공개된 WebDesignIter는 이 문제를 더 직접적으로 다룬다. 저자들은 프론트엔드 저장소를 연속해서 수정할 때 AI에게 부족한 것이 단순한 코드 검색이 아니라 설계 지식이라고 주장한다.

  • 아키텍처 원칙
  • 모듈별 책임
  • 구조적 제약
  • 파일과 컴포넌트 간 의존성
  • 이전 작업에서 내려진 설계 결정

저자들은 이 정보를 코드 그래프와 함께 관리해 관련 파일을 찾고 최소 패치를 생성하는 방식을 평가했다. Claude 4 Sonnet으로 진행한 제거 실험에서는 설계 지식을 빼면 Pass@1이 11.4%p 떨어졌고, 코드 그래프를 빼면 8.4%p 떨어졌다.

두 번의 시도 후에도 해결되지 않은 오류를 유형별로 나눴을 때 회귀 오류가 차지하는 비중은 29.17%에서 4.17%로 낮아졌다. 이 수치는 절대 회귀율이 아니라 미해결 오류의 구성비다. 논문은 이 분석의 모델별 집계 방식과 분모를 별도로 공개하지 않았다. 따라서 회귀 자체가 줄었다기보다 남은 실패에서 회귀 오류가 차지하는 비중이 작아졌다고 해석해야 한다.

이 결과는 코드가 어디에 있는지만 찾는 것으로는 부족하다는 해석을 뒷받침한다. 그 코드가 왜 거기에 있고, 무엇과 연결되며, 무엇을 깨뜨리면 안 되는지도 작업에 필요한 문맥으로 제공돼야 한다.

문맥을 고르는 능력 자체도 병목이다. ContextBench는 66개 저장소의 1,136개 작업에서 에이전트가 정밀도보다 재현율을 우선해 관련 있을 것 같은 코드를 넓게 읽는다고 보고했다. 탐색한 문맥과 실제 해결에 사용한 문맥 사이에도 큰 차이가 있었다. 정교한 에이전트 스캐폴딩을 더해도 문맥 검색의 개선 폭은 미미했다는 관찰도 함께 있다. 코드 위치와 의존성을 정확히 짚는 문제는 LocAgentDependEval에서도 따로 다뤄졌다.

AI에게 전체 저장소를 읽게 하면 비용이 크고 관련 없는 정보까지 섞인다. 그렇다면 아키텍처가 할 일 중 하나는 읽지 않고도 안전하게 수정할 수 있는 범위를 만드는 것이다.

토큰을 줄이면 결과도 좋아질까#

이 질문에 가장 가까운 답은 WebDesignIter 실험에 있다. 저자들은 같은 Web-Bench에서 일반 코딩 에이전트와 토큰 사용량, Pass@1, Pass@2를 함께 비교했다. 기반 모델은 DeepSeek V4 Pro로 맞췄다.

방식입력 토큰Pass@1Pass@2
WebDesignIter533K33.53%53.14%
Claude Code13,949K21.27%35.59%
OpenHands810K26.70%29.60%
SWE-Agent613K20.80%25.20%

입력 토큰을 약 26분의 1로 줄이면서도 더 높은 성공률을 기록했다. 다른 모델 조합에서도 저자들은 Claude Code 대비 25~30배 적은 입력 토큰을 보고했다.

토큰을 줄이느라 정확도를 내주지도 않았다. Web-Agent와 동일한 기반 모델 아홉 개를 비교한 실험에서 WebDesignIter는 평균 Pass@1이 7.98%p, Pass@2가 9.55%p 높았다.

저자들의 설명에 따르면 일반 에이전트는 관련 파일을 찾기 위해 반복적으로 저장소를 읽고 실패한 뒤 다시 문맥을 구성한다. WebDesignIter는 설계 지식과 의존성 그래프로 변경 대상을 먼저 좁히고, 전체 파일을 다시 만들기보다 필요한 부분만 패치한다.

내 프로젝트에서 잰 숫자도 방향은 비슷하다. 이전 글에서 API 프로덕션 TypeScript 전체는 1,017,880바이트였다. 기능 하나에 검색 도구를 추가할 때 먼저 읽은 진입 파일 네 개는 19,120바이트였다. 진입 문맥만 비교하면 약 53분의 1이다.

53배와 26배를 나란히 놓고 비교하면 안 된다. 단위가 달라서다. 26배는 작업을 끝낼 때까지 실제로 소비한 입력 토큰이고, 53배는 작업을 시작하기 위해 처음 읽은 파일의 크기다. 후자를 전자의 하한으로 볼 수도 없다. 반복 탐색과 검증에 들어가는 읽기가 빠져 있다.

DeepSeek V4 Pro를 사용한 토큰 비교에서는 여러 구성 요소를 결합한 WebDesignIter 시스템이 더 적은 입력 토큰으로 더 높은 통과율을 기록했다. 별도의 미해결 오류 분석에서는 회귀 오류가 차지하는 비중이 낮았다.

따라서 소스 코드를 기능별 디렉터리로 나누는 것만으로 같은 개선이 생긴다고 볼 수는 없다.


두 번째 축: 기억해야 할 규칙#

모듈 경계가 중요하다고 해서 AI에게 아키텍처 규칙을 많이 주면 된다는 뜻은 아니다. Constraint Decay에서는 오히려 반대 결과가 나왔다.

Constraint Decay는 여덟 개 백엔드 프레임워크와 열 개 제약 조합으로 80개의 신규 생성 과제를 만들고, 구조 제약을 L0부터 L3까지 바꿔가며 성능을 측정했다. 별도의 기존 저장소 기능 구현 과제 20개는 제약 효과를 분리하는 실험이 아니라, 이미 계층·PostgreSQL·ORM이 들어간 코드베이스에서도 어려움이 이어지는지 확인한 보조 실험이다. 신규 생성 실험에서 기본 성능이 높았던 여덟 개 모델·에이전트 구성은 L0에서 L3로 갈 때 검증 항목 통과율이 평균 30%p 하락했다.

전체 평균만 보지 말고 제약별 결과도 봐야 한다. 논문은 각 제약의 평균 영향을 분리해 계산했다. 표의 ± 값은 표준오차다.

추가된 제약검증 항목 통과율 영향
PostgreSQL 지정-19.3 ± 2.5%p
SQLite 지정-14.3 ± 2.5%p
Clean Architecture 계층-9.1 ± 1.6%p
SQLAlchemy ORM 지정-1.5 ± 2.1%p
Sequelize ORM 지정-0.6 ± 2.2%p

ORM 지정의 감소 폭은 점추정치상 1.5%p 이하로 작았다. 점추정치 기준으로 PostgreSQL 지정의 감소 폭은 Clean Architecture의 약 2.1배였고 SQLite는 약 1.6배였다. 규칙이 몇 개인지만으로 비용을 설명할 수 없고, 어떤 종류의 제약을 어떤 조합으로 만족해야 하는지가 중요하다. 따라서 규칙 수를 무조건 줄이기보다 비용이 큰 제약의 종류와 기능 하나에서 동시에 만족해야 할 제약 조합을 줄여야 한다.

논문은 Clean Architecture의 -9.1%p가 계층 분리의 부담과 일치한다고 해석했지만, 구체적인 원인을 따로 측정하지는 않았다. 가능한 설명 중 하나는 기능 하나가 여러 수평 계층에 나뉜다는 점이다.

routes
→ services
→ entities
→ repositories

기능 하나를 구현하려면 여러 디렉터리를 오가며 계층 규칙을 지켜야 한다. 아키텍처적으로 정돈되어 있어도, AI가 한 번에 붙잡고 있어야 할 조건은 늘어난다.

제안하는 구조도 제약이다#

이 글에서 제안하는 기능별 수직 구조, 즉 기능 캡슐도 계층과 경계를 추가한다. features/*/{ui, model, api, tests}로 나누고 의존 규칙을 걸고 공개 진입점을 강제하므로 비용이 0일 수는 없다. 다만 저장소 전체를 수평 계층으로 나누는 방식과 달리, 한 기능의 ui, model, api, tests를 같은 디렉터리 아래에 모은다는 차이는 있다. 이렇게 한 기능 안에 모으는 방식이 연속 수정에서 제약 비용을 상쇄하는지는 아직 검증되지 않았다.

두 연구가 측정한 대상도 정확히 구분해야 한다. Constraint Decay의 정량적인 제약 비용은 신규 생성 과제에서 분리해 측정됐다. 기존 저장소 보조 실험에서는 어려움이 이어졌지만 구조 제약만의 효과를 분리할 수 없었다. 반면 WebDesignIter는 설계 지식·코드 그래프·최소 패치·자동 검증을 묶은 시스템이 연속 수정에서 효과가 있음을 보였다.

따라서 구조 제약이 첫 구현에는 비용이고 연속 수정에는 이득이라는 설명은 가능한 가설일 뿐, 두 연구가 직접 입증한 결론은 아니다. 내가 최적화하려는 대상은 연속 수정이지만 신규 생성 비용도 그대로 남는다. 이 상충 관계는 별도 실험으로 확인해야 한다.

그래서 규칙의 총개수보다 기능 하나를 바꿀 때 해석해야 하는 제약의 종류와 판단 비용을 줄이는 쪽으로 기준을 잡았다. 기계가 판정할 수 있는 경계는 검사 도구에 맡긴다.

AI가 자연어 규칙 20개를 기억해야 지켜지는 구조는 좋은 구조가 못 된다. 폴더와 공개 API를 따라가면 변경 범위를 좁힐 수 있어야 한다.


프론트엔드에서 특히 어려운 것#

여기까지의 두 축은 프론트엔드만의 얘기가 아니다. 이전 글에서 백엔드에 적용한 정리와 크게 다르지 않다. 기능별 경계를 만들고, 변경할 때 해석해야 할 제약의 종류와 판단 비용을 낮춘다는 원칙은 어느 쪽에나 적용된다.

프론트엔드가 다른 지점은 하나다. 기능 경계를 넘어 UI를 재사용해야 할 일이 많다.

백엔드에서 결제와 정산은 코드를 공유하지 않아도 된다. 프론트엔드에서 결제 화면과 정산 화면은 같은 버튼, 같은 입력, 같은 모달, 같은 표를 써야 한다. 디자인 일관성은 취향이 아니라 제품 요구사항이다. 기능 경계를 그은 직후부터 그 경계를 뚫으라는 요구가 들어온다.

공통 UI를 쓴다고 기능들이 곧바로 서로 결합하는 것은 아니다. 문제는 shared/ui-primitives가 업무 규칙을 알기 시작하거나 공개 API가 자주 바뀔 때 생긴다. 버튼 하나를 바꿨을 때 여러 기능에 영향이 퍼지면 기능 캡슐을 만들어놓고도 확인해야 할 범위가 다시 넓어진다.

AI가 이 압력을 스스로 관리해줄 것 같지도 않다. 앞에서 본 DesignBench에서는 반복 UI가 있어도 React 컴포넌트를 사용한 비율이 0.24%에 그쳤다. 장기 유지보수를 평가한 결과는 아니지만, 별도 요구가 없을 때 재사용 가능한 구조를 자연스럽게 선택하지 않았다는 점은 확인됐다. 반면 SlopCodeBench에서는 반복 수정 중 구조적 중복이 96% 늘었다. 공통화를 미루면 같은 마크업이 여러 곳에 쌓일 수 있고, 반대로 너무 일찍 공통화하면 기능 사이의 결합이 커질 수 있다.

그래서 공유를 두 종류로 나눈다. 모양의 공유와 행동의 공유다.

색, 간격, 타이포그래피, 모서리 반경 → shared/design-tokens
버튼·입력·모달의 껍데기 → shared/ui-primitives
폼 조합, 검증 표시, 흐름 → feature 내부

판단 질문은 하나다.

이 컴포넌트가 업무 규칙을 알고 있는가.

<Button>은 자기가 무엇을 제출하는지 모르므로 shared에 둔다. <SubmitTaxForm>은 세금 계산 규칙을 알므로 features/tax-calculation 안에 둔다. 같은 버튼을 쓰더라도 조합 로직은 각 기능 안에 둔다.

이렇게 나누는 목적은 읽어야 할 문맥을 작게 유지하는 것이다. shared의 공개 API가 안정적이고 업무 규칙이 없다면 기능을 고칠 때 내부 구현까지 읽을 일이 줄어들 수 있다. 반대로 shared의 컴포넌트에 기능별 조건 분기가 쌓이면 기능 하나를 고칠 때마다 그 분기를 전부 읽어야 한다. shared가 커지는 것 자체보다 shared가 업무를 알기 시작하는 것이 문제다.


그래서 무엇을 할 것인가#

프레임워크는 렌더링 계층으로 제한한다#

실제 서비스를 새로 만든다면 제품과 팀에 익숙하고 학습 자료가 충분한 프레임워크를 선택하겠다. React나 Vue를 선택하되, 프레임워크의 관습이 애플리케이션 구조 전체를 결정하게 두지는 않겠다.

UI 프레임워크는 여기까지만 담당한다.

  • 컴포넌트 렌더링
  • 반응형 상태
  • 사용자 이벤트
  • 화면 생명주기

이 선은 선언만으로 지켜지기 어렵다. 프레임워크에서 흔히 쓰는 패턴은 렌더링 결정처럼 보이면서 실제로는 의존 방향을 정한다. 프레임워크가 아키텍처까지 좌우하게 되는 경로는 대체로 세 개다.

공통 hook 또는 composable에 업무 규칙이 들어간다. useDocumentUpload가 파일 크기 정책과 재시도 규칙까지 담으면 그건 렌더링 코드가 아니라 아키텍처다. 정책은 model/의 평범한 함수로 두고 hook은 그 함수를 호출하는 얇은 껍데기로 남긴다.

provider가 기능 경계 밖으로 올라간다. contextproviderapp 최상단에 두는 순간 모든 기능이 그 타입에 묶인다. 필요한 범위가 기능 하나라면 provider도 그 기능 안에 둔다.

업무 로직이 프레임워크 타입에 의존한다. model/의 함수가 컴포넌트 타입이나 반응형 래퍼를 인자로 받기 시작하면 그 로직은 프레임워크 없이 테스트할 수 없다.

세 번째는 기계로 검증하기 가장 쉽다.

features/*/model/**: UI 프레임워크를 import하지 않는다

이 규칙은 model이 UI 프레임워크에 의존하지 않는다는 점을 lint로 보장한다. 업무 규칙이 hook이나 컴포넌트에 직접 들어가는 것까지 막아주지는 않으므로, 그 부분은 테스트와 리뷰가 필요하다.

기능 경계와 의존 방향, 상태의 소유권은 프레임워크 관습이 아니라 프로젝트 구조가 결정한다.

애플리케이션을 프레임워크 중심으로 설계하지 않는다. 독립된 기능을 먼저 만들고, 프레임워크는 그 기능을 렌더링하는 데 사용한다.

구조는 기술 계층보다 기능을 우선한다#

src/
├── app/
│ ├── router/
│ ├── layouts/
│ ├── orchestration/
│ └── main.ts
├── features/
│ ├── document-upload/
│ │ ├── index.ts
│ │ ├── ui/
│ │ ├── model/
│ │ ├── api/
│ │ └── tests/
│ │
│ ├── tax-calculation/
│ │ └── ...
│ │
│ └── consultation/
│ └── ...
├── platform/
│ ├── auth/
│ ├── http/
│ ├── storage/
│ └── telemetry/
└── shared/
├── design-tokens/
├── ui-primitives/
└── generated-api/

app은 기능을 조립한다. 라우터, 레이아웃, 애플리케이션 시작점, 여러 기능이 함께 참여하는 사용자 흐름이 이곳에 있다. 업무 규칙을 직접 구현하지는 않는다.

features는 실제 제품 기능이다. 문서 업로드를 고칠 때는 가능한 한 document-upload 디렉터리 안에서 읽기, 수정, 검증이 끝나야 한다.

platform은 HTTP, 인증, 저장소, 로깅처럼 업무 도메인을 모르는 기반 코드다.

shared에는 시스템 전체에서 정말 같아야 하는 것만 둔다. 디자인 토큰, 순수한 UI 기본 컴포넌트, 생성된 API 타입 정도다. 업무 규칙을 아는 컴포넌트는 앞 절의 기준대로 여기서 제외한다. shared/utils, shared/services, shared/hooks처럼 무엇이든 들어가는 폴더는 만들지 않는다.

규칙은 네 개로 시작한다#

앞 절의 기준을 따르면 처음부터 복잡한 아키텍처 규약을 만들 이유가 없다.

1. feature는 다른 feature를 직접 import하지 않으며, feature 외부에서는 index.ts로 공개한 API만 사용한다.
2. shared와 platform은 feature를 import할 수 없다.
3. feature 간 연결은 app이 각 feature의 공개 API를 통해 담당한다.
4. feature의 model은 UI 프레임워크를 import할 수 없다.

네 번째는 프레임워크 경계를 검증하기 위한 것이다. lint로 위반 여부를 즉시 판정할 수 있으므로 자연어로만 적어둔 규칙보다 빠른 피드백을 줄 수 있다. 다만 검사 비용이 낮다고 해서 AI가 이 규칙을 지키며 코드를 만드는 비용까지 낮다고 볼 근거는 없다. Constraint Decay의 Clean Architecture 제약도 디렉터리와 import 방향을 정적으로 검사했지만 성능 저하가 관찰됐다.

각 기능은 index.ts를 공개 진입점으로 사용한다.

features/document-upload/index.ts
export { DocumentUploadPage } from "./ui/DocumentUploadPage";
export { uploadDocument } from "./model/upload-document";
export type {
UploadDocumentInput,
UploadDocumentResult,
} from "./model/types";

외부에서 기능의 내부 구현을 직접 가져오지 않는다.

// 금지
import { validateFile } from
"@/features/document-upload/model/validate-file";

내부 구현이 경계 밖에서도 필요하다면 공개 API로 승격할지, 기능을 잘못 나눈 것은 아닌지 먼저 판단한다.

이 규칙은 README에만 적지 않는다. eslint-plugin-boundaries, dependency-cruiser 또는 간단한 구조 테스트로 강제할 수 있다. 사람이든 AI든 규칙을 어기면 즉시 실패하게 만든다.

이 규칙들이 보장하는 것은 의존 방향뿐이다. 이 규칙만으로는 중복 누적이나 파일 비대화를 막지 못한다. dependency-cruiser는 마크업 복사를 잡을 수 없다.

기능 간 연결은 상위에서 명시적으로 한다#

기능 A가 기능 B의 store를 직접 수정하기 시작하면 기능 디렉터리를 나눈 의미가 사라진다. 문서 업로드가 끝난 뒤 세금 계산을 시작해야 한다면, 두 기능을 app/orchestration에서 연결한다.

import { uploadDocument } from "@/features/document-upload";
import { calculateTax } from "@/features/tax-calculation";
export async function uploadAndCalculate(file: File) {
const document = await uploadDocument(file);
return calculateTax(document.id);
}

이렇게 하면 업로드 기능은 세금 계산 기능을 모르고, 세금 계산 기능도 업로드 화면을 모른다. 사용자의 전체 흐름을 아는 것은 상위 조립 계층뿐이다.

모든 연결을 이벤트로 바꾸지는 않는다. 이벤트 버스는 흐름을 간접적으로 만들고 오히려 탐색 범위를 넓힌다. 직접 조립만으로 충분하다면 그렇게 하는 편이 낫다.

두 기능이 항상 함께 바뀌고 따로 사용할 이유가 없다면, 경계를 유지하려고 애쓰기보다 하나의 더 큰 기능으로 합치는 편이 맞다. 경계가 작다고 항상 좋은 것은 아니다. 변경 이유가 같은 코드끼리 모여야 경계에 의미가 생긴다.

상태의 기본 위치는 전역이 아니다#

전역 상태는 편하다. 어디서나 접근할 수 있기 때문이다. AI에게는 그 편함이 그대로 위험이 된다. 어디서나 접근할 수 있으면 변경 영향도도 쉽게 퍼진다.

서버가 원본인 데이터 → 서버 상태
검색 조건과 탭 → URL 상태
컴포넌트 입력값 → 컴포넌트 로컬 상태
기능 내부 공유 상태 → feature 내부 store
로그인 사용자 → app 전역 상태

특정 상태 관리 도구를 배척할 생각은 없다. store의 소유권만 기능 쪽에 두면 된다.

features/document-upload/model/upload-store.ts
features/consultation/model/consultation-store.ts

모든 업무 상태를 src/stores/ 한 곳에 모으는 구조는 피한다. 하나의 기능을 고칠 때 전역 store 전체를 읽어야 한다면 문맥 경계가 무너진다.

중복은 허용하되 방치하지 않는다#

이 고민을 시작하면서 “약간의 중복을 허용하는 편이 AI에게 낫지 않을까”라고 생각했다. 이 문장은 오해하기 쉽다.

SlopCodeBench는 여섯 개 제공자의 15개 모델에 자신이 만든 코드를 반복해서 확장하게 하는 방식으로 평가했다. 벤치마크 자체는 특정 언어에 종속되지 않도록 설계됐지만, 실제 평가는 비용 문제로 Python 트랙의 36개 문제와 196개 체크포인트에서만 진행됐다. 어떤 모델도 문제 하나를 끝까지 해결하지 못했고, 반복 수정 궤적의 77%에서 구조적 침식이, 75.5%에서 장황함이 심해졌다. 반복 수정 과정에서 구조적 중복은 96% 늘었다.

별도의 제어 없이 수정을 반복하게 하면 코드 복사와 장황함이 누적될 수 있다. 중복을 포괄적으로 허용하는 규칙은 이 문제를 더 키울 수 있다.

같은 논문의 다른 숫자가 더 중요하다. 구조적 중복이 96% 늘어나는 동안 137개 AST-grep 규칙의 패턴 위반 밀도는 0.3%만 증가했다. 특정 규칙의 위반 여부와 중복의 누적은 서로 다른 지표라는 뜻이다. 의존성 검사는 마크업 복사를 직접 잡지 못하므로, 중복을 제한적으로 허용하더라도 별도의 중복 측정 장치로 추이를 확인해야 한다. 허용과 방치는 여기서 갈린다.

따라서 프론트엔드에서도 같은 비율로 악화된다고 단정할 수는 없다. 다만 반복 수정 과정에서 중복과 구조 침식을 따로 관찰해야 한다는 문제 제기는 참고할 수 있다. 프론트엔드에서 어느 정도, 어떤 형태로 나타나는지는 별도 실험이 필요하다.

그래서 나는 성급한 공통화를 미루는 것을 원칙으로 삼았다. 중복을 늘리자는 주장과는 다르다.

이 코드들은 앞으로도 같은 이유로 함께 변경되어야 하는가.

여기에 이전 글에서 사용한 기준을 하나 더 붙인다. 같아야 할 코드가 서로 어긋났을 때 문제가 눈에 띄지 않은 채 생기는가.

성격선택
항상 함께 바뀌고, 어긋나도 늦게 발견됨인증, API 스키마, 오류 규약반드시 공유하고 기계로 검증
함께 바뀌지만 어긋나면 바로 보임공통 레이아웃, 일부 UI 기본 컴포넌트공유 후보
변경 이유가 다르지만 모양이 비슷함기능별 포맷터, 전용 버튼기능 내부에 유지
변경 이유도 다르고 영향도 작음짧은 변환 로직제한적 중복 허용

코드 세 줄을 공통화하면서 기능 세 개를 연결한다면, 줄 수는 줄어도 AI가 읽어야 할 범위는 커진다. 반대로 보안이나 데이터 정합성 규칙을 기능마다 복사하면 조용한 불일치가 생긴다. 중복이냐 공통화냐는 두 번째 질문이다. 먼저 볼 것은 변경 이유와 실패 형태다.

자연어 규칙보다 실행 가능한 규칙#

처음에는 각 기능에 feature.json을 둘 수 있다고 생각했다.

{
"name": "document-upload",
"responsibility": "세무 문서를 검증하고 업로드한다",
"invariants": [
"파일은 20 MB를 초과할 수 없다"
]
}

하지만 자연어 불변식을 JSON에 넣는다고 실행 가능한 규칙이 되지는 않는다. 문서를 다른 형식으로 옮겼을 뿐이고 코드가 바뀌면 낡는다. 각 정보의 원본은 성격에 맞는 곳에 두는 편이 낫다.

허용된 의존성 → lint 또는 구조 테스트
공개 진입점 → 실제 export와 빌드 설정
업무 불변식 → 테스트
검증 방법 → 실행 가능한 명령
기능의 책임 → 짧은 README

메타데이터 파일을 둔다면 실제 검사 도구가 그 파일을 읽어야 한다. 사람이 읽기만 하는 설정이라면 README와 다를 게 없다. WebDesignIter가 평가한 것도 문서의 양이 아니라, 설계 지식과 코드 구조를 작업 시점에 연결해 관련 문맥을 고르는 방식이었다.

정리하면 일곱 가지#

1. features/로 기능별 수직 분할, index.ts만 공개
2. shared는 모양만 공유하고 업무 규칙은 feature 안에
3. model은 UI 프레임워크를 import하지 않는다
4. store 소유권을 기능에 두고 전역은 예외로
5. 성급한 공통화를 지연하되 중복 추이는 측정
6. 위 경계를 lint 또는 구조 테스트로 강제
7. 배포는 하나로, 소스 경계만 기능 단위로

처음부터 하지 않을 것#

AI 친화적인 구조를 만든다고 아키텍처를 키울 이유는 없다. 요구가 생기기 전까지 아래 항목은 보류한다.

  • Micro Frontend
  • 모든 기능의 별도 패키지화
  • 기능마다 repository, service, use case를 의무적으로 두는 구조
  • 전역 이벤트 버스
  • 모든 상태의 중앙 store 관리
  • 범용 폼 엔진
  • 거대한 공통 hook 또는 composable 모음
  • 자연어 아키텍처 규칙 수십 개

Micro Frontend는 독립 배포와 팀 자율성이 정말 필요할 때 의미가 있다. Micro Frontend 문헌 리뷰는 작은 독립 애플리케이션과 팀 확장이라는 장점과 함께 페이로드 증가, 코드 중복, 팀 간 결합, 모니터링 복잡성을 보고했다.

AI가 코드를 읽기 좋게 만들자고 런타임과 배포 단위까지 쪼갤 이유는 없다. 하나의 애플리케이션으로 배포하면서 소스 경계만 기능 단위로 나누는 모듈러 모놀리스가 먼저다.


근거는 어디까지이고, 어떻게 확인할까#

현재까지의 연구에서 직접 확인된 범위는 아래와 같다.

  • 저장소를 연속해서 변경하는 과제에서 AI의 성공률은 낮았다.
  • 정확한 코드 위치와 의존성을 찾는 능력은 저장소 수준의 작업 성능과 관련이 있었다.
  • 설계 지식과 코드 그래프를 포함한 WebDesignIter 시스템은 비교 대상보다 적은 입력 토큰으로 높은 통과율을 기록했다.
  • DesignBench에서는 Vanilla보다 프레임워크 구현이 대체로 어려웠고, Constraint Decay의 신규 생성 실험에서는 제약 종류에 따라 비용 차이가 컸다.
  • Python으로 평가한 반복 수정 실험에서는 중복과 구조 침식이 누적됐다.

근거의 수준은 세 단계로 나뉜다.

내 프로젝트에서 측정됨
→ 기능별 진입 문맥의 파일 크기는 전체 TypeScript 소스의 약 53분의 1이었다.
논문에서 측정됨
→ 설계 지식과 코드 그래프 등을 결합한 시스템은 입력 토큰을 줄이면서 통과율을 높였다.
아직 미검증
→ 기능별 소스 아키텍처 자체가 개선의 직접 원인이다.

세 번째 항목이 아직 검증되지 않은 이유가 중요하다. WebDesignIter는 소스 아키텍처만 바꾸지 않았다. 설계 지식, 코드 그래프, 최소 패치, 자동 테스트를 함께 사용한 특화 하네스라서 폴더 구조만의 효과를 분리할 수 없다. 토큰 계산 방식과 에이전트별 도구 차이도 있어 26배라는 숫자를 그대로 적용할 수도 없다. 2026년 7월 처음 공개된 프리프린트이며, 논문이 연결한 GitHub 저장소는 2026년 8월 5일 현재 비어 있어 공개 코드만으로 독립 재현하기도 어렵다.

내가 기능별 아키텍처에 기대하는 흐름은 다섯 단계로 이어진다.

기능 경계가 명확해진다
→ 탐색해야 할 후보 파일이 줄어든다
→ 입력 토큰과 관련 없는 문맥이 줄어든다
→ 수정 범위가 작아진다
→ 회귀 가능성이 낮아진다

코드 위치 파악, 의존성 이해, 문맥 선택의 중요성은 LocAgent, DependEval, ContextBench에서 각각 관찰됐다. 그러나 기능별 폴더 경계가 후보 파일과 입력 토큰을 줄이는지는 이 연구들이 측정하지 않았다. WebDesignIter도 전체 시스템의 입력 토큰과 통과율, 미해결 오류의 구성을 보고했을 뿐 각 화살표를 따로 검증하지 않았다. 따라서 위 다섯 단계 전체가 이 글에서 검증하려는 인과 가설이다.

동일한 프론트엔드 애플리케이션에서 기능별 수직 구조가 기술 계층별 구조보다 AI의 작업 성공률을 높이는가.

답하려면 같은 요구사항을 두 구조로 구현하고 동일한 모델과 하네스로 연속 작업을 수행해야 한다.

A: 기술 계층과 공통화 중심
components / hooks-or-composables / services / stores
B: 기능 캡슐 중심
features/document-upload/{ui, model, api, tests}

측정 항목은 작업 통과율과 재시도 후 회귀 발생률, 읽은 파일 수와 입력 토큰이다. 탐색한 파일 가운데 실제로 필요한 파일의 비율과, 실제로 필요한 파일 가운데 탐색한 파일의 비율도 함께 본다. 기능 경계 밖의 수정 파일 수, 테스트 통과율, 중복과 복잡도의 누적, 같은 작업을 반복했을 때의 결과 편차도 기록해야 한다. 요구사항에는 어느 구조의 이름도 쓰지 않고 사용자 관점의 기능만 적는다. 작업마다 이전에 만든 기능의 테스트도 다시 실행한다.

이 실험 전까지 기능 캡슐이 AI에게 더 좋다는 말은 검증되지 않은 가설로 남는다.

그럼에도 나는 이 구조를 출발점으로 택한다. 관련 파일을 좁히고 설계 제약을 작업 문맥에 넣는다는 점에서 선행 실험의 방향과 모순되지 않기 때문이다. AI에 대한 가설을 빼더라도 변경 이유가 같은 코드를 모으고 공개 API를 두는 방식은 사람이 유지보수하기에도 납득할 수 있는 선택이다. 다만 실제 이득과 제약 비용은 앞의 비교 실험으로 확인해야 한다.


결국 남는 기준#

지금 프로젝트를 시작한다면 기술 스택은 팀에 익숙하고 널리 쓰이는 것으로 고른다.

대중적이고 팀에 익숙한 UI 프레임워크
+ TypeScript strict
+ 표준적인 빌드 도구
+ 기능 캡슐
+ 최소한의 공통 기반
+ 실행 가능한 의존성 경계
+ 기능별 단위 테스트와 브라우저 검증

React, Vue, Angular, Svelte, Lit은 생성 난이도와 생태계 측면에서 차이가 있을 수 있다. 그래도 어느 것을 고르든 기능 경계라는 별도의 문제는 남는다. 기능 하나를 해당 디렉터리 내부와 소수의 공개 API만으로 수정할 수 있다면, 경계 밖에서 추가로 읽어야 할 문맥도 줄어들 가능성이 있다.

프레임워크를 고르는 질문보다 먼저 물어야 할 것이 있다.

“문서 업로드 기능을 수정해줘”라고 했을 때 AI가 document-upload 디렉터리와 몇 개의 공개 API만 읽고 수정과 검증을 끝낼 수 있는가.

가능하다면 내가 생각하는 AI 친화적인 구조에 가깝다. 전역 스토어와 공통 훅·컴포저블·서비스·컴포넌트를 모두 돌아다녀야 한다면 프레임워크를 바꾸기 전에 소스 구조부터 다시 봐야 한다.

이 글의 결론은 프레임워크가 중요하지 않다는 것도, 아키텍처 규칙을 많이 두자는 것도 아니다.

현재 근거에서 내가 AI 친화적인 프론트엔드의 출발점으로 택한 것은 프레임워크 교체가 아니라, 기능 하나를 바꿀 때 읽어야 할 문맥을 줄이고 그 경계를 검사 가능한 규칙으로 지키는 구조다. 이 구조가 실제 작업 성능을 높이는지는 비교 실험으로 확인해야 한다.


참고 자료