[Hugo] [구버전] 무한스크롤을 구현해 보자

JS의 Observer와 Hugo 템플릿을 결합해서 무한스크롤을 구현해 보자

새로운 방법 추가!

해당 방법보다 더 좋은 아이디어로 개발했습니다. 해당 게시글 참고 해주세요!

발단

Hudi님의 블로그를 보다 보니 게시글 목록이 무한 스크롤로 구성되어 있었다. 필자도 구현하고 싶어서 여러 가지 방법을 찾아보았다.

찾아보니 제일 좋은 방법은 IntersectionObserver를 이용하는 것이라고 한다.

InsersectionObserver란 상위 요소와의 교차 영역에 대한 변화를 비동기적으로 감지하는 API. element가 화면에 노출되었는지 여부를 간단하게 구독할 수 있음.

문제

그런데 문제는, hugo와 js가 궁합이 그렇게 좋지 않다는 것이다.

블로그 게시글 hugo 템플릿과 js는 공존이 불가능하다.

var num = 1
var test = `
{{ $test := 1 }}{{ $test }}{{ ${num} }}  
`

위와 같은 코드는 빌드 시에 아래처럼 에러가 발생한다.

hugo 에러 로그

이유가 무엇일까? 우리는 hugo 빌드js 실행 사이의 순서를 알아야 한다.

  1. hugo 빌드
  2. hugo 템플릿에 값들이 설정되어 변환됨
    • ex. {{ $test := 1 }}{{ $test }} 라는 템플릿은 1로 변환
  3. 변환된 html이 배포됨
  4. 사용자가 해당 사이트에 접속할 때 js 실행

즉, hugo 빌드 시점에 템플릿에는 값이 설정되어야 하는데 js가 실행되지 않았으므로 빌드를 완료할 수 없는 것이다.

해결 방법

방법은 전체 html을 만들어 놓고, 필요에 따라 잘라서 집어 넣는 것이다.

<section id="posts" class="article-list">
    <!-- 게시글 목록이 보여질 곳 -->
</section>

<!-- IntersectionObserver로 구독할 element -->
<div id="posts-footer"></div>

<script>
    // 전체 게시글 대상으로 게시글 목록 html을 생성
    // "__pd__"의 경우 게시글과 게시글 사이 구분자 
    var pageHtml = `
    {{ range $index, $element := $filtered }}
        {{ partial "article-list/default" . }}
        {{ "__pd__" }}
    {{ end }}        
    `

    // 구분자로 html을 게시글 html list로 만듦
    var pageHtmlList = pageHtml.split('__pd__')
    // 현재 보여지는 게시글 갯수
    var currCnt = 0
    // 몇 개씩 보여질지
    var paginate = 10
    var intersectionObserver = new IntersectionObserver((entries)=>{
        // intersectionRatio가 0 이하이면 아직 노출되지 않은 것
        if (entries[0].intersectionRatio <= 0) return;

        // 게시글 추가 이후 보여지는 게시글 갯수
        let nextCnt = currCnt + paginate
        for (let i = currCnt; i < nextCnt && i < pageHtmlList.length; i++){
            // 게시글 추가
            document.querySelector('#posts').innerHTML += pageHtmlList[i]
        }
        // 현재 게시글 갯수 업데이트
        currCnt = nextCnt

        console.log('Loaded new items');
    });
    // element 구독 
    intersectionObserver.observe(document.querySelector('#posts-footer'));
</script>

이렇게 하면 정상 구동하는 것을 확인할 수 있다.

위 영상을 보면, 새로고침하고나서 Loaded new items가 한번 출력되고, 이후 스크롤을 내리면서 <div id="posts-footer"></div>가 화면에 노출될 때 마다 Loaded new items가 계속해서 호출되는 것을 볼 수 있다.

후기

쉬울듯 하면서도 생각보다 너무 어려웠다. hugo 템플릿은 브라우저에서 동적으로 작동하지 않는다. 라는 것을 모르는 상태에서 js와 결합하려고 하니 그랬던 것 같다. 그래도 IntersectionObserver를 알게되고, hugo 템플릿의 작동 방식을 알게된 좋은 기회였다.

하지만 조금은 아쉬운 방식이다. 저 방식은 한 html에 모든 데이터를 가지고 있고 보여주는 것만 잘라서 보여주는 것이니까, 본질적으로 한번에 모든 데이터를 보여주는 것과 똑같다고 느껴진다. 조금 더 hugo의 정보를 찾아봐서 다른 방식을 찾아봐야 겠다.

오랜만에 하나하나 내가 원하는 것을 만들어가는 재미를 느낀 것 같다. 앞으로도 더 많이 꾸며봐야지.