홈 화면을 렌더링하기 위해 오래전에 봤었던 Nextjs Document를 다시 보게 되었다.
어떻게 데이터를 렌더링할지 여러 고민을 하게 되었는데,
사용자에 따라 화면이 달리 보여야 하니 SSG를 사용할 수는 없고, SEO를 위해 Server-Side로 페이지 렌더링을 하기로 결정했다. 그러기 위해서 Nextjs에서 제공하는 getServersideProps() 함수를 사용했고, 해당 함수 내에서 백엔드 서버로 API 요청을 보낸다. (서버에서만 실행되는 코드..!)
또한 API 요청이 여러개라면 병렬로 비동기 호출을 진행해야 하며, 모든 결과 값을 기다린 후 html 페이지를 생성하고 클라이언트로 페이지를 보내준다. 위의 방식으로 요청을 보내기 위해 Javascript의 Promise.All 함수를 사용하였다.
export const getServerSideProps = async ctx => {
/**
* Promise All 장점 : 병렬로 요청을 진행함
* Promise All 단점 : 한 요청이 실패하면 다른 요청들도 reject됨
*/
const datas = {
bannerDatas: API.BANNER('HOM', 'MAN'),
recommendDatas: API.HOME_PRODUCT('MAN', 'REC'),
seasonDatas: API.HOME_PRODUCT('MAN', 'SEA'),
instagramDatas: API.INSTAGRAM,
};
const keys = Object.keys(datas);
const values = Object.values(datas);
try {
const props: any = {};
const responses = await Promise.all(values.map(url => defaultFetcher(url)));
responses.forEach((item, idx) => {
const key = keys[idx];
props[key] = item;
});
return {
props,
};
} catch (error) {
return {
props: {},
};
}
};
여차저차 구현을 하였는데 테스트 도중 이슈가 생겼다. Promise.All을 사용하면 여러개의 병렬 요청 중 하나가 rejected 되면 전체의 요청은 즉시 거부되고 결과를 저장하지 않는다는 이슈였다.
이 이슈를 해결하기 위해 검색하던 중 es2020에서 지원하는 Promise.AllSettled 함수를 알게 되었다. 이 함수는 요청 중 하나가 실패해도 다른 요청 성공값들은 보존할 수 있게 하는 함수였다. 물론 실패한 요청도 (상태, 실패한 이유)를 함께 배열에 저장한다.
하지만 이 함수를 사용하기 위해 프로젝트의 JS 참조 라이브러리를 갑자기 es2017에서 es2020으로 바로 올릴수는 조심스러웠기에, 공식문서를 참고하여 해당 함수의 폴리필을 직접 구현하였다. (es2020을 사용할 수 있으면 바로 해당함수를 사용하면 된다.)
/**
* PromiseAllSettled polyfill 함수로 구현
*/
export function PromiseAllSettled(promises: any) {
return Promise.all(
promises.map(p =>
Promise.resolve(p).then(
value => ({
status: 'fulfilled',
value,
}),
reason => ({
status: 'rejected',
reason,
}),
),
),
);
}
export const getServerSideProps: GetServerSideProps = async ctx => {
/**
* Promise All : 병렬로 요청을 진행하며 한 요청이 실패하면 다른 요청들도 같이 reject됨
* Promise AllSettled : 병렬로 요청을 진행하며 한 요청이 실패해도 다른 요청들은 보존함
*/
const datas = {
bannerDatas: API.BANNER('HOM', 'MAN'),
recommendDatas: API.HOME_PRODUCT('MAN', 'REC'),
seasonDatas: API.HOME_PRODUCT('MAN', 'SEA'),
instagramDatas: API.INSTAGRAM,
};
const keys = Object.keys(datas);
const values = Object.values(datas);
try {
const props = {};
const responses = await PromiseAllSettled(values.map(url => defaultFetcher(url)));
responses.forEach((item, idx) => {
// 성공
if (item.status === 'fulfilled') {
const key = keys[idx];
props[key] = item.value;
//실패
} else if (item.status === 'rejected') {
console.log(item.reason);
}
});
return {
props: props,
};
} catch (error) {
return {
props: {},
};
}
};
[최종 결과] : 여러개의 병렬 API 요청이 서버로 정상적으로 들어갔고, 그 중 한개의 요청이 실패하여도 정상적으로 responses 배열에 담겨서 props를 정상적으로 내려보냈다.😊