개주 훈련일지/🏋️ 전집중 호흡 훈련

프론트 경로 통일 리팩토링: ctx 기준 링크와 리소스 안정화

lshfood2 2026. 2. 19. 23:46

[ 프론트 경로 통일 ]

프로젝트를 진행하다 보면

페이지는 정상인데 이미지가 안 나오거나,

어떤 페이지에서는 링크가 되는데

다른 페이지에서는 깨지는 상황이 은근히 자주 나온다.

 

대부분 원인은 단순하다.
컨텍스트 경로(ctx)를 기준으로 링크를 만들지 않거나,

정적 리소스 경로와 업로드 리소스 경로를 섞어서 쓰면서

화면에서 참조하는 URL과 실제 파일 위치가 어긋나는 것이다.

 

이번에는 기능을 바꾸는 작업이 아니라,

프론트 경로를 안정화하기 위해 아래 3가지를 통일했다.

  1. JSP 내부 링크/정적 리소스를 ctx 기준으로 통일
  2. 정적 JS 파일에서도 ctx를 쓸 수 있게 전역으로 주입
  3. DB/서버가 내려주는 이미지 URL 형태가 제각각이어도
    화면이 깨지지 않게 URL 정규화 적용

1. 왜 ctx(컨텍스트 경로) 통일이 필요한가

개발 환경에서 프로젝트가 항상

'/'(루트)로 뜨면 상대경로가 얼추 맞아 보이는데

실제로는 프로젝트가 '/animale' 같은

컨텍스트 경로를 가진 상태로 배포/실행되는 경우가 많다.

 

그래서 프론트에서 아래처럼

루트 기준으로 박아두면 환경이 바뀌는 순간 바로 깨진다.

  • /css/style.css
  • /images/news/12.jpg
  • /animeList

이 문제를 피하는 가장 단단한 방법은

‘ctx 기준으로만 경로를 작성’하는 규칙을

팀 룰로 고정하는 것이다.

 

한 장으로 끝내는 경로 규칙

경로가 흔들리는 이유는 결국 ‘출발점’이 여러 개이기 때문이다.

출발점을 하나로 고정하면 문제가 크게 줄어든다.

3가지 룰을 기억하자!

  • 화면에서 링크/리소스는 무조건 ctx 기준으로 만든다
  • 정적 리소스(static)와 업로드 리소스(uploads)는
    성격이 다르니 섞지 않는다
  • DB에서 내려오는 URL은 항상 한 번 정규화해서 화면에 꽂는다

2. JSP 경로 규칙: ctx 하나로 통일

JSP에서는 EL로 컨텍스트 경로를 쉽게 꺼낼 수 있다.

페이지 상단에서 한 번만 선언해 두고

 

이후 모든 링크와 정적 리소스 참조는

ctx로 시작하게 맞춘다.

<%-- =========================================================
  (2) JSP 경로 규칙: ctx 하나로 통일
  - 이 블록은 JSP 파일 상단(태그 선언 아래)에 1회만 선언
  - 이후 모든 href/src는 ${ctx}로 시작하는 규칙을 고정
========================================================= --%>
<c:set var='ctx' value='${pageContext.request.contextPath}' />

<%-- CSS/JS 같은 정적 리소스는 ctx 기준으로 로드 --%>
<link rel='stylesheet' href='${ctx}/css/style.css'>
<script src='${ctx}/js/main.js'></script>

<%-- 페이지 이동 링크도 ctx 기준으로 통일 --%>
<a href='${ctx}/animeList'>애니 리스트</a>

<%-- 이미지도 동일하게 ctx 기준 (정적 리소스 폴더에 있을 때) --%>
<img src='${ctx}/images/news/12.jpg' alt='news thumb'>

이렇게 하면 프로젝트가 ‘/animale’로 뜨든,

다른 경로로 뜨든 프론트 링크가 깨질 일이 확 줄어든다.

 

자주 터지는 실수 2가지도 같이 막아준다.

  • 상대경로(../css/...)가 페이지 깊이에 따라 달라지는 문제
  • 컨텍스트가 붙는 환경에서만
    특정 페이지 리소스가 404 나는 문제

