목차
1. 프로젝트 소개
2. 프로젝트에서 맞닥뜨린 문제들
3. 얻은 것들, 잘한점과 아쉬운점
이 프로젝트를 시작하게된 계기는 Next.js의 활용이다.
기본적인 쇼핑몰 사이트를 만들어 보면서 Next.js가 어떻게 쓰이는지 이해할 수 있었다.

1. 프로젝트 소개
프로젝트는 기본적인 쇼핑몰 구조를 가지고 있다.
메인페이지, 회원가입/로그인, 상품 페이지, 상품 상세페이지, 장바구니 담기, 마이페이지 장바구니 확인, 회원탈퇴, 정보변경, contact.
깃허브 : https://github.com/Goddemi/next-blog
배포 : https://next-blog-3ffp.vercel.app/
주로 쓰인 기술
Next.js / Redux / useQuery / Firebase
Next.js : SSG를 통한 상품페이지 구성, API Route를 통한 백엔드 CRUD 구성
Redux : 로그인 모달 form 상태, 로그인 상태 관리
useQuery : 기존 SSG 데이터 클라이언트 사이드에서 업데이트, 동일 데이터 통신 효율화
Firebase : real time 데이터베이스, Firebase 내장 Authentication 사용.
1) 상품페이지
최상단메뉴의 all products를 통해서 모든 상품들을 볼 수 있고, 그 중 isFeatured 속성이 true로 되어 있는 상품들에 한해 메인페이지에 표기될 수 있도록 하였다.

해당 페이지의 getStaticProps (SSG) 안에 상품을 가져오는 axios 통신을 두어 빌드시에 데이터가 사전생성 될 수 있도록 하였다.
중복되는 코드의 별도 코드 분리
상품 진열이 Allproducts페이지와 Main페이지에 중복되서 나타났다.
상품을 나열하는 ProductsGrid - ProductsItem라는 컴포넌트를 만들어서 필요한 곳에서 데이터값을 props로 전달받아 매핑할 수 있게 하였다.

데이터를 받아오는 함수도 마찬가지로 중복 되서 쓰였다.
별도의 함수로 만들어서 파라미터를 통해 필요한 곳에서 가져다 쓰는 방식으로 사용하였다.
에러의 경우 단순히 console.log로 찍어주기 보다 각각의 에러에 맞게 에러 내용을 UI에 표기해 주는 식으로 핸들링 하였다.

상품 데이터의 중복 요청에 대해 useQuery를 통해 동일 key값으로 요청한 데이터에 대한 효율성을 증가시켰다.
다만 데이터의 초기값은 빠르게 화면에 나타날 수 있도록 SSG 사전생성을 통해 구성하였다.

각 상품들을 클릭했을 때 해당 상품의 이름을 엔드포인트로 하는 상세페이지 링크로 이동할 수 있게 하였다.
next.js의 동적라우팅 기능을 사용하여 상세페이지를 구현하였다.


getStaticProps를 통해 상세페이지의 데이터가 사전 생성될 수 있게 하였다.
이 때 동적페이지의 경우 어떤 페이지를 사전생성해야 할지 별도로 설정해줘야 하기 때문에 getStaticPaths를 통해 지정해 주었다. (
(아래 코드의 'mercury'에 특별한 사유가 있었던 것은 아니고 예시 적용을 위해)
fallback을 blocking으로 지정해두어 만들어 놓지 않은 path에 대해 요청이 들어왔을 경우 SSR처럼 동작할 수 있게 하였다.

2) 인증 / 인가
상단 Nav바에 login 버튼을 클릭하면 어떤 페이지에서든 로그인 창이 활성화 될 수 있도록 Redux를 사용하였다.
상품 상세 페이지에서 장바구니 버튼을 클릭했을 때, 로그인이 안된 상태라면 로그인 버튼이 나오게 구현 하였다.



로그인 클릭 이벤트에 대한 함수
이전까지 Form에서 input에 대한 값을 입력받을 때, onChange와 useState를 이용했었는데 이번에는 Ref를 통해서 진행해 보았다.
굳이 입력을 하나하나 할 때마다 상태값이 변화할 필요가 있을까 라는 생각을 했었고, 리소스가 조금이라도 덜 낭비될 수 있는 방법이라고 생각해서 사용해 보았다.
로그인을 하면 세션스토리지에 토큰 값이 저장된다. 세션스토리지 값을 getSession해서 값이 있으면 토큰을 받아 로그인된 상태를 전역 상태로 쓰고 있다. setTimeout 을 지정하여 로그인 성공 여부에 대한 결과값을 보여준 뒤에 모달창이 꺼지도록 하였다.

