CSR Cache & Next.js
브라우저 cache는 Core의 createCsrCache로 사용하고, Next.js 서버 Data Cache가 필요할 때만 /next의 callApiSsrCache를 추가합니다.
cache wrapper도 commonService의 일부
이 페이지의 callApiClientCache/callApiServerCache 코드는 프로젝트 공용 HTTP adapter의 cache 부분만 떼어 설명합니다. generated service가 이 함수를 import하도록 구성할 수 있으므로, 전체 rxjsHttpService.ts/commonService.ts 구조를 먼저 보면 각 wrapper의 위치와 역할이 더 명확합니다.
CSR cache (클라이언트 cache)
createCsrCache<CacheName>()는 callApiCsrCache(callApiFn, serviceArgs, cacheOptions)와 removeCsrCache(cacheName)를 제공합니다.
import {
createCsrCache,
type ServiceArguments,
} from "@byeolnaerim/typed-rx-http";
import type { paths as Paths } from "./@types/ApiTypes";
type CacheNames = "adminList" | "profile";
const csrCache = createCsrCache<CacheNames>();
export const callApiClientCache = <R>(
args: ServiceArguments<Paths, any, any, R>,
cache: {
cacheName: CacheNames;
cacheTime: number;
cacheSize?: number;
},
) => csrCache.callApiCsrCache(
client.callApi,
args as any,
cache as any,
);
csrCache.removeCsrCache("adminList");
csrCache.removeCsrCache("any-prefix");서버 환경에서는 CSR cache map을 만들지 않고 원래 callApi를 그대로 호출합니다. 브라우저에서는 같은 cache key의 Observable 결과를 shareReplay로 공유합니다.
Next.js adapter: callApiSsrCache
Next.js의 next/cache(unstable_cache)를 사용하는 SSR helper입니다. GET이면서 cacheTime > 0이면 force-cache + revalidate를 사용하고, 그 외 요청은 no-store로 실행합니다.
- headersProvider로 요청별 Cookie/Authorization을 주입할 수 있습니다.
- 401에서 onServer401가 있으면 실행합니다.
import type { paths as Paths } from "./@types/ApiTypes";
import { firstValueFrom } from "rxjs";
import {
callApiSsrCache,
redirectToUnauthorizedOnServer401,
} from "@byeolnaerim/typed-rx-http/next";
const apiUrl = process.env.NEXT_PUBLIC_API_URL!;
const headersProvider = async () => {
const { cookies } = await import("next/headers");
const cookieStore = await cookies();
const cookieHeader = cookieStore
.getAll()
.map((c) => `${c.name}=${c.value}`)
.join("; ");
const accessToken = cookieStore.get("accessToken")?.value;
return {
...(cookieHeader ? { Cookie: cookieHeader } : {}),
...(accessToken
? { Authorization: `Bearer ${accessToken}` }
: {}),
};
};
const obs$ = callApiSsrCache<Paths, { ok: true }, "adminList">({
baseUrl: apiUrl,
serviceArguments: { url: "/api/secure", method: "get" },
cacheForService: { cacheName: "adminList", cacheTime: 10_000 },
headersProvider,
onServer401: redirectToUnauthorizedOnServer401,
});
const res = await firstValueFrom(obs$);사용자별 데이터는 cache하지 않기
브라우저/서버를 하나의 프로젝트 wrapper로 묶는 경우
공용 service에서 브라우저는 createCsrCache, 서버는 callApiSsrCache로 분기하도록 묶을 수도 있습니다. 이것은 프로젝트 구성 패턴이며 Core 자체의 필수 구조는 아닙니다.
commonService.ts
tsconst csrCache = createCsrCache<"memberList" | "profile">();
export const callApiClientCache = (
serviceArguments,
cacheForService,
) => csrCache.callApiCsrCache(
callApi,
serviceArguments,
cacheForService,
);
export const removeCsrCache = csrCache.removeCsrCache;commonService.ts
tsexport const callApiServerCache = (serviceArguments, cacheForService) => {
if (typeof window !== "undefined") {
return callApiClientCache(serviceArguments, cacheForService);
}
return from(
import("@byeolnaerim/typed-rx-http/next"),
).pipe(
mergeMap(({ callApiSsrCache }) =>
callApiSsrCache({
baseUrl: process.env.NEXT_PUBLIC_API_URL || "",
serviceArguments,
cacheForService,
headersProvider,
}),
),
);
};자동 생성 service에서 cache wrapper를 사용할 때도 cache 기능이 필요한 endpoint에만 이 wrapper를 연결하고, 나머지는 일반 callApi를 그대로 사용합니다.