fetch를 next js에서 사용한 이유
nextjs의 fetch기능은 기존의 fetch api와 다른 기능을 가지고 있다.
server측 요청마다 지속적인 caching sementic을 설정해준다.
브라우저에서 cache옵션은 어떻게 fetch 요청이 browser의 HTTP cache와 상호작용할 것인지를 가리킨다.
1) cache option
- force-cache
기본값이며 데이터 캐시에서 일치하는 요청을 찾고, 요청이 신선하면 캐시에서 반환된다.
일치하지 않거나 신선하지 않은 경우 서버에서 리소스를 받아와서 캐시를 업데이트 한다.
- no-store
캐시를 확인하지 않고 매번 서버에서 리소스를 받아온다.
해당 리로스도 캐시를 업데이트 하지 않는다.
2) next.revalidate option
캐시 유지기간을 초단위로 설정할 수 있는 옵션
- false
리소스를 무기한 캐시한다.
- 0
리소스를 캐시하지 않는다.
- 숫자
n초동안 캐시 한다.
3) next.tags option
리소스의 캐시 태그를 설정하는 옵션
데이터는 revalidateTag에 의해 요구에 따라 재검증할 수 있다.
태그는 최대 256자, 태그항목은 64개까지 설정가능하다.
직접 구현한 createCustomFetch의 기능
axios를 사용하는 주 목적 중의 하나가
다양한 api요청에 대해 base url과 설정을 통해 instance를 사용할 수 있는 것이었다.
나의 프로젝트의 경우에도 다양한 api요청이 필요하였기 때문에
요청 할 때마다 설정을 바꾸거나 base url을 일일이 명시해주는 것보다는
createCustomFetch라는 HOF형태를 활용하여
baseURL 및 headers를 설정해주어 custom fetch 함수를 구현하였다.
export const api = createCustomFetch({
baseURL: '/api',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.API_TOKEN as string}`,
},
});
axios interceptor 기능 createCustom에서 사용해보기
axios의 큰 이점중 또다른 하나인 인터셉터 기능이다.
이전의 프로젝트에서는 axios의 인터셉터 기능으로 error 및 error 문구를 캐치하고,
해당 에러를 그냥 보내는 것이 아니라 통일하여 보내서
특정 컴포넌트에서는 토스트로(화면이 중지되지 않도록),
또다른 컴포넌트에서는 에러바운더리로 처리를 했었다.
api.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const { status, data } = err?.response;
if (err.response && status === 400) {
if (data.message[0] === '이메일 형식이 올바르지 않습니다.') {
return data.message[0];
}
}
if (err.response && status === 401) {
if (
data.message ===
'등록되지 않은 이메일 이거나, 유효하지 않은 비밀번호입니다.'
) {
return data.message;
}
}
그런데 지금의 경우 axios에서는 이미 처리가 된 json객체화를 하는 곳에서 문제가 생겼다.
구현중 만난 문제
응답을 인터셉터하는 것까지는 가능했다.
createCustomFetch를 활용하면 try catch문으로 error를 캐치하는 것은 가능하기 때문.
그러나
error코드만으로는 다양한 에러에 대응할 수 없다.
error의 message를 받아야 하는데, 이때 문제가 생긴다.
'/welcome/login'이거나 /signup일땐 toast로 에러를 표시해주고 싶다.
현재 위치가 /welcome/login이 아닌경우엔
응답코드가 400 이거나 401 일때는 '/welcome/login'으로 보내고 있다.
'/welcome/login'이거나 '/welcome/signup'을 구분하는 목적은
toast를 띄울 상황인지 아닌지를 판가름 하는 것이므로
두가지 방법이 있는 것같다
(1) 현재의 위치를 확인하든지,
(2) 에러응답 메시지로 구분하든지
- 현재의 위치를 확인하는 방법은
usePathname()을 사용하는 것이 있을 수 있겠으나,
해당 훅은 컴포넌트 내에서만 사용이 가능하므로 현재로서는 불가능하다.
- 에러응답 메시지로 구분하려면
response.json()을 호출하여 응답 객체를 json 형식으로 변환한 후
response.json을 호출하면 custom api를 활용하는 컴포넌트에서는 json한 데이터를 사용하지 못하는 것 같아 보였다
그러나 그건 코드 안에서의 작은 에러의 이유였다.
수정후 재 실행 해보니 정상적으로 작동하였다.
try {
const response = await fetch(baseFullUrl, {
...options,
headers: customHeaders,
});
if (options?.dynamicValues) {
console.log('요청 성공');
}
const resData = await response.json();
if (
(response.status === 400 || response.status === 401) &&
resData.message !== '이메일 혹은 비밀번호가 일치하지 않습니다!'
) {
window.location.href = '/welcome/login';
}
return resData;
} catch (err) {
throw new Error('new Error');
}
};
어려웠던 점
이전에는 createCustomFetch의 returnType이 Promise<Response>타입으로,
해당 타입은 컴포넌트에서 받아올 때만 명시하면 되었었다.
이전의 코드 Promise<Response>일 때의 Type
const createCustomFetch = ({ baseURL, headers }: CustomFetchType) => {
return async (url = '', options?: ExtendedRequestInit): Promise<Response> => {
커스텀 api를 사용하는 컴포넌트에서 명시한 코드
const { data: foodData, fetchNextPage } = useInfiniteQuery<
FoodReturnType,
unknown,
InfiniteData<FoodReturnType>,
[_1: string, _2: string, _3: string, _4: string],
unknown
>({
queryKey: ['foods', storage, sort, direction],
queryFn: ({ pageParam }) =>
getFoods({
storage,
sort,
direction,
pageParam,
}),
initialPageParam: 1,
getNextPageParam: (data) => {
return data.nextCursor;
},
staleTime: 10 * 60 * 1000,
});
그러나 const response = await res.json()으로,
변환한 것을 return하므로 이제 Promise<Response> 타입이 아닌
특정 ReturnType이어야 했다.
그렇다면 사용할 때 명시할 수 없고
사용때마다 각각 달라지므로
any 혹은 제네릭으로 설정해야만 했다.
그런데 이미 api라는 변수를 선언해
createCustomFetch의 함수에 baseURL, headers 등의 옵션을 넣어주므로,
이때 제네릭에 타입을 넣어주어야 했고,
거기서도 any로밖에 넣어줄 수 없었다.
실제로 활용되는 것은 컴포넌트이므로.
createCustomFetch.ts파일의 일부
api 변수를 선언한 모습
export const api = createCustomFetch({
baseURL: '/api',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.API_TOKEN as string}`,
},
});
실제 api를 사용할 때 동적으로 타입을 넣어줄 수는 없는걸까?
그러려면 조금 복잡하지만 HOF(Higher Order Function) 형태를 사용해 보기로 했다.
api라는 변수는 createCustomFetch를 url과 options를 넣어 retrun하는 무기명함수에 넣어주며 실행하도록 하고,
리턴된 타입을 Promise<T>로 정의하여 createCustomFetch에 제네릭으로 넣어준다면
api 변수를 호출하는 시점에 타입을 넣어줄 수 있게 되었다.
export const api = <T>(
url: string,
options?: ExtendedRequestInit,
): Promise<T> => {
return createCustomFetch<T>({
baseURL: '/api',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.API_TOKEN as string}`,
},
})(url, options);
};
그대신 api를 호출하는 시점에는 꼭 타입을 넣어주어야만 한다.
타입덕분에 이유를 몰랐던 에러 해결!
타입을 명시해준 덕분에
응답은 정상적으로 왔으나, 렌더링을 못시켜주던 문제를 해결할 수 있었다.
이는 res.ok라는 로직때문이었는데,
기계적으로 res.ok를 넣었으나 백엔드에서 응답값을 줄 때
ok라는 키를 넣어주지 않았기 때문에 throw new Error가 되어버렸고,
서버사이드 렌더링 때문인지
해당 에러는 보여지지 않고 아무런 반응도 없는 현상이 일어났었다.
타입을 명시해주어
해당 타입이 return되지 않음을 알아챌 수 있었던 순간이었다...!
'프로젝트' 카테고리의 다른 글
Receipt삭제할때 고려해야 할 점 (0) | 2024.04.17 |
---|---|
stateless하게 유저정보 관리하기 (0) | 2024.04.14 |
컨테이너간 통신 되지 않는 문제 : 해결 (0) | 2024.04.07 |
[배포 준비] next js docker로 실행해보기 (0) | 2024.04.05 |
react-query useInfiniteQuery로 inifinite scrolll구현하기 & useIntersectionObserver 커스텀훅 (0) | 2024.03.30 |