하루 알바 구인구직 플랫폼 프로젝트(앱라우터, 유저기능)

forestream·2024년 5월 10일
2

프로젝트명: 시너지
사장과 일일 알바 대타가 만나는 플랫폼.

앱 라우터

우리 팀은 기존 강의를 통해서 배웠던 pages 라우터보다는 새로 나온 앱라우터를 써보기로 결정했다.
앱라우터는 리액트 서버컴포넌트를 사용한다. 기존 페이지라우터에서 필요했던 _document.js, _app.js 파일은 필요하지 않고 layout과 page 파일을 사용한다.

로그인, 회원가입 페이지 레이아웃 분리의 문제

메인이나, 가게, 공고별 페이지 등은 헤더와 푸터가 유지되어야 하고
로그인, 회원가입 페이지는 헤더와 푸터가 제외되어야 했다.

조건식에 따른 렌더링 시도

layout

app 라우터 구조를 모르는 상황에서 처음에는 params를 활용하여 레이아웃을 렌더링 하고자 했다.
로그인은 /signin, 회원가입은 /signup 경로로 만들고 루트레이아웃 파일에서 useParams를 사용 시도했지만, 최상위 layout 컴포넌트는 클라이언트 컴포넌트로 사용할 수 없었다.

루트레이아웃에는 metadata 객체가 필요하고, 이 객체는 서버컴포넌트에서만 사용이 가능하기 때문이었다.
metadata 객체를 통하여서 HTML title과 description 등을 설정할 수 있다.

컴포넌트

header와 footer 컴포넌트를 클라이언트 컴포넌트로 만들고 params를 참조해서 signin이나 signup 페이지일 때에는 display: none 을 사용하여 사라지도록 했다.

그러나 여전히 조건식에 따라 헤더와 푸터를 숨겨지도록 하는 것은 불필요한 계산이 이루어지는 것 같았다.

해법: 라우트 그룹 route group

공식문서를 읽다 보니 루트레이아웃은 꼭 하나만 있을 수 있는 게 아니었다.
라우트 그룹은 파일시스템 경로상 (...) 와 같이 소괄호로 묶어서 만들 수 있다.
기존의 경로가

app
	layout.tsx
    page.tsx
    signin
    	layout.tsx
        page.tsx

와 같았다면 라우트 그룹을 사용하면

app
	(main)
    	layout.tsx
        page.tsx
	(auth)
		layout.tsx
    	signin
            page.tsx

와 같이 경로를 나누어서 그룹 최상위에 루트레이아웃을 하나씩 만들어서 프로젝트 안에 여러개가 있을 수 있다.
라우트 그룹의 이름은 URL에는 포함되지 않으므로 위에서 전자나 후자의 파일시스템 모두 //signin 경로를 통하여 페이지에 접속할 수 있다.
이렇게 하면 경로 이름에 따라 렌더링을 달리할 필요 없이 처음부터 다른 레이아웃을 통해 페이지를 보여줄 수 있다.

루트레이아웃은 app이나 라우트그룹 최상위에 하나만 존재할 수 있지만, 루트가 아닌 (<html />, <head />, <body />를 포함하지 않는) layout 파일은 중첩하여 사용할 수도 있다.

에러, 로딩, 404 페이지

넥스트가 인식하는 특수한 파일 이름은 위에서 나온 page, layout 말고도 여러가지가 있다. 이번 프로젝트에서는 error와 not-found, loading 파일을 사용했다. 지금까지의 이 다섯 개 파일은 정해진 위계에 따라 렌더링 된다.
layout => error => loading => not-found => page 순이다.

not-found

app 아래 라우트 그룹이 두 갈래로 나뉜 상황에서 not found 페이지를 어디다 배치할지 확실하지 않았다.
결과적으로는 app 바로 아래에 not-found를 배치시켰다.

app
	layout.tsx
	not-found.tsx
    (main)
    (auth)

main 그룹이나 auth 그룹 아래에 not-found 파일이 속한 경우 렌더링 되지 않는다.
not-found 파일은 app 바로 아래에서만 동작하는 것 같다.
그렇기에 not-found를 렌더링 하기 위한 layout이 또 필요했고, 프로젝트의 루트레이아웃 파일은 총 세 개가 됐다.

loading

로딩 컴포넌트는 페이지 컴포넌트가 로딩될 때 ui를 대체할 컴포넌트이다.
이 컴포넌트로 사이트 곳곳에 있는 공고카드 ui에 스켈레톤이나, 스피너를 각각 적용할 수 있을까 했지만, 방법은 찾지 못했고, 페이지 전체가 로딩될 때 (팀원이 만든) 스피너를 표시하도록 했다.
loading은 같은 위치의 page나 하위의 page를 대체하는 fallback 컴포넌트다.

