useQueries
는 여러 개의 쿼리를 병렬로 호출한다. 하지만 queryFn
이 server action
이라면 병렬로 처리되지 않는다.
버그인지 의도인지는 모르겠으나 use server
는 서버 상태를 업데이트하는 일종의 mutation의 목적으로 디자인 되었기 때문에 데이터를 가져오는 작업에는 추천되지 않으며, 한 번에 단 하나의 작업만 수행한다고 한다. (ref)
곰곰히 생각해보면, mutation은 한 번에 하나만 수행하는 경우가 많기 때문에 use server
의 동작방식이 납득이 가는 것 같다.
server action의 waterfall 예시
전제 조건은, 3개의 쿼리를 useQueries
로 호출하며 각 쿼리의 queryFn
은 서버 액션으로 동작한다. ( 모든 queryFn
은 prisma를 사용하여 db 내부의 데이터를 가져온다.)
// util/network.ts
'use server';
export const getClub = async ({ id }: ID) => {
const response = await prisma.club.findUnique({
where: { id }
});
return response;
}
export const getFooter = async ({ id }: ID) => {
const response = await prisma.footer.findUnique({
where: { club_id: id },
});
return response;
};
export const getLink = async ({ id }: ID) => {
const response = await prisma.navigation.findMany({
where: {
club_id: id,
},
});
return response;
};

위 예시를 보면 의도한 바와 다르게 3개의 쿼리가 병렬이 아닌, 한 번에 하나의 작업만 수행되고 있다.
만약 데이터가 많지 않다면 크게 문제가 되지 않을 것 같지만, 미래를 고려해서 바꿔보자.
route handler로의 회귀
export const getClub = async ({ id }: { id: string }) => {
const { data } = await axios<ClubResponse>(`/api/club/${id}/info`);
return data;
};
export const getFooter = async ({ id }: { id: string }) => {
const { data } = await axios<FooterResponse>(`/api/club/${id}/footer`);
return data;
};
export const getLink = async ({ id }: { id: string }) => {
const { data } = await axios<LinkResponse>(`/api/club/${id}/link`);
return data;
};

기존의 서버 액션은 prisma가 브라우저에서 동작할 수 없고 API Endpoint를 작성하는게 번거로워서 사용했다. 하지만 이는 의도했던 바인 병렬 처리로 동작하지 않기 때문에, 서버에서 동작 하면서도 병렬 처리가 될 수 있도록 route handler로 변경했다.
이제 3개의 쿼리가 의도했던 대로 병렬로 동작하는 것을 볼 수 있다.
다른 솔루션 — “중첩 함수”
나에게는 이 솔루션이 약간은 귀찮은 작업처럼 보이지만, 상황과 목적에 맞게 적용하면 될 것 같다.(ref)
핵심은 서버 액션으로 동작하는 함수 내부에 다른 async 중첩 함수를 선언하여 이 중첩 함수에서 로직을 처리하는 것이다. 그리고 외부 함수는 해당 중첩 함수를 반환한다.
요약
문서를 잘 살펴보자