우리 팀엔 자바스크립트 상차만 하는 프런트엔드가 있었다

2 weeks ago 8

들어가며

세일즈서비스팀에는 아주 오래된 기술 부채가 한 가지 있었습니다. 프런트엔드 개발자라면 취업 준비할 때부터 당연하게 다루는 index.html 파일이 프런트엔드 리포지터리에 존재하지 않는 것이었죠. 프런트엔드 개발자로 입사하고 팀 프런트엔드 리포지터리를 구경하던 중 가장 의아하게 생각했던 점이었는데요. 알고 보니 index.html 파일은 백엔드 리포지터리에서 관리하고 있었고, 프런트엔드 개발자의 역할은 webpack과 같은 모듈 번들러로 빌드한 자바스크립트 파일을 백엔드 HTML 템플릿에 연결할 수 있도록 전달하는 것, 소위 ‘상차’하는 것뿐이었어요.


AI 생성 이미지: ChatGPT


문제 인식

당시 구조는 이렇게 구성되어 있었어요.

프런트엔드는 React + TypeScript 코드를 빌드해서 생성한 자바스크립트 파일을 S3에 업로드했고, 백엔드는 그 파일을 S3에서 가져와 백엔드 리액트 템플릿으로 HTML을 로딩하고 그 파일(index.html)에 직접 script 경로를 명시하는 방식으로 웹페이지를 구성했죠.
단순히 서비스를 운영하는 데는 문제가 없어 보였지만, 이 방식은 프런트엔드 개발자에게 여러 제약을 만들어냈어요.

1. index.html을 수정할 수 없음

index.html 파일이 백엔드 리포지터리 안에 있었기 때문에 프런트엔드 개발자가 직접 index.html 파일을 수정할 수 없었습니다. 결국 성능 최적화를 위해 태그를 수정하려고 해도 백엔드 개발자에게 업무 요청 티켓을 발행하고, 백엔드 배포 주기까지 기다려야 하는 웃지 못할 상황이 자주 벌어지곤 했습니다.

2. 역할 경계의 혼란

index.html를 비롯한 정적 리소스 관리를 모두 백엔드 리포지터리에서 하다 보니 “어디까지가 프런트엔드의 책임인지” 명확하지 않았어요. 문제가 생기면 프런트엔드와 백엔드 개발자 모두 원인을 찾아야 했고, 결국 의사소통 비용이 늘어나기도 했습니다.

3. 캐시 문제

백엔드가 자바스크립트 파일 경로를 템플릿에 직접 하드코딩하는 구조였기 때문에, 프런트엔드가 빌드할 때마다 생성되는 파일 이름이 바뀌면 서버 측 경로도 그때그때 수정해야만 정상 작동이 가능한 구조였어요.
따라서 Webpack과 같은 번들러에서 기본적으로 제공하는 contenthash 기반 캐시 전략이나 파일 단위의 캐시 정책을 적용할 수 없었습니다. 정적 리소스를 백엔드가 직접 서빙하다 보니 프런트엔드 빌드 시점의 해시나 캐시 정책을 전혀 반영할 수 없었어요.
결국, 자바스크립트 파일이 업데이트되어도 사용자 브라우저에서는 이전에 캐시된 자바스크립트 파일을 불러오는 일이 발생하기도 했습니다.


프런트엔드 개발자의 시선

이 구조가 문제인 건 다들 인지하고 있었습니다. 하지만 막상 “이걸 바꾸자”라고 하면 너무나도 큰 위험이 따라왔습니다. 세일즈서비스팀에서 다루는 서비스는 배민 파트너의 데이터와 계약, 입점, 승인 프로세스를 관리하는 시스템이었기 때문입니다. 조금이라도 작업에 실수가 생기거나, 페이지가 열리지 않는 현상이 발생하면 파트너 관련 업무가 전면 중단될 수 있는 구조였죠. 한마디로 잘해야 본전인 상황이었기 때문에 선뜻 손대기가 어려운 영역이었습니다.

게다가 팀 내에 구조 전환을 이끌어줄 시니어 프런트엔드 개발자도 부재했어요. 주니어 프런트엔드 개발자끼리 이걸 해낼 수 있을까 하는 암묵적인 두려움이 존재했고, 그러다 보니 “이건 원래 그런 것”이라고 생각하며 현 상태를 유지하고 있었죠. 결국, “바꾸면 위험하고, 안 바꾸자니 불편한” 그 애매한 균형 속에서 시간은 흘러만 갔습니다.


백엔드 개발자의 시선

