기술 (5)
문제해결 (1)
회고 (2)
기타 (1)
Next.js로 블로그 만들기
회고
2024년 10월 06일
계기
개발자에겐 그 동안 자신이 학습한 내용들과 겪었던 문제와 해결 과정을 기록하는 것이 정말 중요하다는 생각이 들었습니다.
이는 개발을 시작한 지 얼마 되지 않았을 때 부터 알고 있었지만, 아직까지도 잘 습관이 들지 않아 꾸준하게 포스트를 작성하지 못하고 있었습니다.
가끔씩 패스트캠퍼스 깃헙 조직을 통해 멘토님들의 깃헙을 둘러보는데 프로필에 개인 기술 블로그를 링크해놓으신 분들이 몇몇 계셨습니다.
블로그마다 각자의 개성이 보이고, UX를 고려하여 블로그를 개발하신 느낌이 들었습니다.
그러면 나도 웹 개발자인데 개인 블로그 하나쯤은 직접 개발해서 운영할 수 있지 않을까 ?
하는 생각이 바로 들었고 내가 원하는 디자인, 기능, 나만의 도메인으로 기술 블로그를 운영하게 된다면 더 애정이 갈 것이고 꾸준히 포스팅을 할 수 있을 거라는 생각이 들기 시작했습니다. 그래서 바로 개발을 시작하였습니다.
사용한 기술 & 스택
- Next.js
우선, 가장 중요한 웹 개발 프레임워크로써 Next.js
를 선택했습니다.
가장 익숙한 도구이기도 하고 이미지, 폰트 최적화 및 SEO
까지 보장해주기 때문에 선택하게 되었습니다.
- TypeScript
TypeScript
는 정적 언어이므로 컴파일 단계에서 타입 에러를 미리 확인할 수 있어
실행 전에 버그를 사전에 방지할 수 있습니다. 따라서 코드에 안정성을 더하여 개발할 수 있기 때문에 선택하게 되었습니다.
- Tailwind CSS
CSS 프레임워크로는 Tailwind CSS
를 사용했습니다. 정의된 클래스명으로 빠르게 퍼블리싱이 가능하고 구성을 통해
디자인 시스템을 통합할 수 있습니다.
- Zustand
상태 관리 라이브러리로는 Zustand
를 사용했습니다. Zustand
는 간결하고 직관적인 API를 제공하여 간단하고 빠르게 전역 상태 관리를 할 수 있어 선택했습니다.
개발 과정
퍼블리싱
Tailwind CSS
와 shadcd/ui
를 사용하여 레이아웃을 구축했습니다.
개발할 페이지는 메인페이지와 포스트 페이지, About 페이지였는데 각각의 페이지에 개발한 기능은 다음과 같습니다.
-
메인페이지
- Left sidebar: 전체 글을 카테고리 별로 조회
- 전체 글을 카드 형식으로 조회
-
포스트 페이지
- Left Sidebar: 전체 글을 카테고리 별로 조회
- Right Sidebar: 포스트 목차 조회, TOC(Table Of Contents)
- MDX를 html로 변환하여 포스트 내용 조회
giscus
를 사용한 댓글 기능
-
About 페이지
- 개발자 프로필 조회
- 개발자의 프로젝트와 자주 사용하는 기술 조회
각 페이지의 기능에 맞게 레이아웃을 구성하고 Tailwind CSS
에서 제공하는 기본 Break Point
에 맞추어 반응형 디자인을 구현했습니다.
MDX 변환
MDX 변환 관련 라이브러리는 다음과 같습니다.
next-mdx-remote
Next.js에서 MDX파일을 동적으로 불러와서 렌더링을 할 수 있게 해주는 라이브러리입니다.
next-mdx-remote
와 함께 사용한 플러그인은 다음과 같습니다.
-
remark-gfm
: 리터럴 자동 링크, 각주, 취소선, 표, 작업 목록을 지원합니다. -
remark-breaks
: markdown은 기본적으로 두 줄 개행인데, 한 줄로도 개행하게 해줍니다. -
rehype-pretty-code
: 코드 블록을 깔끔하게 하이라이팅하고, 코드에 스타일을 적용하기 위해 사용합니다. -
rehype-slug
: TOC를 구현하기 위해 사용한 플러그인으로, mdx문서에서 변환된 heading태그에 id값을 지정해줍니다.
하이라이팅된 MdxComponents
는 MDX에서 변환된 html요소를 개발자가 커스텀하여 재구성해주는 컴포넌트입니다.
저는 아래와 같이 작성했습니다.
발생한 문제
여기까지 개발을 완료했을 때, 개발 환경에서는 원했던 기능 모두 아주 잘 동작하였습니다.
그런데 문제는 vercel
에 배포했을 때 포스트 페이지가 SSR로 동작했기 때문에 서버에서 html을 구성하여 화면을 그릴 때 속도가 굉장히 느렸습니다.
개발 환경 포스트 상세 페이지 LCP 측정 결과
개발 환경 포스트 상세 페이지 성능 측정 결과
배포 후 포스트 상세 페이지 LCP 측정 결과
배포 후 포스트 상세 페이지 성능 측정 결과
원인
이제까지 개발한 경험을 떠올려보면 문제를 해결하기 위해 가장 먼저 해야하는 것은 정확한 원인을 분석하는 것 이었습니다.
다음은 포스트 페이지의 코드입니다.
처음 생각했던 원인은 getPost
함수였습니다.
혹시 로컬에 있는 MDX파일을 파싱하는데 시간이 오래걸리나? 라는 생각이 들었고, 그렇다면 Headless CMS
를 사용하여 클라우드 상에 MDX를 띄워놓고 html로 변환하는 방식으로 구현해보면 될 것이라고 생각했습니다.
그래서 브랜치를 새로 생성하고 Sanity
와 Route Handlers
를 활용하여 데이터를 패칭하는 방식으로 해보았습니다.
/api/posts/[id]에 데이터 띄우기
Sanity Studio로 데이터 관리하고 확인하기 1
Sanity Studio로 데이터 관리하고 확인하기 2
이렇게 데이터를 담아두고 로컬에 있는 MDX파일이 아닌 띄워져 있는 데이터를 받아와서 랜더링하는 방식으로 변경하고, 다시 배포를 해보았습니다.
MDX파일과 썸네일을 로컬에서 관리하는 것이 아니라 클라우드 상에 데이터로 관리하는 점에서 프로젝트 폴더와 게시물을 독립적으로 관리할 수 있다는 장점이 있긴 했지만,
결과적으로 여전히 렌더링 속도는 개선되지 않았습니다...😇
그래서 저는 다른 이유를 찾기 시작했습니다. 이번에는 getPost
함수가 아닌 포스트 페이지의 각 3개의 컴포넌트에 집중했습니다.
결국 포스트 페이지의 렌더링이 늦어지는 것이므로 PostHeader
, PostBody
, Comments
각각의 컴포넌트들을 차례로 제거해보며 테스트를 시작해보았습니다.
그런데 배포환경에서는 렌더링 속도가 늦어지는 오류를 알 수 없었기 때문에 코드를 수정하고 배포해서 테스트 결과를 확인하고를 반복했습니다.
이러한 테스트 방법은 절대로 옳지 않다고 생각하지만, yarn build && yarn start
를 통해 배포 환경을 테스트 했을 때에도 vercel
에 배포했을 때와 다른 결과를 보여주었기 때문에 현재 저의 능력으로는 이 방법으로 할 수 밖에 없었습니다. 역시 개발자는 문제를 맞닥뜨리면서 깨져봐야 자신의 기술의 한계를 파악하고 더 성장할 수 있게 되는 것 같습니다...😢
어쨌든, 결론적으로 랜더링 속도 저하의 문제는 PostBody
컴포넌트에 있었다는 것을 알게 되었습니다.
PostBody
컴포넌트에서는 next-mdx-remote
라이브러리를 사용하여 MDX문서를 HTML로 파싱하는 작업을 하는데 이 과정이 오래걸린다고 판단했습니다.
구글링을 통해 next-mdx-remote
를 사용한 블로그에서 저와 같은 렌더링 속도 저하 문제를 겪은 이슈가 있는 지 알아보았지만, 큰 도움이 될만한 정보는 얻지 못했습니다.
2~3일간 고민하던 중 최적의 렌더링 방식이 무엇일까하는 고민에 들어서기 시작했습니다. 제가 개발중인 블로그는 정적인 웹사이트의 가장 대표적인 예시이고, 그렇다면 정적 렌더링 방식을 적용하면 사용자가 빠르게 웹사이트를 확인할 수 있지 않을까하고 생각했습니다. SSG 랜더링 관련 Next.js 공식문서를 읽어보며 프로젝트에 적용하였습니다.
코드를 수정하고 배포를 하여 결과를 확인해보았습니다. SSG 렌더링 방식은 빌드시점에 지정한 페이지의 HTML을 미리 생성하기 때문에 전보다 훨씬 빠르게 포스트 페이지를 확인할 수 있었습니다. LCP가 무려 3000ms에서 200ms로 대략 93% 감소되었습니다. 하지만 저의 고민은 여기서 끝나지 않았습니다.
만약 데이터(포스트의 내용)가 변경된다면 ?
실제로 테스트할 포스트를 추가한 뒤 빌드를 해보니 다음과 같은 에러가 발생하였습니다.
빌드 에러 메시지
{ cache: "no-store" }
옵션을 주었기 때문에 Sanity studio
에서 데이터가 변경되면 즉시 변경된 데이터를 /api/posts/[id]
경로로 띄웁니다.
위 코드의 흐름을 살펴보면 getPost
함수는 { cache: "no-store" }
라는 옵션을 적용하여 Sanity studio
의 데이터가 변경될 떄 즉시, 변경된 데이터를 반영하여 /api/posts/[id]
경로로 데이터를 띄워줍니다.
/api/posts/[id]
경로에 띄워진 데이터를 바탕으로 포스트 페이지에서 fetch
메서드를 사용하여 데이터를 받아옵니다. 하지만 이 때의 fetch
메서드는 캐시된 데이터를 받아오고 있으므로 실제 변경된 데이터와의 차이가 발생하면서 정적 파라미터를 생성할 수 없다고 생각했습니다.
이 문제를 해결하기 위해 MDX파일을 다시 로컬파일로 관리하는 로직으로 수정했고, 이제는 문제없이 정적 파라미터를 생성할 수 있었고 배포 시 SSG 랜더링도 성공적으로 할 수 있었습니다.
마무리
개인 블로그 개발은 데이터가 수시로 변경되는 웹사이트가 아니다보니까 쉽게 개발할 수 있을 것이라 생각했는데, 정말 예상치 못하게 랜더링 속도가 너무 느려서 이를 해결하고 개선하기 위해 여러 고민과 노력들을 하며 많이 배우게 되었던 프로젝트라고 생각합니다.
아무튼 가장 기본적인 블로그의 구색은 갖추어 개발을 완료할 수 있어서 저는 개인적으로 굉장히 뿌듯한 경험을 한 프로젝트였습니다.
마지막으로 새로 배우게 된 점과 개선할 점에 대해서 정리하며 포스팅을 마무리하도록 하겠습니다.
새로 배우게된 점
- 개발자 도구를 통한 성능 검사
- 랜더링 속도 최적화
- SSR, ISR, SSG, CSR 각각의 정의와 활용
- IntersectionObserver 인스턴스
- next.js에서 fetch의 데이터 캐싱 활용
개선할 점
- 포스트와 프로젝트를 분리하여 독립적으로 포스팅을 하기위해
Headless CMS
를 사용하거나AWS
의S3 Bucket
을 사용하여 클라우드 상에서 MDX파일과 에셋을 관리하기 - 포스트 내용에 변경사항(추가, 삭제, 수정 등)이 생기면 바로 웹사이트에 적용할 수 있도록
CI/CD
구축 - 이미지 최적화
- 포스트의 개수가 많아짐에 따라 메인페이지의
LCP
시간 단축하기 위한 방법 고민
해당 글은 목차가 없습니다.