error

에러 컴포넌트도 로딩과 같이 같은 위치나 하위의 컴포넌트에서 에러가 발생하면 그것을 캐치하여 렌더링한다.
에러 컴포넌트에서 쓸 수 있는 reset 프롭이 있는데 문자 그대로 이 함수를 사용해서 에러가 발생한 리퀘스트를 다시 전송할 수 있다.
또한 에러 컴포넌트는 클라이언트 컴포넌트로만 사용해야 하고 ('use client' 디렉티브를 코드 상단에 명시), 그보다 상위의 레이아웃은 에러의 영향을 받지 않고 렌더링 할 수 있다.

유저기능

사용자는 회원가입시 알바생이나 사장님으로 가입할 수 있다.
알바생이 공고를 클릭하면 공고신청 버튼이 보이고
사장님이 공고를 클릭하면 공고편집하기 버튼이 보여야 한다 (본인의 가게인 경우).

그 밖에도 버튼이나 페이지 접근 권한 등을 유저 종류에 따라 분리하기 위해서 네 가지 데이터를 사용하기로 했다:

  • 액세스토큰, 유저 id, 유저 종류, 가게 id.

인증과 인가를 다루는 첫번째 프로젝트에 처음 써보는 앱라우터라 이 부분에서 스스로도 한동안 고민하고, 팀원들끼리도 아주 많은 토의를 했다.

로컬스토리지

로컬스토리지에 유저 데이터를 저장할 경우 유저 검증은 클라이언트 컴포넌트를 통해서 이루어져야 한다. 이러면 무언가 넥스트를 쓰는 의미가 퇴색되는 느낌. 또 한 번 받아온 액세스토큰이 정해진 기한 없이 브라우저에 남게 된다.

URL 쿼리

URL 쿼리만으로 모든 인가를 처리하면 안 되겠지만 유저 종류는 입력해서 search params를 이용하면 유저를 분류할 수 있을 것이다. 멘토님은 URL 쿼리를 숨길 수 있는 방법이 있다는 것도 알려주셨지만 일단은 팀이 정한 것은 쿠키이다.

쿠키

넥스트를 사용함으로서 이 프로젝트에서 신경써야 할 주체는 앱 프론트엔드, 앱 백엔드, 부트캠프 api 서버 세 개가 된다. 여기서 우리는 cookies() api를 사용하기로 했다.
cookies() api는 넥스트가 제공하는 api로 서버컴포넌트에서 사용 가능하다.

결과적으로 만들어진 로그인 페이지를 보면 먼저

  • 브라우저에서 캠프에서 제공한 api로 리퀘스트가 가고

  • 캠프 api에서 토큰, user id, user type 데이터를 리스폰스로 보내준다. (사장일 경우 가게 id까지)

  • 위 세 개 데이터를 서버 액션을 호출하고 앱 백엔드 측에 리퀘스트를 보낸다.

  • 백엔드에서 함수를 실행하여 cookies api로 위 데이터들을 쿠키값으로 만든다.

    ...
    cookies().set({name: 'accessToken', value: '...'});
    ...
  • 백엔드가 보내는 리스폰스에 생성된 쿠키가 포함되어서 보내지고, 향후 리퀘스트마다 쿠키값이 전송된다.

서버 액션

서버 컴포넌트나 클라이언트 컴포넌트에서 비동기적으로 서버의 동작을 유발시키는 함수이다.
공식 문서에서 읽은 바 기억나는 부분은 <form action={serverAction} >... 과 같이 폼 태그에 action 어트리뷰트를 통해서 실행할 수도 있고 버튼 이벤트 핸들러로서도 호출할 수 있다.

서버 컴포넌트에서 사용한다면 서버액션으로 활용하고자 하는 함수를 정의할 때 함수 몸체 가장 첫번째 줄에 'use server' 디렉티브를 적어야 한다.

클라이언트 컴포넌트에서 사용하려면 클라이언트 컴포넌트 상단에 이미 'use client'가 있으므로 해당 컴포넌트 내부에서는 정의하지 못하고 외부에서 정의 후 import하여 사용할 수 있다. 외부에서 파일 자체를 서버 액션으로 쓰고자 한다면 코드 가장 첫 줄에 'use server'를 명시하면 된다.

0개의 댓글