Thymeleaf : 정적 리소스, URL 제대로 알고 사용하자


    들어가기 전

    토이 프로젝트를 하며 가장 어렵다고 느꼈던 부분은 타임리프에서 URL 링크를 적절히 사용하는 부분이었다. 어떻게 URL 경로를 걸어야 할지 굉장히 애매해서 로컬 환경에서 테스트를 하고 정상으로 확인한 후, 다른 곳으로 전파를 했다. 그런데 이렇게 충분한 테스트를 거쳤다고 하더라도, 다른 곳에서는 정상적으로 적용이 되지 않는 경우가 많았다. 

    가장 어려웠던 부분은 로컬 환경에서 어찌저찌 잘 걸어두었는데, 실제 AWS로 배포를 했을 때 뭔가 로컬과 다르게 작동하면서 CSS, BootStrap 같은 것들이 제대로 링크가 먹지 않는 부분이었다. 이번 포스팅에서는 그런 부분을 잘 정리해보고자 한다.

     


    Spring의 정적 리소스 제공

    스프링은 /resource/static에 넣어둔 파일들을 스프링부트가 내장 TOMCAT 서버를 서빙할 때 자동으로 제공해준다. 정적 리소스는 static 뒷 부분의 경로가 그대로 URL로 출력된다.

    • static 파일 위치 → src/main/resources/static/basic/hello-form.html
    • 브라우저 실행 경로 → localhost:8080/basic/hello-form.html

    브라우저 실행 경로를 입력하면, 작성해둔 정적 리소스가 브라우저에 랜더링 되는 것을 확인할 수 있다. 

     


    Spring의 동적 리소스 제공

    • Controller는 ViewName을 돌려준다. View Resolver를 통해 ViewName을 가지는 View가 만들어진다.
    • ViewResolver는 접미사, 접두사를 바탕으로 View 이름을 한번 더 가공해서 View를 만들어준다.
    • 스프링 ViewResolver에서 사용하는 접미사, 접두사는 설정으로 바꿀 수 있다.

    @Controller를 사용하는 컨트롤러가 String 타입의 값을 반환하면 View Name이 된다. View Resolver는 기존에 설정된 Prefix, Suffix를 이용해 실제 View 경로를 찾아 View를 만들어준다. 

    spring.mvc.view.prefix="resources/templates/"
    spring.mvc.view.suffix=".html"

    스프링에서 제공하는 View Resovler의 prefix와 suffix의 기본값은 다음과 같다. application.properties에서 변경할 수 있다. 

     


    URL의 상대경로, 절대경로

    1. 상대 경로
      • 기준 : 현재 웹페이지의 URL이 기준점
      • ./ : 현재 웹 페이지의 URL
      • ../ : 현재 웹 페이지의 부모폴더
    2. 절대 경로
      • / : 최상위 폴더로 이동 후, 경로를 하나씩 입력해준다.
    3. 외부 경로

     


    타임리프의 URL 경로 문법

    타임리프는 URL 경로 표시를 위한 문법 @{...}을 제공한다. 타임리프에서 정적 리소스를 연결하거나, 다른 컨트롤러로 연결하기 위해서 @{...}을 사용하면 편리하게 할 수 있다. 주된 사용법은 다음과 같다.

    // 절대 경로
    th:href=@{/bootstrap/js/scripts.js}
    
    // 상대 경로
    th:href=@{bootstrap/js/scripts.js}

    앞서 이야기 했던 것처럼 URL은 "상대 경로"와 "절대 경로"가 존재한다. Thymeleaf에서는 @{...} URL 표현식에 "/"의 유무로 상대경로와 절대경로를 구별한다. 

    • 현재 ViewTemplate으로 온 URL 주소를 localhost:8080/abc 라고 가정.
    • 절대 경로 : @{/basic/hello
      • 결과 : localhost:8080/basic/hello
    • 상대 경로 : @{basic/hello}
      • 결과 : localhost:8080/abc/basic/hello

     

    상대 경로는 현재 타고온 URL을 기준으로 뒷쪽에 경로 표현식의 값을 덧붙여 URL로 만든다. 반면 절대 경로는 "/"를 이용해 최상위 루트로 올라간다. 따라서 최상위 경로에서부터 시작해서 뒷쪽의 경로값을 붙인다.

     


    타임리프의 URL 경로 →  정적 리소스, Controller 연결은?

    결론부터 말하면 CSS, Controller를 연결할 때 컨트롤러 URL인지, 정적 리소스 연결인지 고민할 필요가 없다. 앞서 이야기 했던 것처럼 정적 리소스는 static 폴더 아래의 경로를 URL에 붙여 웹 브라우저에 검색하면 그대로 랜더링된다. 타임리프에서 정적 리소스 URL를 이렇게 연결시켜주기만 하면 된다. Controller도 URL을 연결시켜주는 것이기 때문에 동일하다. 정적 리소스, 컨트롤러 모두 접근 URL이 있고 타임리프에서는 이 URL을 잘 연결해주면 된다. 

    따라서 타임리프에서 Controller, 정적 리소스(Css, Script)등을 연결할 때는 상대경로 / 절대경로 표현식을 사용해서 적절한 URL 주소를 잘 표시해주기만 하면 된다. 아래에서 한 가지 예시를 들겠다.

     

     

    정적 리소스 맵핑 예시

    resources/static/bootstrap/admin/js 폴더에 있는 자바 스크립트를 특정 타임리프에서 링크를 걸고 사용해야하는 상황이다. 가장 먼저 해야할 일은 scripts.js가 실제 어떤 URL과 연결되어있는지를 확인하는 것이다.

    스프링의 정적 리소스의 URL은 static 아래에 경로를 그대로 표현해주면 된다. 따라서 script.js의 URL은 http://localhost:8080/bootstrap/admin/js/script.js가 될 것이다. 특정 타임리프에서 이것을 사용하고 싶으면, 이 URL을 상대경로를 나타내든, 절대경로를 나타내든 잘 표시해주기만 하면 된다.

    // 타임리프 URL 문법 사용하지 않고 연결 + 절대 경로
    <script src="/bootstrap/js/scripts.js"></script>
    
    // 타임리프 URL 문법 사용해서 연결 + 절대 경로
    <script th:src="@{/bootstrap/js/script.js}"/>

    "/"를 앞에 붙여주는 것은 최상위 폴더로 올라간다고 했다. 즉, 현재 URL 경로와 상관없이 "localhost:8080/"에서 시작한다. 위 두 가지 방법을 통해서 만들어지는 것은 모두 "http://localhost:8080/bootstrap/admin/js/script.js"가 된다.

     

    컨트롤러 맵핑 예시

    <li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="/logout">로그아웃</a></li>
    <li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" th:href="@{/logout}">로그아웃</a></li>

    컨트롤러는 "/logout"에 맵핑된다. 따라서 실제로 맵핑되는 URL은 http://localhost:8080/logout이다. 컨트롤러가 원하는 URL을 맞춰주면 된다. 절대경로를 표현할 경우 루트 경로로 가서 뒷쪽에 URL을 붙여준다. 위는 모두 "/"를 이용한 절대경로를 이용했기 때문에 결과물은 모두 동일하게 http://localhost:8080/logout이 나온다. 

    댓글

    Designed by JB FACTORY