오늘은 Nextjs에서 SSR기능을 사용해 보며 토큰관리의 불편함을 나름의 고민 끝에 나온 방법으로 완화했던 방법에 대해 아주 간략히 기록해보려고 한다. 정답은 없다~
때는 Next.js를 쓰면서 SSR을 어떻게 쓰는지 알게 되고 난 후에 본격적으로 이곳저곳에 적용하던 평범한 날이었다.
(백엔드에서 넘겨준 토큰으로 인증처리를 진행하고 있었던 상황)
Next.js 에서 서버사이드로 데이터패칭을 할때 인증이 필요한 API일 경우엔 당연히 토큰을 담아야 했지만
그 이전까지 모든 코드가 토큰을 localStorage에 담아서 관리하고 있던 터라 클라이언트에서 쓰는 토큰을 서버에서 쓰지 못하는 문제가 발견되었다.
이걸 조금 늦게 알게 된 이유는 그전까지 딱히 SSR이 제대로 활용되는 구간이 없었던 것이 아닐까 싶다.
(전부 클라이언트에서만 처리하고있었)
서버사이드에서는 브라우저의 localStorage에 접근할 수가 없기 때문에 당연히 localStorage에 저장한 토큰은 클라이언트 사이드에서만 사용이 가능했고 자연스레 인증이 필요한 구간에서는(거의전부) SSR 기능들을 사용할 수가 없었다.ㅋ
그렇다면 서버사이드에서 패칭을 할 때 토큰은 어디서 가져와야 할지 고민과 검색등을 거쳤다.
middleware.ts 만들어서 인증이 필요한 api가 호출될 때 headers에 토큰을 끼워 넣어줄까?
도 생각했는데 이러면 로그인이 절대 안 풀릴 거 같아서 배제했고 그러던 중
Next.js에서 cookies 라는 함수를 지원하는 걸 알게 됐는데 여기에 담긴 값은 서버 측에서 사용할 수 있다고 한다.
https://nextjs.org/docs/app/api-reference/functions/cookies
그래서 로그인할 때 로컬스토리지에 토큰을 저장하는 로직에 위 쿠키에 토큰도 같이 저장해 주는 내용을 쓱 끼워 넣어주고
써봤는데 잘 되는 것을 볼 수 있었다.
대충 아래와 같은 느낌으로 사용을 해볼까 했었다.
// page.tsx
import Client from "./client";
import { cookies } from "next/headers";
export default async function Page() {
const cookie = cookies();
const token = cookie.get("token")?.value || null;
let headers = {}
if (token != null) {
headers = { Authorization: `Bearer ${token}` }
}
const data = await fetch(URL, { headers: headers }).then(res => res.json())
return <Client data={data} />;
}
그런데 1.cookies를 import 하고 2.token을 선언하고 3.headers를 조건부로 생성해 주는 부분들이
페이지마다 반복될 수밖에 없는 구조였고 상당히 거슬렸다.
(반복되는 부분은 항상 곱게 보이진 않는다)
그래서 서버에서 패칭을 쏘든 클라이언트에서 쏘든 "Authorization : Bearer ${TOKEN}" 부분을
생성해 주는 함수를 만들어 보는 방향으로 반복을 조금이라도 줄여볼 수 있지 않을까
싶은 생각에 아래처럼 getToken() 이라는 함수를 만들어 보았다.
// getToken/getClientToken.ts
export const getClientToken = () => {
return localStorage.getItem("token") || null;
};
// getToken/getServerToken.ts
"use server";
import { cookies } from "next/headers";
export const getServerToken = async () => {
const cookie = cookies();
const token = cookie.get("token")?.value || null;
return Promise.resolve(token);
};
// getToken/index.ts
import { getClientToken } from "./getClientToken";
import { getServerToken } from "./getServerToken";
export const getToken = async () => {
const server = await getServerToken();
if (server) {
return server;
}
return getClientToken();
};
import { getToken } from "./getToken";
export const createHeadersWithToken = async () => {
const token = await getToken();
if (token && token.length > 0) {
return {
Authorization: `Bearer ${token}`,
};
}
return {};
};
굳이 getClientToken과 getServerToken을 나누어준 이유는
"next/headers" 에서 불러오는 cookies는 오직 서버단에서만
사용할 수 있어서 localStorage와 함께 사용자체가 불가하기 때문이었다.
그래서 파일로 쪼개서 따로 불러와 조건부로 생성해 주면 되지 않을까 싶어서
시도해 본 거였는데 다행히 정상적으로 동작은 한다.
덕분에 8줄 이상 반복되던 부분을 아래처럼 꽤 많이 줄일 수 있었지만
더 좋은 방법은 없을까 계속 고민이 되는 부분이긴 하다.
// page.tsx
import Client from "./client";
import { createHeadersWithToken } from "@/core/func/createHeadersWithToken"
export default async function Page() {
const headers = createHeadersWithToken()
const data = await fetch(URL, { headers: headers }).then(res => res.json())
return <Client data={data} />;
}
물론 위 createHeadersWithToken() 함수를 page.tsx의 서버사이드에서
바로 fetch에 매번 호출해서 넣어주는 것도 성에 차지 않아서
fetch 하는 부분을 아예 모듈화 시켜서 사용 중이라 현재는 사용하고 싶은 곳에서는
모듈화 해둔 fetch 함수만 사용하고 있다.
어찌 됐든... 해결은 했지만 나중에 더 좋은 방법으로 개선하는 방법이 떠오르거나
다른 고수님의 좋은 글을 보고 새로운 사실을 알게 된다면 또 업데이트를 해볼 예정이다.
'프로젝트' 카테고리의 다른 글
[Toy Project] 이커머스 개발일지(D+fin?) (1) | 2023.02.12 |
---|---|
[Toy Project] 이커머스 사이트 개발일지(Day1) (0) | 2023.01.19 |
적정수면시간 제공 서비스 "Sleepwell" (0) | 2021.06.05 |
Hobbygram(미니프로젝트) (0) | 2021.04.25 |
velog 클론코딩 (0) | 2021.04.09 |