[Hugo] [개선판] 무한스크롤을 구현해 보자

좀 더 나은 무한스크롤을 구현해 보자

이전 버전의 문제

저번 게시글에서 무한 스크롤을 구현했지만, 하나의 html에 모든 데이터를 가지고 있는 구조였다. 해당 구조는 게시글이 1,000개 혹은 10,000개가 된다면, 메인 페이지를 불러오는데, 많은 네트워크 자원을 소비하게 될 것이다. 실제로 게시글 200개 정도로 테스트해 보니, 요청에 600kb를 사용하는 것을 확인했다.

방법을 찾자

처음 발상은 이랬다.

특정 범위에 해당하는 게시글 리스트를 미리 저장해놓고, http GET 요청을 통해 그때그때 가져오면 되지 않을까?

그런데 여기에는 아래와 같은 조건이 붙는다.

  • html로 만들어진 게시글 리스트가 필요.
    • 테마가 업데이트되어도 수정 없이 무한 스크롤을 구현하고 싶음.
    • json과 같은 데이터는 js로 html을 만들어야 하므로, 현재 사용하는 테마가 업데이트되면, 필자가 작성한 js도 수정해야 함.

즉, 5개씩 로딩하고 싶다면, 아래와 같은 리스트를 5개씩 html 형태로 빌드하고, 해당 데이터를 http get을 이용해 가져와야 한다는 것이다.

메뉴 리스트

여러가지 생각이 들었는데,

  • 게시글.md게시글.html로 바꿔주는 로직이 있으니, 게시글.md게시글.html, 게시글-타이틀.html로 만들어 줄 수 있겠다.
    • 공식문서를 살펴보니, 게시글.md는 무조건 하나의 게시글.html로 치환되도록 설계되어 있음.(공식 문서)
    • 적용 불가
  • Pagination 기능을 활용해 볼까? (공식 문서)
    • Pagination 기능을 활성화하면, /page/${pageNum}으로 접근 가능함.
    • Paginationindex.html을 총 페이지 숫자에 맞게 여러 개 생성해 줌.
    • 그래서 index.htmlPagination 템플릿을 삽입하면 각 페이지에 맞는 컨텐츠를 삽입해 줌.
    • index.html
      {{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }}
      {{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }}
      {{ $filtered := ($pages | intersect $notHidden) }}
      {{ $pag := .Paginate ($filtered) }}
      
      <section id="posts" id="posts" class="article-list">
          {{ range $index, $element := $pag.Pages }}
              {{ partial "article-list/default" . }}
          {{ end }}
      </section>
      
    • 위와 같은 index.html이 있고, paginate=5로 설정해둔다면,
      • index.html1~5 게시글이 삽입되어서 빌드.
      • page/1/index.htmlindex.html으로 리다이렉팅하게끔 빌드.
      • page/2/index.html6~10 게시글이 삽입되어서 빌드.
      • 즉, index.htmlpage/{pageNum}/index.html은 기본 구조가 동일
    • 그렇다면 이것을 활용하면 되겠다.

그래서 Pagination을 이용해서 개발을 진행하기로 한다.

구현

hugo.toml 수정

baseURL = 'https://fhdufhdu.github.io/'
languageCode = 'ko-KR'
title = 'fhdufhdu'
theme = 'hugo-theme-stack'
paginate = 5  //추가

index.html 수정

<section id="posts" class="article-list">
    {{ range $index, $element := $pag.Pages }}
        {{ partial "article-list/default" . }}
    {{ end }}
</section>

<div id="posts-footer"></div>

<script>
    var postsId = '#posts'
    var http = new XMLHttpRequest();
    var page=2 // 1 페이지는 index.html에 들어가 있으니까. 2 페이지로 init value 지정
    var intersectionObserver = new IntersectionObserver((entries)=>{
        // intersectionRatio가 0 이하이면 아직 노출되지 않은 것
        if (entries[0].intersectionRatio <= 0) return;

        const url = `/page/${page}/`
        console.log(`${page}페이지 로드 중...`)
        // http 요청을 통해 page html 가져오기
        fetch(url)
            .then((response) => {
                // ok가 아니라면 마지막 페이지라는 것
                if (!response.ok) throw new Error(`${page-1}페이지가 마지막입니다`) 
                return response.text()
            })
            .then((html) => {
                // 데이터 파싱을 위해 html을 가상의 element에 넣음.
                const element = document.createElement('temp')
                element.innerHTML = html

                // #posts 데이터를 들고와서 innerHTML 추가(innerHTML에는 게시글 리스트가 있음)
                document.querySelector(postsId).innerHTML += element.querySelector(postsId).innerHTML
                console.log(`${page}페이지 로드 완료`);
                page++
            })
            .catch((err)=>console.log(err.message))

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

후기

이전 게시글에서 무한 스크롤을 구현하고나서 많이 뿌듯했지만, 한편으로는 가슴속에 남아있는 불편함이 있었다. 보기에는 무한 스크롤이지만, 실제로는 무한 스크롤이라고 부르기는 좀 좋지 않은 구조였기 때문이다. 실제로 게시글 수가 많아질수록 쓸데없이 네트워크 자원을 소비한다는 문제가 있는 구조이다.

어쨌든, 좀 더 나은 코드를 작성하는 것이 바로 개발자의 역할이 아니겠는가? 이 불편한 감정을 해결하기 위해 고민을 오래 했던 것 같다. 샤워하다가도 생각나고, 밥먹다가도 생각나고, 출근하다가도 생각이 났다.

그런 상황에서 해결하고 나니, 진짜 진지하게 속이 뻥 뚫리는 느낌이다. 이래서 개발을 못 끊나 보다.