백엔드 개발자 입장에서는 로그인·인증·권한 확인 같은 기능이 여러 곳에 나눠서 구현되어 있다 보니 전체 흐름을 한눈에 파악하기 어려웠습니다.
예를 들어 인증 실패 시 어떤 화면으로 보낼지, 구글 OAuth 토큰을 취소(Revoke)하는 등의 인증/인가 로직이 필터, 인터셉터, 컨트롤러, HTML 파일 등 다양한 위치에 흩어져 있었습니다. 이렇다 보니 예전에 만들어둔 코드가 어디에서 사용되는지 정확히 알기 어려웠고, 필요 없는 로직이 남아 중복 처리와 불필요한 서버 부하까지 발생하는 문제가 있었습니다.
또한 백엔드가 API를 서빙하는 역할만 하는 것이 아니기 때문에 인프라 작업이나 이슈 해결을 할 때에도, 이게 프런트엔드에 영향을 주는지 안 주는지를 재차 고민하고, 점검하느라 시간과 수고가 더 많이 들어갔고 과감하게 수정하지 못하는 경우가 많았습니다. 그래서 팀의 프런트엔드 개발자인 영민 님이 제안을 주셨을 때 이번 기회에 최대한 백엔드의 인증/인가 구조를 정리하고, 불필요한 로직을 걷어내는 것을 목표로 삼았습니다.


index.html 이관 과정

이렇듯 프런트엔드, 백엔드 모두에게 비효율적인 구조를 지켜만 볼 수는 없었습니다. 프런트엔드 개발자로서 할 수 있는 역할이 너무나도 제한되었고, 백엔드 개발자에게는 불필요한 관리 요소가 존재하는 문제가 있었죠. 두려움보다는 현재 구조의 답답함이 훨씬 컸기에 이 구조를 깨뜨리는 길을 걸어보기로 하였습니다.

1. 팀 내 공감대 형성

본격적으로 프런트엔드로 index.html을 이관하는 작업에 들어가기 전에, 먼저 팀 내에 이 작업을 하고자 하는 이유와 예상되는 결과에 관해 설명하고 공감대를 얻고자 했습니다. 작업에 시간이 많이 걸릴 것으로 보여, 리소스를 확보하려면 먼저 팀원의 공감을 얻어야 한다고 생각했기 때문입니다.

세일즈서비스팀은 프런트엔드, 백엔드, PM 등 서로 다른 직군이 한 팀 안에 모여 함께 협업하고 있습니다. 그래서 각 직군이 맡은 영역을 서로 더 잘 이해하기 위해, 모르는 내용을 쉽게 풀어 설명해 주는 ‘품앗이 설명회’라는 시간을 정기적으로 가지고 있는데요. 이 시간을 활용해서 index.html 프런트엔드 이관 작업의 목적과 당위성을 설명하고 팀 내 공감대를 형성하기로 했어요.

아래는 품앗이 설명회의 목차를 일부 캡처한 내용이에요.

프런트엔드 주도 개발 개선 건이었기 때문에 다른 직군 구성원들은 이해하기 어려운 내용이 존재하기도 했고, 겉보기엔 달라지는 내용이 없어 보이는 작업일 수도 있으므로 쉽게 이해할 수 있게 직접 만든 만화와 비유를 활용해서 설명했습니다.

2. 분할 정복

그동안 프런트엔드 개발자로서 프로젝트 단위로 자바스크립트를 상차하는 일만 담당했고, 백엔드 개발자로서 API 작업을 주로 담당했어요. 따라서 AWS나 인프라를 상세히 다루어볼 일도 없었고, 프로젝트를 전반적인 시선으로 바라볼 일도 거의 없었어요. 따라서 index.html을 프런트엔드로 가져오기 위해서는 구체적으로 어떤 길을 걸어야 할지 알 수 없었습니다.

우선 목표를 “index.html을 프런트엔드가 직접 서빙한다”로 단순화했어요. 그리고 이를 위해 어떤 단계가 필요한지 쪼개보기 시작했습니다. 결국 해야 할 일은 크게 전체 구조 설계, 정적 리소스 업로드 공간 확보, 라우팅 문제 해결로 크게 세 갈래로 나뉘었죠.

전체 구조 설계하기

이관을 시작하면서 가장 중점적으로 고려했던 사항은 구조 변경 이후에도 사용자에게 동일한 동작을 제공할 수 있어야 한다는 점이었습니다. 이 과정에서 단순히 index.html을 프런트엔드로 가져오는 것 외에도 인프라 비용 문제, 보안 문제 등을 고려해야 했습니다. 따라서 팀 내부는 물론 인프라, 보안 관련 팀의 의견도 참고하여 최종 구조를 결정하게 되었습니다.

