Cache
Use cache() for request-scoped data caching. Prevent redundant database queries and API calls within a single request — even when the same data is fetched from multiple components.
Overview
The cache() function wraps any async function to enable automatic request-scoped memoization. When multiple components call the same cached function with identical arguments during a single request, only the first call executes — subsequent calls return the cached result instantly.
Basic usage
Wrap your data fetching function with cache():
import { cache } from 'nukejs'
import { prisma } from './prisma'
// Without cache: every call hits the database
export async function getUser(id: number) {
return prisma.user.findUnique({ where: { id } })
}
// With cache: first call hits DB, subsequent calls return cached value
export const getCachedUser = cache(async (id: number) => {
console.log('DB query for user', id)
return prisma.user.findUnique({ where: { id } })
})Example: Prevent N+1 queries
A common pattern: multiple components in the render tree need the same user data. Without caching, each component triggers a separate database query. With cache(), the query runs once and all components share the result.
import { getCachedUser } from '../../lib/db'
import ProfileHeader from '../../components/ProfileHeader'
import RecentActivity from '../../components/RecentActivity'
import Sidebar from '../../components/Sidebar'
export default async function DashboardPage() {
const userId = 123
// Three components each call getCachedUser(123)
// Only the first call hits the database
return (
<div>
<ProfileHeader userId={userId} />
<RecentActivity userId={userId} />
<Sidebar userId={userId} />
</div>
)
}import { getCachedUser } from '../lib/db'
export default async function ProfileHeader({ userId }: { userId: number }) {
// First component to call getCachedUser(123) — executes DB query
const user = await getCachedUser(userId)
return <h1>{user.name}</h1>
}import { getCachedUser } from '../lib/db'
export default async function RecentActivity({ userId }: { userId: number }) {
// Second call with same userId — returns cached result, no DB query
const user = await getCachedUser(userId)
return <p>Recent posts by {user.name}</p>
}import { getCachedUser } from '../lib/db'
export default async function Sidebar({ userId }: { userId: number }) {
// Third call — also returns cached result
const user = await getCachedUser(userId)
return <aside>Logged in as {user.email}</aside>
}console.log in getCachedUser, you'll see "DB query for user 123" printed only once per request, even though three components called the function.Cache key behavior
The cache key is computed from the function arguments using JSON.stringify. Arguments must be JSON-serializable (strings, numbers, booleans, arrays, plain objects). Functions, symbols, and class instances cannot be cached and will bypass the cache silently.
import { cache } from 'nukejs'
export const getPostsByTag = cache(async (tags: string[]) => {
// Arrays are serialized for the cache key:
// ['react', 'ssr'] and ['react', 'ssr'] → same key (cached)
// ['react', 'ssr'] and ['ssr', 'react'] → different keys (separate calls)
return db.posts.findMany({ where: { tags: { hasEvery: tags } } })
})
export const getPost = cache(async (id: number, options: { draft?: boolean }) => {
// Object arguments work too:
// (1, { draft: true }) and (1, { draft: true }) → same key
// (1, { draft: true }) and (1, { draft: false }) → different keys
return db.posts.findUnique({ where: { id }, include: { author: options.draft } })
})Example: Cascading data dependencies
Use cache() to share intermediate results across unrelated parts of the component tree without prop drilling or global state:
import { cache } from 'nukejs'
import { prisma } from './prisma'
export const getCurrentUser = cache(async (sessionToken: string) => {
// Expensive session lookup
const session = await prisma.session.findUnique({
where: { token: sessionToken },
include: { user: true }
})
return session?.user ?? null
})
export const getUserProjects = cache(async (sessionToken: string) => {
// Reuses getCurrentUser result if already cached
const user = await getCurrentUser(sessionToken)
if (!user) return []
return prisma.project.findMany({ where: { ownerId: user.id } })
})
export const getUserNotifications = cache(async (sessionToken: string) => {
// Also reuses getCurrentUser result
const user = await getCurrentUser(sessionToken)
if (!user) return []
return prisma.notification.findMany({
where: { userId: user.id, read: false }
})
})import { getCurrentUser, getUserProjects, getUserNotifications } from '../../lib/data'
import { useRequest } from 'nukejs'
export default async function WorkspacePage() {
const { headers } = useRequest()
const token = headers['x-session-token'] ?? ''
// All three functions call getCurrentUser(token) internally,
// but the session lookup runs only once
const [user, projects, notifications] = await Promise.all([
getCurrentUser(token),
getUserProjects(token),
getUserNotifications(token),
])
if (!user) return <p>Please log in</p>
return (
<main>
<h1>Welcome, {user.name}</h1>
<p>You have {notifications.length} unread notifications</p>
<ul>
{projects.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
</main>
)
}When to use cache()
| Use case | Benefit |
|---|---|
| Shared data across multiple components | Fetch once at the top level, call everywhere without prop drilling |
| Cascading dependencies (user → projects → tasks) | Each level can call the parent function; intermediate results are cached |
| Layout + page both need the same data | Both can fetch independently; cache ensures only one network/DB call |
| Heavy computation (parsing, sorting, aggregation) | Run once per request even if multiple components need the result |
Limitations
- Server-only —
cache()works during SSR. It has no effect in client components or after hydration. - Arguments must be serializable — Functions, symbols, and complex objects are not cached. The call falls through to the original function.
- Request-scoped — Cache entries do not persist across requests. For cross-request caching, use a separate solution like Redis or an in-memory LRU cache.
- Async only — The function passed to
cache()must return a Promise. Synchronous functions are not supported.
TypeScript
The cache() function preserves the type signature of the wrapped function:
import { cache } from 'nukejs'
// Fully typed, including parameters and return type
export const getUser = cache(async (id: number): Promise<User | null> => {
return prisma.user.findUnique({ where: { id } })
})
// Usage is fully type-safe
const user = await getUser(123) // user: User | null
const invalid = await getUser('abc') // ❌ TypeScript error