약 두달의 기간동안 NewTips라는 Project를 진행했다.
NewTips란??
- 소비자, 판매자의 네일예약 과정에서 불편함을 해소해주기 위해 만든 서비스로
NextJs 와 TypeScript로 진행한 Project이다.
마감기한까지 성공적인 결과물을 얻어내지 못했다고 이전회고에서 작성한 것처럼
이번 프로젝트에는 올바르지 못한 개발 방향에 따른 영향으로 다양한 문제점을 겪게됐다.
이번 회고에서는 코드적인 문제점에 대해 작성해보려고 하는데
내가 프로젝트에서 맡았던 부분들은 하나의 큰 문제점에서 부터 시작했다.
문제의 원인은 무엇일까...
- 우리팀은 설계적인 부족함에 코드를 작성하는 과정에서 시간에 쫓기게 됐다.
NextJs를 사용한 project임에도 불구하고 Framework에 대한 충분한 고민 없이 React에서 하던 작업방식을 도입해서 코드를 작성하기 바빳다.- SSR 방식과 CSR방식을 충분히 고민해보지 않고 모든 작업을 클라이언트 측에서 동작하도록 코드를 작성하게 됐는데. 여기서 부터 문제가 발생했다.
NewTips에서 내가 맡아서 작업한 부분들은 다음과 같다.
- api요청의 자동화를 위한 axios-instnace설정
- tanstack-qeury를 ssr - csr 두 환경에서 모두적용하기 위한 설정
- 사용자 권한에 따른 접근권한 설정
기본적으로 팀원들이 프로젝트를 진행하면서 작성해야할 기본 설정에 대한 작업들이 주된 task였다.
설정관련 setting을 작업하며 팀원들이 내가 작업한 코드를 최대한 간편하게 사용할 수 있도록 코드를 작성하는 것이 목표였는데 SSR 과 CSR 두 환경에서 문제없이 사용하도록 동작하는 과정에서 문제점을 겪게된 부분들을 얘기해보려 한다.
문제점을 해결하는 과정에서 나는 3단계를 거쳐서 문제를 해결했다.
- 원인분석 : 어디서 문제점이 발생하는지
- 목표 설정 : 여러가지 해결 방식 중 어떤 방식이 프로젝트에서 최선일지
- 해결 : 해결 목표에 맞게 코드를 작성해서 문제해결
이렇게 3단계를 거쳐서 문제를 해결했는데 프로젝트를 진행하며 겪게된 주된 문제점 세가지를 위와같은 해결방식에 맞게 해결했던 경험을 작성해 보려 한다.
NextJs에서 fetch를 사용해도 되는데 axios를 사용할 필요가 있었나...
지금와서 생각해보면 굳이라는 생각이 들긴하지만 다음과 같은 이유로 axios를 사용했다.
- axios-instance설정으로 baseUrl설정 api요청 함수를 편리하게 작성
- axios-interceptor 사용으로 요청 전,후의 일관된 작업을 다루는데 편리함
다음과 같은 이유로 axios를 도입, instance설정을 완료한 후 프로젝트를 진행하던 과정에서 문제상황을 맞이하게 되었다.
- 이미 api호출 함수에서 axiosInstance() 를 사용한 클라이언트 패칭 작업이 모두 완료된 상황
- 서버 패칭을 위해서 api호출을 해야하는데 기존 함수들은 클라이언트 측에서만 동작하기 때문에 문제가 발생했다.
// 기존 axios-instance코드 (쿠키를 가져오는 상황에서 문제가 발생)
export const axiosInstance = () => {
let instance = axios.create(instanceConfig)
instance = setRequestInterceptor(instance, contentType)
instance = setResponseInterceptor(instance)
return instance
}
const setRequestInterceptor = () => {
instance.interceptors.request.use(
(config) => {
const accessToken = getCookie(ACCESS_TOKEN)
// 쿠키를 가저오는 getCookie함수는 클라이언트 측에서만 동작을 하는 상황
if (accessToken && config.headers) {
config.headers.Authorization =`Bearer$ {accessToken}`
}
api요청을 보내기 위해서는 요청 header에 access_token 정보를 포함시켜서 보내야하는데
현재 getCookie함수는 클라이언트 측에서만 동작하는 코드이기에 문제가 발생했다.
어떤 코드가 문제의 원인이고 이것을 해결한다면 문제 또한 해결 가능하다는 것은 인식했다.
getCookie함수가 클라이언트 측에서만 동작하니 서버 환경에서도 동작하도록 코드를 수정해준다면 문제가 해결될 것이다.
가장 간단한 해결책으로 처음 생각한 방식은 두개의 axiosInstance를 생성하는 방법이다.
const axiosInstanceServer = ()=>{ 서버 패칭시 사용 }
const axiosInstanceClient = ()=>{ 클라이언트 패칭시 사용}
위와 같이 2개의 instance를 생성한다면 문제는 당연히 해결될 것이다.
정말 최선인가?
위와같은 방식으로 해결하는 것이 정말 최선인가? 라고 생각했을때 별로라는 판단을 했다.
설정작업의 담당자로서 팀원들에게 "코드의 사용법을 숙지시켜야 한다" 라는 방식은 좋은 방식이 아니라고 생각을 햇다.
설정파일의 담장으로서 작업을하면서 목표로 한 부분은 내가 작업한 코드를 팀원들이 가져다 쓰기만 해도 자동으로 동작시키도록 하는것이 목표이기에 더 좋은 방식을 고민해보고 팀원들과 이와 관련한 회의도 열어서 최적의 방법을 고민해봤다.
axios문제의 해결 목표
내가 최종적으로 완성하고 싶은 코드의 형태는 다음과 같았다.
이미 api호출 함수들에 axiosInstance()라는 코드가 포함되어 있는 상태이다.
따라서. 기존코드의 수정 없이 axiosInstace설정 내부에서 모든 것을 해결하는 것이다.
getCookie
함수는 cookies-next
라이브러리를 사용하고 있었다.
해당 라이브러리를 사용한 이유는 서버환경 & 클라이언트 환경 모두다 getCookie가 가능했기 때문에 사용하게 되었는데
// getCookie() 서버와 클라이언트 환경에서 각각 사용하는 방법
const client_cookie = getCookie("key")
// 인자로 키값을 넣어준다
const server_cookie = getCookie("key",{cookies})
// 키값 + next/headers의 cookies를 두번째 인자로 추가
이를 활용한 axiosInstance 의 코드는 다음과 같다
- typeof window === "undefiend"를 활용해 코드가 실행되는 환경을 check
- getServerCookie() , getClientCookie() 함수를 정의 해서 cookie를 가져옴
- 가저온 access_token을 요청 header에 추가
const setRequestInterceptor = () => {
instance.interceptors.request.use(
async (config) => {
const accessToken = await getAccessToken() // 실행환경에 맞는 accessToken 저장
const validAccessToken = await getValidAccessToken(accessToken) // 토큰 만료여부에 따라서 유효한 토큰 반환
if (validAccessToken && config.headers) {
config.headers.Authorization = `Bearer ${validAccessToken}`
완성된 코드는 어려운 코드가 아니지만 이를 해결하기 위해 여러가지 코드를 고민해보고,
팀원들과 회의를 통해 현재 어떤 문제가 있는데 이를 해결하기 위해 여러가지 해결방식중
최선의 방법이 무엇인지 논의해보는 과정에서 목표한 코드를 완성할 수 있었다.
react-query를 사용하면 api호출과 전역상태 관리를 동시에 사용하는 장점이 있다.
이전 react 프로젝트에서도 자주 사용해왔는데
next-js에서도 사용해도 좋을것 같다는 판단에 서버에서 받아온 data를 tanstack-qeury 라이브러리를 통해 관리했다.
굉장히 편리한 장점이 있는 tanstack-qeury에서도 우리 프로젝트와 맞지 않는 부분이 있었는데 서버패칭을 계획하면서 문제점이 발생했다.
서버 패칭과정에서는 tanstack-query를 사용하지 못한다..!!
우리 팀은 이미 api패칭과정에서 많은 부분을 useQeury 또는 useMutation을 통해 관리하고 있었는데 서버패칭에서는 이를 적용하기 힘든 상황을 맞이했다.
해결 목표
이번 해결 목표도 이전과 동일했다.
- 이미 기존 코드가 작성되어 있는 상황. 기존 코드 수정없이 동작에 문제없도록 코드를 작성하는 방향
- react-qeury를 서버패칭에도 활용하는 방향
이를 해결하기 위해 몇가지 해결방식을 생각 해 볼 수 있엇는데
- 서버패칭이 필요하다면? => tanstack-qeury사용 없이 api호출후 결과를 사용
- initialData props를 활용해서 초기 데이터를 패칭후 클라이언트 컴포넌트에게 전달
두 방식모두 해결은 가능한 코드이지만 최선이라는 생각은 들지 않았다.
- 팀원들에게 서버패칭시 주의 사항을 알려줘야함
- props로 data를 넘겨주는 과정에 props drilling 이 일어날 수 있음
- axios를 사용하기 때문에 api를 서버1회 클라이언트1회 2회를 호출해야 해서 비효율 적이다.
위와같은 문제점 때문에 다른 해결 방식을 고민해보던 중
HydrationBoundary라는 것을 알게 되었고 이를 적용하기로 했다.
HydrationBoundary를 사용하면 prefetchQuery를 통해 서버패칭을 진행하고
클라이언트 컴포넌트에서 동일한 queryKey, queryFn을호출 한다면 캐싱된 데이터를 불러와서 api를 재호출 하는 일 또한 막을 수 있엇다.
팀원들과 공유하기
처음 적용해보는 내용이라 팀원들과 해당 내용을 공유 하기 위해서
사용법과 함께 연습과 해당 내용에 개념들을 담은 연습공간을 제공해서 팀원들과 공유했다.
이를 통해서 SEO최적화를 위한 server-fetching의 설정을 완료할 수 있었고 SSR CSR환경 둘다 코드 작성가능 하도록 했다.
NewTips프로젝트는 사용자 권한에 따른 분기처리가 3가지 존재한다.
- 비로그인 사용자
- 로그인한 소비자
- 로그인한 판매자
3가지 경우의 수가 존재하니 페이지별로 권한을 부여해야하는데...
react를 활용한 프로젝트라면 router설정을 통해서 작업해왔는데 NextJs에서는 react-router-dom 처럼 한 파일에서 설정하는 방식을 경험해 본적이 없었다.
첫 해결 방식
각 page.tsx
파일에서 인증여부를 check후 접근 불가 페이지라면? 특정경로로 redirect시키는 방식을 떠올렸다.
위와같은 해결 방식 또한 앞에서 해결했던 해결방식과 일치하지 않았다.
"각 페이지 작업자들에게 페이지 컴포넌트를 작업할땐 경로설정을 해주세요~"
위와같이 요청을 해야하는데 효율적인 방식은 아니라는 판단을 했다.
더 좋은 방식
한 파일에서 모든 권한 설정관련 코드를 관리하면 편하지 않을까?
이와 관련된 내용를 찾아보던 중 역시 프레임 워크답게 이를 도와주는 파일이 존재한다는 것을 알게 되었다.
middleware.ts
라는 파일의 존재와 역할을 알게되었다.
이또한 처음 작업해보는 내용이라 팀원들과 회의시간에 내용을 공유하고 설정 방식에대한 주석으로 이해를 돕도록 작성했다.
프로젝트를 진행하며 다양한 문제상황들을 맞이했는데 코드에는 정답이 없기에 동작만을 우선시한다면 어떤 방식이든 코드를 작성해도 문제없을 것이다.
협업이기에 여러가지 방식을 고민해보고, 또한 팀원들과 공유하기 가장 좋은 방법을 고민해보는 과정에서 다양한 지식을 얻을 수 있엇고 스스로 해결하지 못한 문제를 회의를 통해 풀어나가는 과정에서 더 좋은 결과를 얻을 수 있었던것 같다.