Memoize getCurrentUser per request and document cache rules

getCurrentUser is wrapped in React cache so pages that call it from
several components validate the session once per request. Cross-request
caching ("use cache", unstable_cache, module-level storage) stays
forbidden for cleo data and sessions: the PHP app writes to the same DB,
and a cached session would keep a logged-out user signed in.

CLAUDE.md and the cleo-code-review skill now state both rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
Deardo Satria 7 hours ago
parent 76fda6d1c4
commit baa01c38bd

@ -174,6 +174,13 @@ sama supaya pembaca tahu ini bukan regresi.
- Hapus baris yang masih direferensikan tabel lain harus ditolak dulu (lihat 3a). - Hapus baris yang masih direferensikan tabel lain harus ditolak dulu (lihat 3a).
- Tanggal "hari ini" pakai zona `Asia/Jakarta`, bukan `toISOString()` (UTC). - Tanggal "hari ini" pakai zona `Asia/Jakarta`, bukan `toISOString()` (UTC).
- Tanggal `0000-00-00` tetap string, tidak diubah ke `Date`. - Tanggal `0000-00-00` tetap string, tidak diubah ke `Date`.
- Fungsi baca yang dipanggil beberapa komponen dalam satu render (mis. opsi select yang dipakai
tombol tambah dan daftar) dibungkus `cache` dari `react`, bukan diambil dua kali.
`getCurrentUser()` harus tetap `cache` React.
- `"use cache"`, `unstable_cache`, atau penyimpanan lintas request (variabel modul, Map global)
untuk data `cleo` atau session adalah **Bug**: PHP lama ikut menulis ke DB sehingga data basi,
dan session yang tersimpan membuat user yang sudah logout tetap dianggap login. Cek:
`grep -rn '"use cache"\|unstable_cache' app data lib`.
- Batas panjang zod sama dengan panjang kolom di `db/amigo/schema.ts`. - Batas panjang zod sama dengan panjang kolom di `db/amigo/schema.ts`.
- Aturan bisnis dicocokkan dengan kode lama di `old-source/` (mis. format kode pelanggan). - Aturan bisnis dicocokkan dengan kode lama di `old-source/` (mis. format kode pelanggan).

@ -128,6 +128,11 @@ proxy.ts pengganti middleware di Next 16; hanya cek rin
- Session dicek lewat `getCurrentUser()` di `data/auth.ts` (redirect ke `/login` kalau tidak ada), - Session dicek lewat `getCurrentUser()` di `data/auth.ts` (redirect ke `/login` kalau tidak ada),
dipanggil oleh page dan action yang butuh user. Layout tidak dipakai untuk cek session: layout dipanggil oleh page dan action yang butuh user. Layout tidak dipakai untuk cek session: layout
tidak di-render ulang saat navigasi antar halaman di dalamnya. tidak di-render ulang saat navigasi antar halaman di dalamnya.
- Fungsi baca di `data/` yang dipanggil beberapa komponen dalam satu render (mis.
`getCurrentUser`, `listGroupProductOptions`) dibungkus `cache` dari `react`: query jalan sekali
per request, request berikutnya mengambil ulang. Jangan pakai `"use cache"` (cache lintas
request) untuk data `cleo` atau session: PHP lama menulis ke DB yang sama sehingga data jadi
basi, dan session yang di-cache membuat user yang sudah logout tetap dianggap login.
- `cacheComponents` aktif: komponen yang membaca session (`headers()`/`cookies()`) wajib berada - `cacheComponents` aktif: komponen yang membaca session (`headers()`/`cookies()`) wajib berada
di dalam `<Suspense>`, kalau tidak build gagal. Jangan `await` session di level atas layout. di dalam `<Suspense>`, kalau tidak build gagal. Jangan `await` session di level atas layout.
- `proxy.ts` hanya untuk redirect cepat berdasarkan ada/tidaknya cookie session, bukan pengganti - `proxy.ts` hanya untuk redirect cepat berdasarkan ada/tidaknya cookie session, bukan pengganti

@ -1,4 +1,5 @@
import "server-only"; import "server-only";
import { cache } from "react";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { auth } from "@/lib/auth"; import { auth } from "@/lib/auth";
@ -9,7 +10,9 @@ export type CurrentUser = {
username: string | null; username: string | null;
}; };
export async function getCurrentUser(): Promise<CurrentUser> { // `cache` React: session divalidasi sekali per request walau dipanggil beberapa komponen.
// Jangan diganti `"use cache"`: cache lintas request membuat user logout tetap dianggap login.
export const getCurrentUser = cache(async (): Promise<CurrentUser> => {
const session = await auth.api.getSession({ headers: await headers() }); const session = await auth.api.getSession({ headers: await headers() });
if (!session) redirect("/login"); if (!session) redirect("/login");
@ -18,7 +21,7 @@ export async function getCurrentUser(): Promise<CurrentUser> {
name: session.user.name, name: session.user.name,
username: session.user.username ?? null, username: session.user.username ?? null,
}; };
} });
// Dipakai halaman login: session divalidasi (bukan cuma cek cookie) supaya // Dipakai halaman login: session divalidasi (bukan cuma cek cookie) supaya
// cookie kedaluwarsa tidak memicu redirect bolak-balik dengan getCurrentUser(). // cookie kedaluwarsa tidak memicu redirect bolak-balik dengan getCurrentUser().

Loading…
Cancel
Save