CSR Cache & Next.js

เบราว์เซอร์ cache ใช้ createCsrCache ของ Core และจะเพิ่ม /next ของ callApiSsrCache เมื่อจำเป็นต้องใช้ Data Cache ของเซิร์ฟเวอร์ Next.js เท่านั้น


cache wrapper ก็เป็นส่วนหนึ่งของ commonService

โค้ด callApiClientCache/callApiServerCache ของหน้านี้อธิบายเฉพาะส่วน cache ของ HTTP adapter ที่ใช้ร่วมกันในโปรเจกต์ สามารถกำหนดให้บริการที่สร้างขึ้นนำเข้าฟังก์ชันนี้ได้ ดังนั้นการดูโครงสร้างทั้งหมดของ rxjsHttpService.ts/commonService.ts ก่อนจะทำให้เห็นตำแหน่งและบทบาทของแต่ละ wrapper ได้ชัดเจนขึ้น

CSR cache (cache ของไคลเอนต์)

createCsrCache<CacheName>()มี callApiCsrCache(callApiFn, serviceArgs, cacheOptions) และ removeCsrCache(cacheName) ให้บริการ

ts
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 ตามปกติ ในเบราว์เซอร์จะใช้ shareReplay เพื่อแชร์ผลลัพธ์ของ Observable ที่มี cache key เดียวกัน

Next.js adapter: callApiSsrCache

เป็น SSR helper ที่ใช้ next/cache(unstable_cache) ของ Next.js หากเป็น GET และ cacheTime > 0 จะใช้ force-cache + revalidate และคำขออื่น ๆ จะดำเนินการด้วย no-store

  • สามารถฉีด Cookie/Authorization ตามคำขอได้ด้วย headersProvider
  • จะดำเนินการเมื่อมี onServer401 ที่ 401
ts
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 โปรเจกต์เดียว

ในบริการที่ใช้ร่วมกัน เบราว์เซอร์สามารถรวมให้ใช้ createCsrCache และเซิร์ฟเวอร์ใช้ callApiSsrCache ได้ นี่เป็นรูปแบบการจัดโครงสร้างโปรเจกต์และไม่ใช่โครงสร้างที่จำเป็นของ Core เอง

commonService.ts
ts
const csrCache = createCsrCache<"memberList" | "profile">();

export const callApiClientCache = (
  serviceArguments,
  cacheForService,
) => csrCache.callApiCsrCache(
  callApi,
  serviceArguments,
  cacheForService,
);

export const removeCsrCache = csrCache.removeCsrCache;
commonService.ts
ts
export 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,
      }),
    ),
  );
};

เมื่อใช้ cache wrapper ในบริการที่สร้างขึ้นอัตโนมัติ ให้เชื่อมต่อ wrapper นี้เฉพาะกับ endpoint ที่ต้องการฟังก์ชัน cache เท่านั้น ส่วนที่เหลือใช้ callApi ตามปกติ