🤔NextJS 쿠키?
브라우저에 저장되는 일반적인 쿠키와 다를게 없지만
NextJS는 SSR을 지원한다는 점에서
cookie를 서버에서 확인할 수 있는 방법이
제한적이기 때문에 CSR(React 등)만 활용하여
개발하던 개발자들은 생각보다 더 난해한 문제를
겪게 될 수 있다.
그런 개발자 중 한명이 필자였고 특히
쿠키를 활용한 JWT 로그인 유지를 구현할 때
interceptor를 통해 매번 토큰을 header에 넣어 요청하고
accessToken이 만료된 경우에는 refreshToken을
서버에 재발급요청하여 재생성된 토큰들을 쿠키에 저장하고
저장된 쿠키를 통해서 이전 요청을 재요청하는 프로세스에서
accessToken 쿠키가 undefined인 상태로 요청이 되는 이슈로
꽤나 험난한 삽질과 많은 시간을 고민하게 되었다.
결과적으로 NextJS를 제대로 다루기 위해서는
hydration,
middleware,
server - component,
server - actions / mutations,
위 4가지의 작동방식은 필수적으로 학습을 통해
Next가 작동되는 방식에 대해 이해하고 있어야한다는
생각이 들었다.
🫗hydration
(hydration에 대한 글은 개인적 주관이 강한 글이기 때문에 넘어가셔도 좋습니다😊)
필자는 단순히 하이드레이션을 주입식 교육처럼
하이드레이션 = 서버의 결과를 클라이언트로 합치는 것
이라고만 생각했지만 결과적으로 이해하게 된 계기는
여러 이슈를 겪으며 어원에 대해 다시 생각하는 것으로
이해도를 갖추게 된 것 같다.
hydration이란 수분공급 이라는 어원을 가지고 있다.
그렇다면 어째서 수분공급이지? 라는 의구심을 가지고
좀 더 단순하게 어원에 대해 생각했을 때 식물이 뿌리에서
흡수한 물을 줄기를 통해서 수분공급을 하는 방식을 떠올려 봤다.
뿌리 = Server
(땅속에 있어 우리가 표면적으론 볼 수 없지만 핵심이 되는 뿌리)
줄기, 잎, 꽃 등 = Client
(땅을 파지 않아도 겉으로 볼 수 있는 식물의 외형)
식물은 수분공급이 제대로 이루어지지 않는다면
그 식물은 시들어버리거나 제대로 성장하지 못한다.
그렇기 때문에 Server에서 보여주고자 하는 것을
Client에 공급해주어 제대로된 결과물을 보여주는 것이
Hydration이구나! 라는 결론을 내리게 되었다.
물론 그저 나의 상상력으로 여기까지 온 것일 수도 있다...
하지만 좀 더 어원에 대해 이해하려할 수록
이해도가 높아지는 경우가 생각보다 많았던 것 같다.
만약 영어가 아닌 한글이었다면 조금 더 직관적으로
프로세스를 이해했을 수도 있지만 프로그래밍 언어가
기본적으로 영어로 되어있기 때문에 우리가 프로그래밍에
대해서 이해하기 어렵게하는 하나의 벽이 되고있지 않나...
하는 생각이 들곤 한다.
🔗middleware
middleware는 server에서 client에 렌더링되기 이전에
거치게 되는 곳으로 서버와 클라이언트의 교집합 지점이라고
볼 수도 있는 영역이다. 때문에 NextJS를 다룬다면 정말 중요한
기능이기 때문에 꼭 알아야된다고 생각하는 것 중 하나다.
NextJS를 활용해서 사용자에게 보여주기전에 server에서
내부적으로 redirect하거나 필수적인 쿠키를 미리 설정
가능하고 국제화를 하는 경우에는 페이지에 접속한
국가에 적합한 언어로 구성된 페이지를 제공하기 위해서
활용하기도 한다.
middleware 활용처는 너무 많기 때문에 예제코드는
필자의 깃허브 링크로 남겨두겠습니다.
* nextjs14 app-router 국제화를 적용한 템플릿을 예제로 넣었습니다.
Github: https://github.com/zeriong/next14-app-router-i18n-template/blob/master/middleware.ts
⚙️server-component
NextJS에서 컴포넌트를 구성할 때 최상단에 "use client"를
작성하지 않는다면 기본적으로 server-component로 작성된다.
그리고 "use client"를 작성한다면 react에서 사용되는 CSR관련
Hooks를 활용할 수 있게 된다.
단 여기서 중요한 것은 "use client"라고 해서 SEO가 안되는 것은
아니라는 점이다.
html 구성은 server에서 렌더링이 되기 때문에 SEO는
영향을 끼치지 않는다. 단지 CSR에서 활용 가능한
React의 Hooks를 활용해서 CSR에서 작동되는 JS를 활용하겠다.
라고 선언하고 활용하는 것에 불과하다.
그러니 "use clinet"를 썼다고 해서 SEO가 안될 것이라는
걱정은 하지 않아도 될 것이다.
(useEffect를 활용하여 state 변경후에 생성되는 컴포넌트 제외)
🪄server - actions / mutations
서버에서 실행되는 비동기함수를 뜻한다.
form에서 활용하는 action이나 기타 fetch를 활용한
api요청 등을 server에서 작동하게 만드는 것이다.
활용방식은 아주 간단하게 해당 함수의 환경 최상단에
"use server" 를 선언하게 되면 이는 server에서 작동하는
함수로 정의된다.
"use server"를 정의하는 환경은 주로 파일단위나
함수단위로도 활용 가능하다.
상황에 따라서 우리는 다양하게 활용하지만 주로
하나의 파일에서 "use server"를 최상단에
정의하여 모듈화하여 활용하는 경우가 보편적이다.
예시적으로 actions.ts 파일을 생성한다고
가정했을 때의 예제 코드이다.
- server-actions 예제코드
// actions.ts
"use server";
/**@desc login server-action */
export async function loginAction(prevState: { success?: boolean; msg?: string }, formData: FormData) {
const userId = String(formData.get("userId")).trim() as string;
const userPassword = String(formData.get("userPassword")).trim() as string;
if (!userId.length || !userPassword.length) return { success: false, msg: "아이디 또는 비밀번호를 확인해주세요" };
const reqBody = { userId, userPassword };
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL_AUTH}/auth/login`, {
method: "POST",
body: JSON.stringify(reqBody),
});
const resBody = await res.json();
if (resBody.code != 200) return { success: false, msg: resBody?.message };
return { success: true, msg: "success" };
}
위처럼 server-action을 구성하고 해당 server-action을 로그인을 하는 컴포넌트에서 useFormState 훅을 활용해서 state로 response 결과값을 client에서 동적으로 확인할 수 있다.
- server-mutations 예제코드
// mutations.ts
"use server";
import { getUserInfo } from "@/apis/user";
/** get user data */
export async function getUserData() {
const res = await getUserInfo();
return res.body.data;
}
정말 단순하게 "use server"를 명시해주어 server에서 해당 api로 fetch를 통해 response 값을 받아볼 수 있다.
⭐server - actions / mutations 장점
- network 요청 수를 줄일 수 있다
- server 내부에서 미리 값을 fetch 받아 렌더링하기 때문에 SEO 향상
- API route 없이 서버 측에서 직접 DB에 접근가능
- 민감한 데이터를 클라이언트 측에 노출시키지 않을 수 있음
- client side 번들 사이즈가 줄어 초기 로딩 속도 개선
🍪NextJS 쿠키 트러블 슈팅
내가 맞닥뜨린 이슈는 결국 NextJS에서 쿠키에 대한
hydration이 필요한 상황이었고 가장 첫번 째로 고민한 것은
가능한 것에 대한 고찰이었다.
서버에서 쿠키에 접근가능한 방식을 생각해봤을 때
next header에 접근할 수 있어야하는 것이
우선 선행이 되어야 했다.
그렇다면 어떠한 경우에 server에서 쿠키에 접근할 수 있는가?
내가 떠올릴 수 있는건 두가지였다.
1. middleware의 NextRequest를 통한 접근
2. server-component에서 next-header의 cookies()로 접근
1번의 경우는 현실성이 부족했고 2번으로 접근하기로 했다.
하지만 interceptor를 구성할 때 따로 api를 요청할 때 마다
지속적으로 재사용해야 하기 때문에 파일로 분기하여
구성할 수 밖에 없었다.
단순히 fetch에 interceptor를 구성하여 붙이는 코드로 구성되어있어
최상단에 "use server"를 명시하는 것도 말이 안되고,
그렇다고 interceptor를 server-component에서 구성하는 것도
말이 안된다고 생각이 들었다.
그때 마침 생각난 것이 바로 server-component가
next-header에 접근할 수 있는 이유에 대해서 생각해봤다.
nextjs14에서는 컴포넌트 구성 시 최상단에
"use client"를 명시하지 않으면 서버컴포넌트로 정의된다.
이는 즉 이전 버전에서 사용하던 "use server"가 생략됐을 뿐이고
server - actions / mutations에서 최상단에 "use server"를
명시하고 활용하는 것과 동일하다고 생각이 들었다.
그렇다면 방법은 하나 뿐이다.
utils에 server 폴더를 만들고 최상단에 "use server"를 명시한
cookies.ts파일을 만들어 내부에 유틸을 구성하는 방법이다.
getServerSideCookie,
setServerSideCookie,
removeServerSideCookie,
위 세가지 쿠키관련 유틸을 구성하고
next-header에 접근할 수 있는 환경에서
쿠키를 컨트롤할 수 있도록 만들어주었다.
결과는 성공적이었고,
모든 server - actions / mutations와
클라이언트에서의 fetch 요청으로도 성공적인
로그인 유지가 가능한 결과를 얻을 수 있었다.
✍️마치며...
개발에 대한 경험이 늘어남에 따라 느끼는 것은 개발은 언제나 정답이 정해져있는 것이고 그 정답에 도달하기 위해선 내가 맞닥뜨린 문제에서 성공적인 예시를 통해 하나씩 이어가는 것이 가장 빠르고 올바르게 이해하며 정확하게 문제를 해결할 수 있는 방법인 것 같다.
'Frontend > Next.js' 카테고리의 다른 글
[ Next.js 13 / page router ] getServerSideProps 통신데이터 SSR에 적용시키기 (0) | 2023.07.09 |
---|---|
[ Next.js 13 / page router ] 주요기능 (0) | 2023.01.14 |
[ Next.js ] React.js의 framework (0) | 2023.01.02 |