3. 메뉴 링크가 많을 때: c:set로 링크를 ‘중앙화’한 이유

관리자 페이지처럼 메뉴가 많아지면

같은 링크를 여러 군데에서 반복하게 된다.


그때 페이지 중간중간에

${ctx}/something을 계속 박아두면

  • 오타가 나기 쉽고
  • 매핑이 바뀌면 수정 범위가 넓어지고
  • 어디가 링크 출발점인지 한눈에 안 잡힌다

그래서 상단에서 링크를 변수로 정의해 두면

메뉴가 깔끔해진다.

<c:set var="urlNewsManage" value="${ctx}/newsList"/>
<c:set var="urlAnimeListManage" value="${ctx}/animeList"/>
<c:set var="urlMyPosts" value="${ctx}/myPostPage" />

 

그리고 본문에서는 변수만 쓴다.

<a class="menu-link" href="${urlNewsManage}">뉴스 관리</a>
<a class="menu-link" href="${urlAnimeListManage}">애니리스트 관리</a>

 

이로 인해 경로 수정 작업을 했다고 말할 때,

‘프론트 링크를 상수화(중앙화)했다’고 정의할 수 있다.


4. c:url을 쓰는 이유

c:set은 문자열을 그대로 저장한다.
반면 c:url은 두 가지를 안전하게 처리해 준다.

  • 컨텍스트 경로 처리(컨텍스트 기반 URL 생성)
  • 쿼리 파라미터 인코딩(특수문자, 공백 등)

특히 파라미터가 붙는 링크는 c:url이 실수가 덜 난다.

<%-- =========================================================
  (4) c:url을 쓰는 이유
  - value가 '/'로 시작하면, c:url이 컨텍스트 경로를 자동으로 붙여준다
  - 쿼리 파라미터는 c:param으로 붙이며 인코딩까지 안전하게 처리된다
========================================================= --%>

<%-- 권장: value는 컨텍스트 제외한 절대경로(/로 시작)로 작성 --%>
<c:url var='boardManageAnimeUrl' value='/boardList'>
  <c:param name='boardCategory' value='ANIME' />
</c:url>
<a href='${boardManageAnimeUrl}'>ANIME</a>
<%-- 예: 컨텍스트가 /animale 이면 → /animale/boardList?boardCategory=ANIME --%>

<%-- 주의: ctx를 value에 직접 붙이면 컨텍스트가 중복될 수 있음 --%>
<c:url var='dupUrlRisk' value='${ctx}/boardList'>
  <c:param name='boardCategory' value='ANIME' />
</c:url>
<%-- 환경에 따라 /animale/animale/boardList?... 처럼 중복 가능 --%>

결과는 이런 형태가 된다.

/boardList?boardCategory=ANIME

 

컨텍스트가 앞에 있으면 실제 렌더링은

/프로젝트명/boardList?boardCategory=ANIME 처럼 동작

 

정리하면

  • 고정 링크는 c:set도 충분
  • 쿼리스트링 링크는 c:url이 더 안전

이 차이가 핵심이다.


5. 정적 리소스 vs 업로드 리소스가 섞이면 왜 깨지는가

스프링(특히 스프링 부트)은

정적 리소스 요청을 컨트롤러로 보내지 않고

정해진 정적 리소스 위치에서 바로 찾아서 내려준다.

 

즉, 브라우저가 아래 URL을 요청하면

${ctx}/images/news/12.jpg

서버는 정적 리소스 핸들러가

해당 파일을 찾아서 그대로 응답한다.

그래서 별도 컨트롤러가 없어도

이미지/CSS/JS가 ‘그냥’ 로딩된다.

 

반대로 업로드 파일(/uploads/...)은 보통

디스크 경로에 저장되기 때문에,

정적 리소스 위치에 없으면 자동으로 서빙되지 않는다.

 

결국 이 차이를 모르고 섞어 쓰면 이런 일이 생긴다.

  • 화면은 ${ctx}/uploads/xxx.jpg 를 찍는데
  • 서버는 그 경로를 서빙할 설정이 없어서 404

