Building Type-Safe APIs with Nuxt 3 and Prisma
Learn how to build end-to-end type-safe APIs using Nuxt 3 server routes and Prisma ORM for a seamless developer experience.
Master HTTP caching headers, CDN strategies, and cache invalidation to deliver blazing-fast experiences while keeping content fresh.
Mbeah Essilfie
April 24, 2026 at 06:04 AM
A cached response takes 0ms server time. No optimization beats not doing the work at all.
The single most important caching mechanism:
Cache-Control: public, max-age=3600, s-maxage=86400, stale-while-revalidate=60
Breaking it down:
public — CDNs and browsers can cache thismax-age=3600 — Browser cache: 1 hours-maxage=86400 — CDN cache: 24 hoursstale-while-revalidate=60 — Serve stale content while revalidating in background// server/middleware/cache-headers.ts
export default defineEventHandler((event) => {
const path = event.path
// Static assets: cache forever (use hashed filenames)
if (path.match(/\.(js|css|woff2|png|jpg|svg)$/)) {
setHeader(event, 'Cache-Control', 'public, max-age=31536000, immutable')
return
}
// API data: short cache with revalidation
if (path.startsWith('/api/posts') && event.method === 'GET') {
setHeader(event, 'Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300')
return
}
// HTML pages: no browser cache, CDN caches briefly
if (!path.startsWith('/api')) {
setHeader(event, 'Cache-Control', 'public, s-maxage=300, stale-while-revalidate=60')
return
}
})
export default defineEventHandler(async (event) => {
const post = await getPost(event)
const etag = `"${hashContent(JSON.stringify(post))}"`
setHeader(event, 'ETag', etag)
// If client already has this version, return 304
const ifNoneMatch = getHeader(event, 'if-none-match')
if (ifNoneMatch === etag) {
setResponseStatus(event, 304)
return null
}
return post
})
The two hard problems in computer science: cache invalidation, naming things, and off-by-one errors.
Strategy 1: Time-based (TTL)
s-maxage=60 ← Content is at most 60 seconds stale
Strategy 2: Event-based purging
// After updating a post, purge CDN cache
async function updatePost(id: string, data: UpdateInput) {
const post = await prisma.post.update({ where: { id }, data })
await purgeCDNCache(`/api/posts/${post.slug}`)
await purgeCDNCache('/api/posts') // List endpoint too
return post
}
Strategy 3: Versioned URLs
<!-- Hash in filename = cache forever, new file = new URL -->
<script src="/app.a3f2b1c.js"></script>
Vary: Accept-Encodingprivate or no-store for user-specific data| Content | Strategy |
|---|---|
| Hashed static assets | immutable, max-age=1y |
| HTML pages | s-maxage=5m, stale-while-revalidate=1m |
| Public API (lists) | s-maxage=1m, stale-while-revalidate=5m |
| User-specific data | private, no-cache |
| Real-time data | no-store |
Caching done right is invisible to users and transformative for performance.
Fullstack Software Developer
Learn how to build end-to-end type-safe APIs using Nuxt 3 server routes and Prisma ORM for a seamless developer experience.
Go beyond basic utility classes and learn how to build cohesive, maintainable design systems with Tailwind CSS.
Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.
