개발관련 정보를 공유하는 커뮤니티 서비스로 유저들이 소통할수있는 자유게시판과 뉴스, 블로그, 핫딜 관련 게시글을 크롤링해 제공하는 서비스입니다.
react.js, redux,redux-saga,next.js,styled-componentnode.js,express,sequelize(MYSQL),passport.js,bcrypt,multer,puppeteer.jsReact를 이용해 SPA를 만드는것은 상호작용이 있을 때 빠르고 매끄럽게 동작하는 장점이 있지만 초기로드가 느린점, SEO 관점에서 첫페이지를 빈html로 제공한다는 점이 아쉬웠습니다. Next.js 프레임워크를 이용해 CSR 뿐만 아니라 여러 렌더링 방식을 지원하고 그 외에도 라우터나 이미지 최적화 등 많은 기능을 제공하기 때문에 이전의 프로젝트보다 더 많은 기능을 제공해 성능과 SEO를 최적화하기 위해 선택하였습니다.
프로젝트 초기에는 페이지 로드에 대한 방식을 SSR로 전부 적용했었습니다. 그러나 매 요청마다 서버사이드 렌더링을 적용하면 페이지 이동시 깜빡임 발생, 같은 컨텐츠임에도 다시 서버에서 불필요한 렌더링을 해야하는 점 등의 이유로 변화가 많지 않은 페이지는 SSG, ISR로 교체하는 작업을 진행했습니다. 뉴스,블로그,핫딜은 하루 내지 한시간 단위로 업데이트가 이루어 지기 때문에 그 시간에 맞춰서만 업데이트를 진행하고 그 외에는 정적 페이지를 미리 생성해 더 빠르게 페이지를 제공하도록 했습니다. 그리고 이 프로젝트에서는 css 부분을 styled-component 로 작성했기 때문에 pre-rendering 하는 과정에서 스타일에 관한 부분이 적용이 되지 않았고, _document.js 파일을 수정해 css이 pre-rendering 되도록 수정했습니다.
이 프로젝트는 redux 와 redux-saga를 이용해 상태를 관리합니다. 초기에는 하나의 비동기 요청에 대해 loading, Done, Error 총 3개의 상태를 생성해 관리하는 패턴으로 상태를 구성했습니다.

하지만 비동기 요청이 늘어날수록 관리해야할 상태 개수가 너무 많아지고, loading Done Error 의 모든 상태를 한번에 사용하는 것이 아니기 때문에 좀더 적은 상태로 관리하고 싶은 생각이 들었습니다. 다행히 redux 공식문서에서는 하나의 상태에 문자열 형태로 현재의 상황을 명시하는 패턴을 권장하고 있었습니다.


그 결과, 관리해야 하는 상태의 개수가 2/3 로 줄었고 좀더 간단한 상태로 비동기 요청을 관리할 수 있게 되었습니다. 그리고 API 요청이 실패했을때 사용자에게 실패에 대한 메시지를 보여줘야 하는데 서버로부터 받은 메시지를 그대로 보여주면 보안에 위협이 될 수 있기 때문에 에러메시지를 그대로 보여주지 않고 메시지모달을 통해 다른 형태로 알려주도록 처리했습니다.

처음에는 단순하게 하나의 컴포넌트에 스타일, 기능에 대한 로직을 전부 포함해서 작성을 했습니다. 프로젝트가 진행될수록 한 파일에 차지하는 코드가 많아지고, 컴포넌트를 분리해도 스타일에 관한 부분과 길어지는 함수와 변수, 상태에 관한 로직을 분리할 필요성을 느꼈습니다. 스타일은 혼동이 되지 않게 하나의 폴더에서 컴포넌트 파일과 스타일 파일이 위치하도록 구성했고 일부 길어지는 기능에 대한 부분은 hooks 폴더에 정리했습니다. 게시글의 필터기능, 인피니트 스크롤링, 사가함수, 유효성 검증관련 함수, input 상태관리 핸들러 등을 분리해 정리하고 일부는 재사용했습니다.


