claude code 에 비해 gemini cli 의 아쉬운 점
“dev 에서는 정상 동작하는데, aws 배포를 하면 생기는 문제를 분석해 달라”고 하니 분석 수준이 차이납니다.
claude code 는 대상 코드 뿐만 아니라, 배포 환경인 aws 사용상의 문제 패턴을 인식하고 있습니다. 반면 gemini 는 대상 코드내에서만 문제를 찾으려고 하네요.
dev 에서 정상 동작한다면 환경 문제일 수 있는데 말입니다.
이 사례 뿐만 아니라 다른 경우에서도 claude code가 개발자가 처할 수 있는 문제에 대해서 다각도로 폭넓게 고려한다는 느낌이 들었습니다. 다음 번에는 codex 를 비교해 봐야겠습니다.
질문 :
현재 marketing 앱만 container 에 붙였는데, dev 환경에서는 문제가 없어. 그런데 prod 로 배포하면 다음과 같이 에러가 나. 이유가 뭘까
Uncaught SyntaxError: Unexpected token '<' (at remoteEntry.js:1:1)
claude code 의 분석은 다음과 같습니다. (정답이 들어 있습니다)
진단: prod에서 remoteEntry.js SyntaxError 원인 분석
에러 메시지
JavaScript
Uncaught SyntaxError: Unexpected token '<' (at remoteEntry.js:1:1)
원인
이 에러는 브라우저가 JS 파일 요청에 HTML 응답을 받을 때 발생합니다. < 는 HTML의 시작(<!DOCTYPE html>)을 의미합니다.
가장 유력한 원인: CloudFront 404 → index.html 폴백
시나리오
SPA(React Router) 새로고침 문제를 해결하려면 CloudFront에 다음 설정이 필요합니다.
Custom Error Response:
404→/container/latest/index.html(HTTP 200)
이 설정이 있으면, marketing의 remoteEntry.js를 찾지 못할 경우 CloudFront가 JavaScript 파일 대신 index.html(HTML)을 돌려줍니다.
브라우저 요청:
GET /marketing/latest/remoteEntry.jsS3 응답:
404(파일 없음)CloudFront:
404→index.html반환 (200 OK)브라우저: HTML을 JS로 파싱 →
SyntaxError: Unexpected token '<'
왜 dev에서는 괜찮은가?
dev에서는
marketingwebpack-dev-server(port 8081)가 직접 응답하므로 항상 실제 JS를 반환합니다.prod에서는 CloudFront/S3를 경유하므로 파일이 없으면 위와 같은 HTML 폴백이 발생합니다.
이차 원인들
1. PRODUCTION_DOMAIN 환경변수 미설정
packages/container/config/webpack.prod.js 파일 내 설정:
JavaScript
const domain = process.env.PRODUCTION_DOMAIN
remotes: {
marketing: `marketing@${domain}/marketing/latest/remoteEntry.js`
}
Secret이 비어있으면 URL이
marketing@undefined/marketing/latest/remoteEntry.js가 됩니다.잘못된 URL 요청 →
404에러 → HTML 폴백 발생.
2. Marketing이 먼저 배포되지 않음
container가 먼저 빌드/배포된 경우.marketing의remoteEntry.js가 S3에 아직 업로드되지 않아404에러가 발생합니다.
빠른 진단 방법
브라우저 Network 탭 확인:
remoteEntry.js요청 클릭 후 Response 내용을 확인합니다.<!DOCTYPE html>로 시작하면 HTML 폴백이 발생 중인 것입니다.실제 URL에
undefined가 포함되어 있는지 확인합니다.
GitHub Actions 로그 확인:
container빌드 단계에서PRODUCTION_DOMAIN값이 정상적으로 주입되었는지 확인합니다.S3에서 파일 존재 여부 확인:
Bash
aws s3 ls s3://BUCKET_NAME/marketing/latest/
수정 방법 (원인에 따라)
Case 1: CloudFront 404 폴백이 문제인 경우
CloudFront Custom Error Response를
/container/latest/index.html이 아닌 라우팅 전용으로 범위 제한을 둡니다.또는
marketingS3 오리진을 별도 origin으로 설정하여marketing경로는 폴백 없이 실제 404를 반환하도록 합니다.
Case 2: PRODUCTION_DOMAIN 미설정인 경우
GitHub repository Settings → Secrets에서
PRODUCTION_DOMAIN값을 등록합니다.형식:
https://d123456.cloudfront.net(후행 슬래시 없이 입력)
Case 3: 배포 순서 문제
marketing을 먼저 배포한 후container를 배포하도록 순서를 변경합니다.또는
containerworkflow에marketing배포 완료 대기 단계를 추가합니다.
관련 파일
packages/container/config/webpack.prod.js— remote URL 구성packages/marketing/config/webpack.prod.js—publicPath,output설정.github/workflows/container.yml—PRODUCTION_DOMAIN주입, 배포 순서.github/workflows/marketing.yml— S3 배포 경로
반면 gemini cli 는 aws 배포시에 생길 수 있는 특성을 별로 고려하지 않고, 코드만 들여다 봅니다. (그래서 헛짚습니다)
Gemini의 응답
Uncaught SyntaxError: Unexpected token '<' 에러는 브라우저가 JavaScript 파일(remoteEntry.js)을 기대했지만, 실제로는 HTML 내용(보통 404 에러 페이지)을 받았을 때 발생합니다.
현재 설정과 상황을 분석했을 때 가장 가능성 높은 원인들은 다음과 같습니다.
1. PRODUCTION_DOMAIN 환경 변수 설정 오류
packages/container/config/webpack.prod.js를 보면 다음과 같은 코드가 있습니다.
JavaScript
const domain = process.env.PRODUCTION_DOMAIN;
// ...
remotes: {
marketing: `marketing@${domain}/marketing/latest/remoteEntry.js`
},
배포 시 PRODUCTION_DOMAIN이 정확하게 설정되어 있는지 확인이 필요합니다.
잘못된 예:
https://d3v123.cloudfront.net/(끝에/가 포함되면 경로가 중첩됨)올바른 예:
https://d3v123.cloudfront.net(도메인만 포함)
만약 이 값이 비어있거나 잘못되어 undefined/marketing/latest/remoteEntry.js 같은 경로로 요청이 가면, S3/CloudFront에서 해당 파일을 찾지 못하고 404 HTML 폴백 페이지를 반환하게 됩니다.
2. S3 버킷 내 파일 경로 불일치
marketing.yml 워크플로우에서 파일을 동기화하는 경로를 확인해 보세요.
YAML
- run: aws s3 sync dist s3://${{ secrets.AWS_S3_BUCKET_NAME }}/marketing/latest
이 설정은 dist 폴더 안의 내용이 버킷의 /marketing/latest/ 경로 아래에 들어가게 합니다. 하지만 만약 marketing 앱의 webpack.prod.js에서 publicPath가 /marketing/latest/로 되어 있다면, 빌드 결과물 내의 참조 경로가 꼬일 수 있습니다.
3. CloudFront 캐시 문제
새로운 remoteEntry.js를 배포했음에도 CloudFront가 이전 버전(혹은 이전에 발생했던 404 응답)을 캐싱하고 있을 수 있습니다. 워크플로우에 무효화(Invalidation) 단계가 있지만, 경로가 정확한지 다시 확인해 보세요.
Bash
--paths "/marketing/latest/remoteEntry.js"
4. marketing 앱의 publicPath 설정
packages/marketing/config/webpack.prod.js에서:
JavaScript
output: {
filename: '[name].[contenthash].js',
publicPath: '/marketing/latest/'
},
이 publicPath 설정은 브라우저가 에셋을 찾을 때의 기준이 됩니다. 만약 CloudFront 도메인 루트에서 바로 접근하는 구조라면 이 경로가 실제 S3 구조와 일치하는지 대조해봐야 합니다.
💡 해결을 위한 제안 (Next Steps)
브라우저 개발자 도구의 Network 탭에서
remoteEntry.js를 요청하는 실제 URL이 무엇인지 확인하세요.해당 URL을 직접 브라우저 주소창에 입력했을 때 JavaScript 코드가 나오는지, 아니면 HTML(404 페이지 등)이 나오는지 확인하세요.
container앱을 빌드할 때 사용하는 CI/CD 환경(예: GitHub Actions)에서PRODUCTION_DOMAIN시크릿 변수가 올바른 형식으로 등록되어 있는지 점검하세요.