먼저 처음으로 검토했던 안은 아래와 같습니다.

초기안(1안): CloudFront에서 요청 경로 기반 라우팅하는 구조


기존처럼 프런트엔드와 백엔드를 동일 도메인 아래 두기 위해, CloudFront에서 API에 해당하는 경로(/api/**)의 요청은 서버 LB로, 나머지는 S3 정적 리소스로 전달하는 구조를 고려했습니다. 도메인 변경 없이 CloudFront 설정만으로 프런트와 백엔드를 분리할 수 있다는 점이 장점이었습니다.

장점
  • 추가 인프라 구성 변경 없이 CloudFront 설정만으로 분리 가능
  • 프런트엔드와 백엔드가 동일 도메인을 사용 → CORS 및 쿠키 호환 문제없음
  • 기존 경로/도메인 변경 필요 없음

실제로 베타 환경에서 쉽게 적용할 수 있었고, 가장 간단한 방안이었지만 보안 검토를 받는 과정에서 문제가 발견되었습니다.

단점 (보안, 비용 등)
  • 프런트엔드 전용 CDN과 API 엔드포인트가 동일한 CloudFront 배포에 묶여 보안 정책을 분리하기 어려움
  • 보안 구성상 CloudFront에 AWS WAF/Shield(DDoS)가 연결되며 WAF/Shield에서 출발지 IP가 CloudFront IP로 변경이 되어 보안 위협 대응이 어려워짐
  • CloudFront를 API 경로의 트래픽까지 관통하며 WAF 트래픽이 증가해 비용 및 관리 부담 증가
  • CloudFront 로그에 API 경로의 로그까지 함께 기록되어 분석 복잡도 증가
  • Private Subnet의 Internal LB를 Internet-facing CloudFront에 직접 연결하는 것이 네트워크·보안 정책상 적절하지 않음

결과적으로, CloudFront를 통해 프런트엔드와 백엔드를 한곳에서 처리하는 구조는 처음부터 지속 운영하기 어려운 형태였습니다.

최종안(2안): 프런트엔드의 도메인과 백엔드의 도메인을 완전 분리


최종적으로는 프런트엔드와 백엔드를 별도 도메인으로 완전히 분리하는 구조를 채택했습니다. 이는 초기 목적이었던 프런트·백엔드 분리와도 가장 잘 맞는 방식이었습니다.

장점
  • API 트래픽이 CloudFront를 우회해 직접 백엔드로 전달됨 → WAF/Shield 정책 명확
  • 원본 클라이언트 IP를 WAF/Shield에서 정확하게 식별하여 보안 대응 가능
  • 프런트엔드/백엔드 완전 분리 및 인프라 구조 단순화→ 독립 배포 및 장애 격리 가능
  • 프런트엔드 전용 CloudFront 캐시·버전 관리·배포 자동화가 용이
  • 향후 도메인 확장 시 유연성 확보
  • 로그 수집 지점이 분리되어 분석이 명확해짐

이렇게 최종 구조를 확정하고, 다음 작업을 이어 나갔습니다.

정적 리소스를 올릴 공간 만들기

S3와 CloudFront를 이용해 프런트엔드 배포 파이프라인을 구성하기로 했어요. “배포 자동화”가 핵심이었죠. 백엔드에 자바스크립트 파일 경로를 전달하는 대신, 빌드가 끝나면 S3에 업로드되고 CloudFront 캐시를 무효로 하는 형태로 바꾸려 했습니다.

라우팅 문제 해결하기

저희 서비스는 SPA(Single Page Application)가 아니라 MPA(Multi Page Application) 구조였기 때문에, 단순히 모든 요청을 /index.html로 리다이렉트할 수는 없었어요. 페이지마다 별도의 엔트리 파일이 존재했고, CloudFront 단에서 단순한 정적 라우팅으로는 이를 처리할 수 없었습니다. 그래서 Lambda@Edge를 활용하기로 했습니다. CloudFront 요청이 들어올 때 Lambda@Edge가 실행되어, 요청 경로에 맞는 index.html을 찾아 S3에서 반환하도록 구성했어요. 예를 들어 사용자로부터 /dashboard 요청이 들어오면 S3의 /dashboard/index.html을 반환하도록 처리하고자 했습니다.

태스크를 더 세부적으로 나누기

이후 세 가지 작업에 필요한 상세 작업은 무엇이 있을지 분석하며 다시 한번 목표를 세웠고, 총 12가지 항목을 도출할 수 있었습니다. 이 순서대로 하나씩 직접 부딪혀가며 작업을 진행했고, index.html 이관 작업을 완료했습니다.

3. 첫 베타 배포와 검증

모든 준비가 끝났다고 생각했지만, 막상 실제로 배포를 시도하니 예상치 못한 문제들이 연이어 발생했습니다. 단순히 index.html만 옮기는 게 아니라, 실제 환경과 네트워크가 얽혀 있어 발생한 문제도 있었어요.

도메인 분리 시 반드시 생각해봐야할 것 – 쿠키

도메인을 분리할 때 쿠키는 의외로 복병이었습니다. 저희는 결과적으로 쿠키를 제거했는데요. 이 과정에서 겪었던 시행착오를 공유해보고자 합니다.

우리 시스템은 원래 단일 도메인이었기 때문에, 쿠키를 세팅할 때 SameSite 옵션을 명시하지 않고 있었습니다. 이 경우 최신 브라우저(Chrome 80 버전 이후)에서는 기본적으로 SameSite=Lax 를 적용하게 됩니다. Lax로 적용을 하면 일반적으로 브라우저 URL 이동, 링크 클릭 등을 제외하고는 크로스 도메인 간 쿠키 전송이 되지 않게 됩니다.

가장 간단한 해결책은 SameSite를 None / Secure로 설정을 바꾸는 것이었지만, 특정 브라우저에서 예외 동작이 일어날 수 있고, CSRF 공격 위험이 증가하는 등 부작용도 있어서 대안에서 제외하였습니다.

처음 시도한 해결책은 루트 도메인에 쿠키를 추가하는 것이었습니다. 하지만 이로 인해 브라우저에는 기존 서버 도메인(예)baemin-server.test.com)과 루트 도메인(예) .test.com) 쿠키가 함께 설정되었습니다. RFC 6265의 쿠키 도메인 매칭 규칙에 따르면, path가 동일할 경우 더 구체적인 도메인의 쿠키가 우선합니다. 이 규칙 때문에 의도했던 루트 도메인의 쿠키가 전달되지 않는 문제가 발생했습니다.

이러한 문제를 해결하기 위해 기존에 설정된 서버 도메인의 쿠키를 제거하였습니다. 결국 루트 도메인에만 쿠키가 남게 되었고, 이 방법은 문제를 단기적으로 해결할 수는 있었지만 추가적인 문제가 발견되었습니다. 사내망 공용 도메인에 새로 생성된 쿠키가 저장되었고, 타팀의 API 호출 시 헤더 크기 초과 (4kb) 에러가 발생하였습니다. 결과적으로 루트 도메인에 쿠키를 저장하는 안도 적절한 해결책은 아니었습니다.

서버에서 타 도메인인 프런트엔드 도메인으로 쿠키를 세팅할 수도 없었고, SameSite 설정을 바꾸거나 루트 도메인을 쿠키로 세팅하는 것이 문제를 유발했기에, 결과적으로 쿠키를 사용하지 않는 방향으로 결정했습니다. 대부분의 쿠키 로직을 삭제/이관하고, 데이터 암복호화에 사용하는 쿠키를 비대칭 키 암호화 방식으로 전환했습니다.

WAF 문제

CloudFront에는 사내에서 설정된 WAF가 기본적으로 적용되어 있었는데요. 기존에 접근이 가능하던 고객센터, VDI 환경, 협력사 IP로부터의 요청이 WAF 규칙에 걸려 어드민에 접근할 수 없는 상황이 발생했습니다.

이를 해결하기 위해, 신규 배포 형상에 예외 규칙을 추가했고, 기존에 허용되던 IP에서는 정상 접근이 가능하도록 수정했습니다. 덕분에 운영 배포 이후에도 보안 규칙을 유지하면서 정상적인 페이지 서빙이 가능했습니다.

첫 배포는 기술적 난관과 시행착오의 연속이었지만, 프런트엔드 독립 배포가 실제로 가능하다는 확신을 얻을 수 있었습니다. 실제로는 단순히 S3에 파일을 업로드하는 것처럼 보여도, 네트워크와 보안 설정이 얽혀 있어서 배포 과정이 꽤 복잡함을 실감했어요.

4. 분리 배포

베타 배포 후 내부적으로 안정성을 확인했지만, 한 번에 모든 사용자를 대상으로 새 구조를 적용하는 것은 여전히 큰 위험이었습니다. 그래서 분리 배포 전략을 선택했습니다. 불도저처럼 단번에 밀어 버리고 싶은 마음은 굴뚝같았지만, 현실은 안전이 먼저였죠.

  • 내부 사용자 우선 배포 (내부 어드민)
    먼저 사내 구성원과 영업, 승인 담당자 등 내부 사용자만 접근할 수 있는 환경에 우선 배포했습니다. 실제 트래픽 환경에서 Lambda@Edge, S3, CloudFront, 쿠키/보안 설정이 정상적으로 작동하는지 검증했죠. 배포 후 발견된 사소한 문제들도 빠르게 수정하며, 외부 사용자에게도 안전하게 배포할 준비를 마쳤습니다.
  • 외부 사용자 배포 (외부 어드민)
    내부 검증이 끝난 후, 외부 영업 매니저와 파트너가 접근하는 익스터널 어드민을 배포했습니다. 이 과정에서는 기존 백엔드 도메인과 새 프런트엔드 도메인 간 인증 및 쿠키 연동, WAF 규칙과 보안 그룹 상태, CloudFront 캐시 정책 등을 꼼꼼히 점검하며 단계별로 안정성을 확보했습니다.

그 결과 아무런 이슈 없이 성공적으로 배포를 마무리할 수 있었습니다. 결국, 분리 배포 전략 덕분에 위험 부담을 최소화하면서도 안정적으로 구조 전환을 완료할 수 있었습니다.


index.html 이관이 가져온 변화

이관 과정을 마무리한 뒤, 팀과 서비스 환경에는 눈에 띄는 변화가 생겼습니다.

1. 정성적 변화

예전에는 작은 HTML 수정이나 스크립트 업데이트도 백엔드 배포 주기에 맞춰야 했지만, 이제는 프런트엔드가 독립적으로 배포할 수 있게 되면서 배포 주기가 획기적으로 빨라졌습니다. 작은 수정 사항도 프런트엔드 개발자가 즉시 반영할 수 있어, 업무 효율이 크게 개선되었습니다.

또한 index.html와 정적 리소스의 관리 주체가 모호했지만, 이제 index.html과 정적 리소스 관리는 전적으로 프런트엔드 책임이 되었습니다. 문제가 발생하면 각자가 맡은 영역을 책임지고 바로 대응할 수 있게 되었죠. 덕분에 의사소통 비용이 줄고, 원인 파악도 빨라졌습니다.

2. 정량적 변화

업무 효율의 개선, 역할의 명확한 분리 외에 정량적인 변화도 있었는데요,
프런트엔드에서는 CDN 및 캐시 정책 적용을 통해 기존 대비 큰 성능 향상을 이루어낼 수 있었습니다.

[프런트엔드] 로딩시간 감소

  • 내부 어드민: 기존 대비 72% 감소
  • 외부 어드민: 기존 대비 77% 감소

[프런트엔드] Lighthouse Performance 점수 향상

  • 내부 어드민: 71점에서 87점으로 16점 증가
  • 외부 어드민: 58점에서 87점으로 29점 증가

백엔드에서도 큰 개선이 있었습니다.

[백엔드] 코드 정리 및 구조 개선


마치며

이 작업을 진행하는 동안 프로젝트 채널을 거의 DM처럼 사용하며 소통했습니다. 덕분에 빠른 피드백과 유연한 의사결정이 가능했고, 무엇보다 서로를 진심으로 신뢰하는 팀워크가 만들어졌습니다. 이 신뢰와 팀워크의 힘이 결국 이번 구조 전환을 성공으로 이끈 가장 큰 원동력이었다고 생각합니다.

프로젝트를 진행하면서, 단순히 코드만 다루는 것이 아니라 네트워크와 인프라 전반을 깊이 이해하게 되었습니다. S3, CloudFront, Route53, Lambda@Edge, CloudWatch, LB, 캐시, 쿠키 정책, WAF, 보안 그룹 등을 전반적으로 다뤄보면서, 개발하는 서비스가 실제로 어떻게 동작하는지 전체적인 관점에서 바라보는 눈이 생겼죠.

또한 설계한 구조를 실제 운영 환경에 큰 장애와 중단 없이 안전하게 적용하는 경험을 하면서, 자신감도 크게 높아졌습니다.


AI 생성 이미지: ChatGPT

이번 경험에서 얻은 자신감과 효율적인 구조를 바탕으로, 앞으로도 지속적인 서비스 개선을 통해 파트너님들을 위한 더욱 완성도 높은 프로덕트를 만들어 나가겠습니다.

Read Entire Article