프로젝트에서는 두 가지 방식의 페이지네이션을 구현했습니다.
전자는 게시글 목록을 구현할 때 사용하고, 후자는 하나의 게시글안에서 댓글목록을 표시하는데 사용했습니다. 원리는 간단합니다. 백엔드로부터 전체 페이지수를 받아오고 개수를 바탕으로 페이지 버튼의 개수를 계산하고 현재 페이지의 위치에 따라 왼쪽, 오른쪽 페이지 이동 버튼을 활성화/비활성화 시키면 됩니다. 다만 전자의 방식은 페이지 이동시 router.push()를 이용해 페이지 자체를 이동시킨뒤, 초기로드에서 해당하는 게시글을 불러왔고, 후자는 페이지위치에 따라 댓글을 잘라서 필요한 부분만 보여주는 방식입니다.
글 작성시 이미지 업로드에 대해 미리보기를 구현했습니다. file 타입의 input 태그를 이용해 이미지를 첨부하면, 자동으로 첨부한 이미지 파일을 formdata에 넣어 백엔드로 요청을 보냅니다. 백엔드에서는 multer를 이용해 해당 이미지를 디스크에 저장하고 이미지경로를 응답으로 보내 프론트에서 이미지를 띄우도록 구현했습니다.

처음에는 로그인시 단순히 성공/실패 로 처리로 구현을 했지만 실제 웹사이트에서는 ID/Password 에 따라 각기 다른 응답을 보여주는것이 일반적입니다. ID가 존재하지 않는경우, 비밀번호가 틀린경우, 탈퇴한 ID인 경우 등으로 분기를 나누어 응답하도록 처리했습니다.


처음에는 스크롤 이벤트를 이용해 화면의 일정 스크롤 높이에 도달할 경우 추가 게시물을 요청하는 방식으로 구현을 했습니다. 하지만 스크롤을 이동시키면서 너무 많은 이벤트 핸들러가 실행되고 이것들이 쌓이면 성능에 영향을 주는것을 알게 되었습니다. 그래서 이 방식보다 개선된 방식인 intersection observer를 이용한 인피니트 스크롤링 방식으로 교체를 했습니다. intersection observer의 원리는 특정 요소에 대해 observe를 걸어놓고 특정 요소가 화면에 보이는 순간 추가적인 게시글을 요청하는 방식입니다.

스크롤을 이용한 인피니트 스크롤링 방식

intersection observer로 변경한 인피니트 스크롤링 방식
passport.js를 이용한 세션인증 방식을 바탕으로 로그인기능을 구현했습니다. 세션인증 방식의 장점은 인증정보를 서버에 보관하고 세션id값만 클라이언트로 전달하기 때문에 외부에 개인정보를 노출하지 않아도 되는 장점이 있습니다. 단점으로는 로그인한 유저가 많아질수록 서버에 쌓이는 인증정보가 많아지고 서버의 부하가 심해진다는 점이 있지만 이 부분은 추후에 redis에 세션정보를 저장해 보완할 수 있는 부분입니다.
여러 사이트들을 둘러봤을 때 크게 두가지의 패턴이 있다고 생각했습니다.
저 같은 경우는 끊임없이 들어가는 방식은 댓글이 많아질경우 시각적으로 구조를 파악하기 어렵다고 판단해서 한 단계만 안쪽으로 들어가는 방식을 선택했습니다.
처음에는 댓글과 대댓글을 관리하는 DB테이블을 따로 만들어서 관계설정을 하는 방식으로 관리를 했습니다. 하지만 중복되는 attribute가 많고 해당페이지를 시퀄라이즈 메소드로 가져오는 과정에서 데이터 객체의 깊이가 너무 증가하는 경향이 있어 하나의 Comment 테이블을 만들고 그 안에 CommentId로 댓글과 대댓글을 연결하는 방식으로 변경했습니다.