1️⃣ 문제 상황
소셜 로그인 + JWT 기반 인증을 적용한 후,
선착순 강의 시스템의 강의 상세 페이지(/lectures/{id}) 에서
“신청하기” 버튼을 눌러도 아무 반응이 없는 문제가 발생했다.
정상 동작 흐름 (의도한 동작)
- 로그인 후 강의 상세 페이지 진입
- “신청하기” 버튼 클릭
- 대기열 등록 API 호출
- 폴링으로 순번 확인
- 입장 가능(SUCCESS) 시 결제 페이지 이동
하지만 실제로는:
❌ 버튼 클릭 시 화면 변화 없음
❌ 네트워크 요청 없음
❌ 오류 메시지도 없음
2️⃣ 원인 분석
문제의 핵심은 버튼이 아니라 JavaScript 실행 자체가 중단된 것이었다.
✔️ 핵심 원인 1 : Token is not defined 에러
브라우저 콘솔에 다음 오류가 발생했다 :
Uncaught ReferenceError: Token is not defined
상세 페이지에서는 다음과 같은 코드가 실행되고 있었다 :
Token.saveTokenFromUrl();
Token.getToken();
Token.apiFetch(...);
그러나 token.js 파일에는 Token 객체가 정의되어 있지 않았고,
단순히 토큰을 저장하는 코드만 존재했다.
기존 token.js
const token = searchParam('token')
if (token)localStorage.setItem("access_token",token)
즉, Token이라는 전역 객체가 존재하지 않아
스크립트 실행 중 바로 에러가 발생했고,
그 이후 코드가 실행되지 않았다.
✔️ 핵심 원인 2 : 이벤트 등록 전에 스크립트 실행 중단
버튼 클릭 이벤트는 아래처럼 스크립트 하단에서 등록된다.
applyBtn.addEventListener("click", () => {
enqueueNew();
});
하지만 위쪽에서 에러가 발생하면:
- 스크립트 실행 중단
- 이벤트 등록 코드 실행되지 않음
- 버튼 클릭 시 아무 반응 없음
👉 즉, “버튼 문제”가 아니라 “이벤트 자체가 존재하지 않는 상태”였다.
✔️ 핵심 원인 3 : th:inline = "javascript" + 템플릿 문자열 충돌
초기 구현에서는 Thymeleaf의 JS 인라인 기능을 사용했다.
<script th:inline="javascript">
const lectureId= [[${lecture.id}]];
resultText.innerHTML=`
<span>...</span>
`;
</script>
이 구조에서 문제가 발생할 수 있다.
이유
th : inline = "javascript" 는 내부 문자열을 변환하면서 :
- 따옴표 처리
- 이스케이프
- 템플릿 치환
을 수행한다.
특히 아래가 동시에 존재하면 문법 오류 가능성이 높다:
- Thymeleaf 인라인 JS
- 백틱(`) 템플릿 문자열
- HTML 멀티라인 문자열
👉 브라우저에서는 SyntaxError 발생 -> 스크립트 중단
3️⃣ 진단 과정
1. 콘솔 확인 (가장 중요)
F12 -> Console 탭
- ReferenceError
- SyntaxError
등이 있으면 스크립트가 실행되지 않은 것이다.
2. 네트워크 요청 확인
F12 -> Network 탭
버튼 클릭 시:
- API 요청 없음 → 이벤트 미등록 가능성 높음
- 요청 있음 → 서버 또는 인증 문제
이번 사례는 요청 자체가 없었다.
3. 정적 파일 실제 로딩 내용 확인
브라우저에서 직접 접근 :
http://localhost:8080/js/token.js
👉 서버가 실제로 어떤 JS를 서빙하는지 확인 가능
4️⃣ 해결 방법
✔️ 해결 1: Token 모듈 구현
token.js를 전역 객체 기반 모듈로 변경했다.
window.Token= { saveTokenFromUrl, getToken, apiFetch };
주요 기능
- URL에서 access token 추출
- localStorage 저장
- Authorization 헤더 자동 첨부
- API 호출 래퍼 제공
✔️ 해결 2: Thymeleaf 인라인 JS 제거
lectureId 전달 방식을 변경했다.
기존 (문제 발생)
const lectureId= [[${lecture.id}]];
개선 (data attribute 사용)
<body data-lecture-id="[[${lecture.id}]]">
const lectureId = document.body.dataset.lectureId;
👉 템플릿 엔진과 JS 파싱 충돌 제거
✔️ 해결 3: JS 캐시 문제 방지
개발 중에는 브라우저 캐시 때문에
옛 JS 파일이 계속 로드되는 문제가 발생한다.
Thymeleaf를 이용해 캐시 무력화:
<script th:src="@{/js/token.js(v=${#dates.createNow().time})}"></script>
🎯 결과 (Result)
수정 후 정상 동작:
- 버튼 클릭 시 API 요청 발생
- 대기열 등록 성공
- 폴링 시작
- SUCCESS 시 결제 페이지 이동
🏁 회고 (Retrospective)
이번 문제는 UI나 서버 문제가 아니라
프론트 스크립트 실행 흐름을 이해하지 못하면 찾기 어려운 문제였다.
특히 다음 사실을 명확히 깨달았다:
JavaScript는 위에서 에러가 나면 아래 코드는 “존재하지 않는 것”과 같다.
이 경험을 통해:
- 콘솔 확인의 중요성
- 이벤트 등록 시점 이해
- 템플릿 엔진과 JS 분리 설계의 필요성
을 체감할 수 있었다.
'프로젝트 > 스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글
| ⏰🚨 선착순 강의 신청에서 “이미 신청했는데 다시 신청 가능?” 문제 해결 (0) | 2026.03.06 |
|---|---|
| ⏰💡 선착순 강의 Redis 대기열(ZSET) + 입장권 원자화 + 소비(consume) 적용하기 (Lua 1개로 끝) (0) | 2026.02.26 |
| ⏰💡 소셜 로그인 + 선착순 강의 신청 시스템 통합 (JWT 기반 userId 전환) (1) | 2026.02.23 |
| ⏰🚨 동시성 테스트 중 500 Internal Server Error 발생 원인과 해결 (1) | 2026.02.23 |
| ⏰🚨 Enroll 동시성 테스트에서 신청이 거의 발생하지 않은 문제(JMeter) (0) | 2026.02.15 |