즉, 아래와 같이 정리할 수 있다.

  • /images/... 는
    프로젝트 정적 폴더에 있으면 바로 서빙 가능
  • /uploads/... 는
    리소스 매핑(리소스 핸들러 설정)이 없으면 404

이번 경로 통일 작업의 중요한 포인트가 여기였다.


6. DB/서버가 주는 이미지 URL이 제각각일 때: URL 정규화

실무에서는 이미지 URL이 항상 한 형태로만 오지 않는다.

  • 절대 URL: https://...
  • 컨텍스트 포함: /animale/uploads/...
  • 루트 기준: /uploads/...
  • 상대 경로: uploads/...

그래서 애니 썸네일처럼 화면에서

안정적으로 보여야 하는 값은

‘정규화’를 한 번 거쳐서 표시하도록 만들었다.

 

정규화 규칙(화면 기준)

  1. 값이 비어있으면 기본 이미지
  2. http/https로 시작하면 그대로 사용
  3. '/'로 시작하면 ctx를 붙여서 절대 경로로 만든다
  4. 그 외(상대 경로)는 ctx + '/' + raw로 만든다

이렇게 케이스를 모두 커버하면

DB 값이 조금 흔들려도 화면이 안 깨진다.

<%-- =========================================================
  (6) URL 정규화 - JSP 렌더링에서 썸네일 안정화
  목적: DB/서버가 내려주는 raw 경로 형태가 제각각이어도 화면이 안 깨지게 함

  케이스:
  1) empty/null      → 기본 이미지
  2) http/https      → 그대로 사용
  3) '/...' 시작     → ctx + raw
  4) 그 외(상대경로) → ctx + '/' + raw
========================================================= --%>

<c:set var='rawThumb' value='${anime.thumbnailUrl}' />
<c:set var='thumbUrl' value='' />

<c:choose>
  <%-- 1) 값이 없으면 기본 이미지 --%>
  <c:when test='${empty rawThumb}'>
    <c:set var='thumbUrl' value='${ctx}/images/default/thumb-default.png' />
  </c:when>

  <%-- 2) 절대 URL이면 그대로 --%>
  <c:when test='${rawThumb.startsWith("http://") or rawThumb.startsWith("https://")}'>
    <c:set var='thumbUrl' value='${rawThumb}' />
  </c:when>

  <%-- 3) 루트 기준(/uploads/..., /images/...)이면 ctx 붙여서 절대경로로 --%>
  <c:when test='${rawThumb.startsWith("/")}'>
    <c:set var='thumbUrl' value='${ctx}${rawThumb}' />
  </c:when>

  <%-- 4) 상대경로(uploads/...)면 ctx + '/' + raw --%>
  <c:otherwise>
    <c:set var='thumbUrl' value='${ctx}/${rawThumb}' />
  </c:otherwise>
</c:choose>

<img src='${thumbUrl}' alt='anime thumb'>

여기서 포인트는 하나다.

  • raw가 어떤 형태든 최종 thumbUrl은
    브라우저가 접근 가능한 완전한 URL이어야 한다

7. JS가 카드 HTML을 만드는 경우: JS에서도 정규화가 필요

애니 리스트는 JS로 카드 HTML을 만들기 때문에,

JSP에서만 정규화하면 부족하다.

그래서 JS에서도 같은 규칙으로 한 번 정규화를 한다.

정규화를 어디에 두는 게 유지보수에 유리한가

  • JSP 렌더링 페이지
    > JSP 유틸(조건 분기)로 정규화
  • 비동기 리스트/컴포넌트
    > JS 유틸 함수로 정규화
  • 공통 규칙
    > 같은 정책을 두 군데에서 다르게
       구현하지 않게 규칙을 문서로 고정

8. 정적 JS는 JSP의 ${ctx}를 모른다: 전역 ctx 주입

JSP 안에서 쓰는 ${ctx}는 

서버 렌더링 대상이라서 가능한 것이다.

 

assets/js 같은 정적 JS 파일은

EL이 적용되지 않는다.

 

