로그인을 하고 인증 토큰이 필요한 API 요청에서 오류가 계속 나는 걸 볼 수 있습니다.
로그인 후 토큰은 저희가 의도한 대로 로컬 스토리지에 잘 저장되어 있었지만 계속 오류가 발생하였고, 새로고침을 해야 오류가 발생하지 않고 서버와 통신할 수 있었습니다.
메인 페이지에서 프리패칭하여 프로젝트 리스트 데이터를 가져오고 있는데 isLiked 속성이 계속 false로 오는 문제가 발생하였습니다.
그래서 메인 페이지에서는 자신이 누른 게시물에 좋아요를 확인할 수 없는 문제가 있었습니다.
import { HEADER } from "../_constants/HeaderToken";
getCurrentUserId: async () => {
return await httpClient().get<CurrentUserIdType>("/profile", { "": "" }, HEADER.headers);
},
import { getToken } from "../_utils/handleToken";
export const HEADER = {
headers: {
Authorization: "Bearer " + getToken()?.accessToken,
},
multipartHeaders: {
Authorization: "Bearer " + getToken()?.accessToken,
"Content-Type": "multipart/form-data",
},
applicationHeaders: {
Authorization: "Bearer " + getToken()?.accessToken,
"Content-Type": "application/json",
},
};
첫 번째 문제부터 분석을 해보면 위와 같은 코드에서 getToken
함수를 이용해 로컬 스토리지에서 토큰을 가져와 HEADER
객체를 만든 후 API 로직에 import로 가져와 사용하고 있다.
이렇게 보면 문제가 없어 보여서 문제를 찾는데 좀 까다로웠다.
이 상황에서 getToken
함수가 로그인 후에 토큰을 제대로 가져오지 못한다.
이유는 HEADER
객체가 정의될 때 이미 getToken
함수가 실행되기 때문입니다. 이 경우, HEADER 객체가 처음 정의될 때는 로컬 스토리지에 토큰이 없을 수 있으며, 이후에 로그인 후에 토큰이 추가되어도 초기 HEADER 객체는 갱신되지 않습니다.
두번 째 문제도 위와 같은 문제로 일어난 줄 알고 이것만 해결하면 둘 다 해결될 줄 알았지만 그건 내 착각이었다.
isLiked = 자신이 게시물에 좋아요을 눌렀는지 확인하는 속성
스웨거로 테스트를 해본 결과 프로젝트 리스트 데이터를 요청할 때 헤더에 토큰이 없으면 isLiked 속성 값은 무조건 다 false
로 오고, 토큰을 포함해서 요청을 하면 isLiked true
로 잘 온다.
export const projectApi = {
getProjectList: async ({
page = 1,
size = 12,
limit = 0,
searchString = "",
projectTechStacks = [],
sortCondition = "RECENT",
}: ProjectListParams) => {
return await httpClient().get<ProjectResponseType>(
"/projects",
{
sortCondition,
projectTechStacks,
searchString,
page,
size,
limit,
},
HEADER.applicationHeaders,
["pojectList"]
);
},
두 번째 문제도 비슷하게 HEADER
객체를 전달하여 헤더에 인증 토큰을 담아 서버에 데이터 요청을 하는 방식으로 구성되어 있습니다.
앞서 말한 첫 번째 문제로 로그인 직후에는 토큰이 포함되지 않는 오류가 있기 때문에 isLiked false
로 오는 게 이해가 되었지만 새로고침 후에도 isLiked는 계속 false
값으로 돌아왔다.
async function MainPage() {
revalidateTagAction("pojectList");
const queryClient = getQueryClient();
const projectListQuery = projectQueryKeys.list({ page: 1, size: 16 });
await queryClient.prefetchInfiniteQuery({
queryKey: projectListQuery.queryKey,
queryFn: projectListQuery.queryFn,
initialPageParam: 1 as never,
getNextPageParam: (lastPage: any) => {
const { customPageable } = lastPage;
if (customPageable.hasNext) {
return customPageable.page + 1; // 다음 페이지 번호 반환
}
return undefined; // 더 이상 페이지가 없으면 undefined 반환
},
});
const dehydratedState = dehydrate(queryClient);
return (
<HydrationBoundary state={dehydratedState}>
<main className="mx-auto my-16 grid w-[1200px] grid-cols-[230px_minmax(976px,_1fr)] grid-rows-[100px_minmax(800px,_1fr)]">
<SelectStack />
</main>
</HydrationBoundary>
);
}
문제의 원인은 프리패칭을 하는 과정에서 발생했던 것이었다. 위 코드처럼 프리패칭을 하게 되면 서버에서 데이터 패칭을 하게 된다.
서버에서 데이터 패칭이 실행되기 때문에 클라이언트에 있는 코컬스토리지에는 접근이 불가능했던 것이다.
그럼 프리패칭을 안하면 되는거 아닌가? 라고 생각할 수 있습니다.
프리패칭을 안 하고 클라이언트에서 데이터 요청을 하게 되면 사진을 보면 알 수 있듯이 LCP 지표가 3초로 측정되는 것을 볼 수 있을 것이다.
앞으로 메인 페이지에서는 더 많은 사진 데이터를 가져와 화면에 보여줄 것이기 때문에 적어도 프로젝트 리스트를 서버에서 가져오는 요청은 프리패칭이 되야한다고 생각했습니다.
getCurrentUserId: async () => {
const HEADER = getHeaders();
return await httpClient().get<CurrentUserIdType>("/profile", {}, HEADER.headers);
},
import { getToken } from "../_utils/handleToken";
export const getHeaders = () => {
const token = getToken()?.accessToken;
return {
headers: {
Authorization: "Bearer " + token,
},
multipartHeaders: {
Authorization: "Bearer " + token,
"Content-Type": "multipart/form-data",
},
applicationHeaders: {
Authorization: "Bearer " + token,
"Content-Type": "application/json",
},
};
};
첫 번째 문제는 데이터 요청마다 getHeaders
함수를 호출하여 로컬 스토리지에 있는 토큰을 가져올 수 있도록 로직을 개선해 보았습니다.
export const projectApi = {
getProjectList: async (
{
...
}: ProjectListParams,
token?: string
) => {
const applicationHeaders = {
Authorization: "Bearer " + token,
"Content-Type": "application/json",
};
return await httpClient().get<ProjectResponseType>(
"/projects",
{
sortCondition,
projectTechStacks,
searchString,
page,
size,
limit,
},
applicationHeaders,
["pojectList"]
);
},
async function MainPage() {
const queryClient = getQueryClient();
const cookieStore = cookies();
const ACCESS_TOKEN = cookieStore.get("ACCESS_TOKEN");
const projectListQuery = projectQueryKeys.list({ page: 1, size: 16 }, ACCESS_TOKEN?.value);
await queryClient.prefetchInfiniteQuery({
queryKey: projectListQuery.queryKey,
queryFn: projectListQuery.queryFn,
initialPageParam: 1 as never,
getNextPageParam: (lastPage: any) => {
const { customPageable } = lastPage;
if (customPageable.hasNext) {
return customPageable.page + 1; // 다음 페이지 번호 반환
}
return undefined; // 더 이상 페이지가 없으면 undefined 반환
},
});
const dehydratedState = dehydrate(queryClient);
return (
<HydrationBoundary state={dehydratedState}>
...
</HydrationBoundary>
);
}
export default MainPage;
두 번째 문제는 로그인 후 인증 토큰을 쿠키에 저장할 수 있도록 로직을 변경시키고, 메인 페이지에서 쿠키에 접근하여 저장된 토큰을 가져와 프로젝트 리스트 API 로직에 매개변수로 전달하여 통신이 이루어지도록 로직을 개선해 보았습니다.
알아보니깐 쿠키는 서버와 클라이언트 모두가 접근이 가능하기 때문에 위와 같은 방법으로 문제를 해결해 보았습니다.