각 실패 원인 별로 핸들링을 해주었다.
![]() |
![]() |
![]() |
![]() |

통신 성공 실패 여부에 대해서 사용자에게 알려주는 notification 이 로그인 / 회원가입 / 장바구니에서 중복되서 필요했기 때문에
별도의 컴포넌트를 만들어서 필요한 곳에서 import해서 사용하는 방식으로 만들어 보았다.


3) 마이페이지 (장바구니 + 회원정보 변경)
![]() |
![]() |
장바구니에 상품을 넣으면, 해당 상품 고유의 상품아이디를 생성해서 유저 토큰과 함께 데이터베이스에 저장한다.

장바구니에 들어가면, 로그인한 사람의 토큰 값을 통해 장바구니 상품 데이터를 요청한다.
장바구니에서 삭제 할 때는 상품을 장바구니에 넣을 때 생성한 productId를 통해 데이터베이스에 delete 요청을 보낸다.
비밀번호 변경과 회원탈퇴는 비밀번호 재확인이 된 이후에 가능하게 하였다.

회원탈퇴의 경우 재확인 메시지가 전송된 다음, ok를 눌렀을 때 회원탈퇴가 진행되게 구성 하였다.

2. 프로젝트에서 맞닥뜨린 문제들
- 새로고침시 로그인 유지하기
가장 많은 시간을 소요한 부분 중에 하나이다. 쉽게 해결할 수 있는 문제라고 생각했는데 생각지도 못했던 부분에서 차질이 있었다.
새로고침시 로그인이 유지될 수 있게 처음 계획했던 방법은, 로그인할 때 세션스토리지에 토큰 값이 저장이 되게 해두었으니, 리 렌더링시에 세션스토리지 로그인 값이 있으면 로그인 상태를 유지하는 방법이었다.
작업 중 발생한 첫번째 문제는 next.js에서 sessionStorage에 접근할 수 없는 것이었다.
ReferenceError: sessionStorage is not defined
이는 Next.js가 클라이언트 사이드 렌더링 전에 서버 사이드 렌더링을 수행하기 때문에 (클라이언트 사이드 객체인 'window` 객체가 정의되지 않은 시점) localStorage, sessionStorage에 접근이 불가능한 문제였다. 즉 페이지가 클라이언트 사이드에서 로드되고 window 객체가 정의 된 이후에 접근이 가능했던 것.
이를 해결할 수 있는 방법이 window 객체의 존재 유무를 살피고 window 함수에 접근하는 것.
if (typeof window !== 'undefined') {... }
이렇게 코드를 작성해서 해결되는듯 하였으나 또 다른 문제가 발생했다.
Error: Hydration failed because the initial UI does not match what was rendered on the server.
서버사이드에서 pre-rendering된 React 트리와, 브라우저에서 rendering되는 React 트리가 달랐기 때문에 발생한 에러이다.
결과적으로 window 객체를 사용했기에 발생한 문제였다. 그런데 그렇다고 window객체를 사용하지 않으면 session Storage를 접근할 수 없었다. Next.js 공식문서에 나와있는 해결책은 useEffect를 사용 하라는 것이었다.
useEffect는 클라이언트 사이드에서 실행되기도 하였기 때문에 다행히 session Storage 접근 시에도 좋은 해결책이 되었다.
여기까지의 시행착오를 통해 정리한 로직은,
페이지 마운트시 useEffect를 통해 sessionStorage안의 로그인 유저 값을 확인하여 로그인 상태를 체크하는 것.
그러면 새로고침으로 인한 리로딩이 발생하더라도 sessionStorage에 값이 아직 있으니 로그인 상태가 유지될 것이라고 생각했다.
로그인 상태값이 필요한 페이지마다 session Storage값을 불러오는게 비효율적이라고 생각되어 Redux의 initial state에 값에 지정 해주는 방식을 추가하였다.
사이트에 처음 들어 갔을 때는 로그인을 하지 않은 상태이기 때문에 세션스토리지에는 값이 없는 상태이고 그로 인해 Redux의 전역 로그인상태는 false가 된다.
로그인을 하면, 세션스토리지에 값이 들어가고 그 값을 Redux의 로그인 상태 초기값 initial State에 넣어주어서, 새로고침으로 리 로딩되면 세션스토리지 값이 Redux 상태 초기값에 들어가 로그인 상태가 true인 상태를 유지할 수 있을 것이라고 생각했다.
그런데 또다른 문제가 생겼다..
Redux 초기값 initial State에 useEffect를 통해 얻은 로그인 세션스토리지 값을 넣어주는 것이 불가능했던 것.
Redux inital State를 관리하는 곳은 Javascript XML (jsx) 파일이 아닌 js 파일이고, useEffect는 컴포넌트 안에서만 쓸 수 있는 React Hook이였기 때문이다.
이 문제를 해결할 수 있도록 생각해낸 방법은 initial State은 그대로 " " 빈 값으로 주되, 로그인 상태값이 필요한 곳이 Mount될 때 Redux action 을 통해 상태값을 변경시켜 주는 것이었다. 그러면 새로고침시에 초기 값은 빈 값이어도 컴포넌트가 마운트되며 변경 값을 넣어주기 때문에 초기값에 세션스토리지 안의 값을 덮어 씌울 수 있게 되는 것.

useEffect를 통해 Nav 컴포넌트가 마운트될 때 loggedIn action이 실행될 수 있게 하였고, 결과는 성공적이었다 !
- Next.js의 _document.tsx 역할


공식문서의 중요성을 이렇게 또 깨달았다. 처음에 _document안에서 레이아웃 작업을 했었다. (레이아웃 작업하기 좋게 생김..)
정말 이상하게도 UI들은 다 나왔으나 그 안의 로직이 아무것도 작동하지 않았다. 그 흔한 console조차 나오지 않아 매우 혼란스러웠다.
_document는 서버에서 실행되어 브라우저 api, 이벤트 핸들러가 포함된 코드는 실행되지 않는 다는 것을 배웠다.
해결 : 별도의 레이아웃 파일을 생성해서 코드를 옮겼더니 아주 잘 동작하였다.
- useRef ref값의 props전달.
<InputForm name={name} ref={inputRef} />
ref 값을 props로 전달 해주고 있는 코드이다. 그런데 ref라는 속성 자체를 props로 전달할 수 없었다.
ref는 예약어(쓰임이 이미 정해져있는 키워드) 이기 때문. 다른 이름으로 전달해주거나, forwardRef를 사용해야 했다.
전달받는 컴포넌트를 forwardRef로 감싸주어서 해결할 수 있었다.

그런데 이때 위와같은 또 다른 에러가 발생했다.
forwardRef() 함수를 호출할 때 익명 함수를 넘기게 되면 브라우저에서 React 개발자 도구를 사용할 때 컴포넌트의 이름이 나오지 않기 때문에 발생하는 에러이다. 화살표 함수를 이용했기 때문에 생긴 에러이기도 하다. 타입스크립트에서만 발생하는 에러라고 한다.
구글링을 통해 직접 display name을 지정해주면 된다는 것을 알게 되어, Input.displayName = "Input"; 을 직접 지정해서 해결하였다.
- Firebase의 database POST
장바구니에 물건을 넣을 때 생겼던 문제이다.
파이어베이스에 데이터를 포스트하면, 파이어베이스에서 만들어준 고유의 id값을 키 값으로 데이터가 들어가고 있었다.
예를들어 const greeting = {hi : "ho"} 라는 greeting 오브젝트를 POST하면 데이터베이스에는

이렇게 들어가 있었다.
이미 고유의 productId를 만드는 로직이 데이터에 있었기 때문에, 굳이 추가로 키 값을 만들 필요가 없었다. firebase 공식문서 확인 결과 POST method를 이용하면 별도의 firebase에서 생성한 고유키 값을 통해 데이터가 들어가고 PUT method를 이용하면 키 값 없이 가능했다.
다만 PUT method를 사용하면 모든 데이터를 overwrite하게 되는 것이 문제였다.
기존에 있는 데이터를 GET해온 다음에 그 데이터와 새로운 데이터를 합쳐서 새로운 데이터를 만들어서 PUT 함으로서 해결할 수 있었다.
그 밖에 잔잔한 것들.
- Form안에 버튼이 두개가 있었다. enter를 눌렀을 때 적용되는 submit이 두개의 버튼 중 하나에만 적용되게 하고 싶었다. 버튼 중 하나는 enter+click에 반응하고 하나는 click에만 반응할 수 있게. 버튼의 타입을 type=submit, type=button 으로 구분해서 나누면 되었다.
- key값을 변수로 적용하는 것.
장바구니 넣기를 통해 데이터를 데이터베이스에 저장할 때 고유의 id와 함께 저장하고 싶었다. 데이터 저장을 위해 키 값이 필요한 상황이었는데 데이터의 키값 자체를 고유 id로 동적으로 지정하면 나중에 delete할 때도 더 쉽게 할 수 있을것이라 생각했다. 기본적인 것 같았는데 그동안 써보지 못했던 것이었다. key값(id)을 변수로 넣는 방법 대괄호 이용.
const object = { [id] : content }
- Modal 창 만들기. 아래와 같은 구조를 만들어두고 AuthForm의 형제 div에게 배경 역할을 맡긴 뒤, 온클릭시 모달창이 닫히게 되는 함수를 넣는다.
<div className="flex justify-center items-center fixed inset-0 z-10">
<div
className="fixed inset-0 bg-zinc-200/50"
onClick={() => dispatch(authModalOff())}
></div>
<AuthForm />
</div>
- tailwind : z-index 문제. index문제가 분명한데 인덱스를 z-1, z-2 아무리 줘봐도 안됐었다. 이 문제가 아닌가 싶어 한참을 이것 저것 다 건드려보다가 결국 태일윈드의 z-index는 10단위라는 것을 알았다. z-10, z-20 ...
3. 얻은 것들, 잘한점과 아쉬운점
- Next.js의 활용성
이론으로만 배웠던 SSR, SSG을 직접 적용해보며 이해할 수 있게 되었다. api route를 통한 백엔드 구성과, 페이지 라우팅의 간편함에 놀랐고 막연하고 어려워 보이던 서버사이드렌더링에 대해 익숙해질 수 있었다. 다만 이것을 쓰는 핵심적인 이유에 대해 체감할 수 없었던 것이 아쉽다. 대량의 데이터로 인해 페이지가 버벅거렸지만 그것이 next.js SSR을 통해 개선된다거나, SEO의 개선을 통해 사이트의 접근성을 높이는 것과 같은 것들. 그래도 어떻게 사용해야 하는지 배웠으니 앞으로 일할 곳에서 잘 활용하여 효과를 얻을 수 있을 것이라 생각한다.
- Error Handling
그 동안은 통신 결과에 대한 에러를 throw하고 console.log로 찍어 핸들링 했던 경우가 많았다. (핸들링이라고 할 수 도 없나..ㅎ) 이번에는 어디서 어떤 실패가 생겼는지 파악하고 그 문제를 UI를 통해 사용자에게 알려주는 것까지 구현해 보았다. Notification 컴포넌트를 통해 에러별로 통신 결과값을 표기하였다. response되는 값들을 찾아 각각의 조건에 맞는 메시지들을 매칭하였다.
-Clean code
하나의 컴포넌트, 하나의 함수가 하나의 역할만 할 수 있도록 노력했다. 중복된 함수, 컴포넌트는 따로 빼서 재사용할 수 있게 했다. 컴포넌트를 분리하여 코드가 줄어들고 가독성이 올라가는 것을 보며 짜릿함을 느꼈다. 이정도면 괜찮지에서 조금만 더 줄여보자는 힘이 생기게 하였다. 이런 저런 기능이 섞여 있는 것에 불편함을 느낄 수 있게 되고 좋은 코드가 어떤 코드인지에 대해서 더 잘 알 수 있게 되었다.
- tailwind의 활용
tailwind가 왜 큰 규모의 프로젝트에서 쓰이지 않는지, 개인 프로젝트에서 최고인지 알았다. 단순히 css파일을 만들지 않고, css 부분과 html 부분을 왔다갔다 하지 않아도 되는 부분에 대한 편리성 외에도, className을 만들지 않아도 된다는 것이 큰 장점이자 단점이라고 생각했다. 만들지 않아도 되니 class명에 대한 고민이 없어 무지하게 빠르지만, 이게 무슨 역할을 하는 엘리먼트인지 알기 어려웠다.
- 후기
프로젝트를 할 때마다 성장하고 있음이 느껴져서 좋다. 이전 프로젝트 까지는 문제 해결 자체에 집중 했다면 이번에는 '기록'에 조금 더 집중해 보았다. 문제가 생겼을 때 그 문제를 기록해두고 어떻게 해결했는지에 대해 자세히 기록해 두었다. 그 기록들을 정리하며 후기를 쓰다보니 왜 이런 후기들이 중요한지 알겠다. 복습도 되고 어쩌면 다시 미래에 했을지 모를 실수들을 방지하게 해준다. 당연한 것인데, 이전까지는 잘 못하고 있었다는 생각이다. 처음부터 그렇게 하지 못해왔던게 아쉽지만 지금이라도 알아서 다행이다.
'Frontend Study - 1 > 프로젝트 후기' 카테고리의 다른 글
Nextron 실시간 채팅 프로젝트 후기 (0) | 2023.02.13 |
---|---|
개인 포트폴리오 프로젝트 후기 (0) | 2023.01.09 |
위코드 2차 프로젝트 후기 : myhoneytrip (myrealtrip) (0) | 2022.08.14 |
위코드 1차 프로젝트 후기 : TURTLE HOME (ZARA HOME) (1) | 2022.07.31 |