그래서 JSP가 ctx를 한 번만 전역으로

내려주고 JS가 그 값을 쓰게 했다.

<!-- =========================================================
  (8) 정적 JS는 JSP의 ${ctx}를 모른다: 전역 ctx 주입
  - 정적 JS(.js 파일)에서는 EL이 동작하지 않음
  - JSP가 ctx를 전역 변수로 1회 내려주고, 정적 JS는 그 값을 사용
========================================================= -->
<script>
  window.APP_CTX = '${ctx}'; // 정적 JS에서 사용할 컨텍스트 경로
</script>
<script src='${ctx}/assets/js/anime-list.js'></script>

이 패턴을 잡아두면 아래가

전부 ctx 기준으로 안전해진다.

  • fetch URL
  • 이미지 URL
  • 페이지 이동 URL
/* =========================================================
  (7)(8) JS에서 URL 정규화 + APP_CTX 사용
  목적:
  - raw 경로가 어떤 형태든 브라우저가 접근 가능한 URL로 통일
  - 정적 JS에서도 컨텍스트 경로를 안전하게 적용

  규칙:
  1) empty/null      → 기본 이미지
  2) http/https      → 그대로 사용
  3) '/...' 시작     → APP_CTX + raw
  4) 그 외(상대경로) → APP_CTX + '/' + raw
========================================================= */
function normalizeThumbUrl(raw) {
  const ctx = window.APP_CTX || ''; // JSP가 주입한 컨텍스트 경로

  // 1) 값이 없으면 기본 이미지
  if (!raw) return `${ctx}/images/default/thumb-default.png`;

  // 2) 절대 URL이면 그대로
  if (raw.startsWith('http://') || raw.startsWith('https://')) return raw;

  // 3) 루트 기준이면 ctx 붙이기
  if (raw.startsWith('/')) return `${ctx}${raw}`;

  // 4) 상대경로면 ctx + '/' + raw
  return `${ctx}/${raw}`;
}

/* 카드 렌더링에서 사용 예시 */
function renderAnimeCard(anime) {
  const thumbUrl = normalizeThumbUrl(anime.thumbnailUrl);

  return `
    <div class='anime-card'>
      <img src='${thumbUrl}' alt='thumb'>
      <div class='anime-title'>${anime.title}</div>
    </div>
  `;
}

/* fetch URL도 ctx 기준으로 안정화 */
function loadAnimeList() {
  return fetch(`${window.APP_CTX}/animeListData`)
    .then(res => res.json());
}

지금까지의 작업을 한 줄로 정의하면

이번에 한 건 ‘경로 통일 리팩토링’이다.

  • 컨텍스트 경로(ctx) 기준으로 href/src를 전부 통일
  • 메뉴 링크를 변수화해서 경로 변경 비용을 줄임
  • 정적 JS에서도 ctx를 쓰게 만들어
    API/리소스 URL이 안정화됨
  • DB/서버가 내려주는 이미지 URL이 달라도
    화면에서 정규화로 흡수하게 만듦

마무리

기능은 그대로인데 페이지가

더 안정적으로 동작하게 되는 작업이 있다.

경로 통일은 딱 그 종류다.

 

이 작업을 해두면 이후에 배포 환경이 바뀌거나,

파일 저장 방식(정적/업로드)이 바뀌어도

프론트에서 깨지는 지점이 확 줄어든다.

 

경로 설정 체크리스트

JSP

  • 페이지 상단에 ctx 선언이 있는가
  • link/script/img/a 태그가 ctx 기준으로 시작하는가
  • 쿼리스트링 링크는 c:url로 만들었는가
  • 같은 링크를 여러 번 쓰면 변수로 중앙화했는가

JS

  • 정적 JS에서 ctx가 필요하면
    window.APP_CTX를 사용하고 있는가
  • DB에서 내려온 이미지 경로는
    화면 렌더링 전에 정규화했는가

리소스

  • 정적 리소스(/images, /css, /js)와
    업로드(/uploads)가 역할상 분리되어 있는가
  • 업로드가 404라면 리소스 핸들러 설정이 필요한가