[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"header-categories":3,"post-developers-guide-http-caching":73,"parsed-post-f0cda6e8-230a-4618-8454-c067288d1703":147},{"success":4,"data":5},true,{"items":6,"pagination":70},[7,16,24,32,40,48,56,63],{"id":8,"name":9,"description":10,"parentId":11,"createdAt":12,"updatedAt":12,"parent":11,"children":13,"_count":14},"ai-future","AI & The Future","Artificial intelligence and emerging technology",null,"2026-07-24T06:02:45.990Z",[],{"posts":15},1,{"id":17,"name":18,"description":19,"parentId":11,"createdAt":20,"updatedAt":20,"parent":11,"children":21,"_count":22},"tools-productivity","Tools & Productivity","Developer tools and workflow optimization","2026-07-24T06:02:45.712Z",[],{"posts":23},7,{"id":25,"name":26,"description":27,"parentId":11,"createdAt":28,"updatedAt":28,"parent":11,"children":29,"_count":30},"career","Career & Growth","Professional development for developers","2026-07-24T06:02:45.486Z",[],{"posts":31},3,{"id":33,"name":34,"description":35,"parentId":11,"createdAt":36,"updatedAt":36,"parent":11,"children":37,"_count":38},"opinion","Opinion & Analysis","Industry analysis and technical opinions","2026-07-24T06:02:45.272Z",[],{"posts":39},6,{"id":41,"name":42,"description":43,"parentId":11,"createdAt":44,"updatedAt":44,"parent":11,"children":45,"_count":46},"tutorials","Tutorials","Step-by-step learning guides","2026-07-24T06:02:44.965Z",[],{"posts":47},21,{"id":49,"name":50,"description":51,"parentId":11,"createdAt":52,"updatedAt":52,"parent":11,"children":53,"_count":54},"software-engineering","Software Engineering","Best practices, patterns, and principles","2026-07-24T06:02:44.689Z",[],{"posts":55},24,{"id":57,"name":58,"description":59,"parentId":11,"createdAt":60,"updatedAt":60,"parent":11,"children":61,"_count":62},"devops-cloud","DevOps & Cloud","Infrastructure, deployment, and cloud computing","2026-07-24T06:02:44.453Z",[],{"posts":39},{"id":64,"name":65,"description":66,"parentId":11,"createdAt":67,"updatedAt":67,"parent":11,"children":68,"_count":69},"web-development","Web Development","Frontend and backend web development tutorials and guides","2026-07-24T06:02:44.169Z",[],{"posts":55},{"page":15,"limit":71,"total":71,"totalPages":15,"hasNext":72,"hasPrev":72},8,false,{"success":4,"data":74},{"id":75,"slug":76,"title":77,"excerpt":78,"content":79,"status":80,"visibility":81,"featuredImage":82,"canonicalUrl":11,"readingTime":83,"viewCount":84,"commentEnabled":4,"publishedAt":85,"scheduledAt":11,"createdAt":86,"updatedAt":87,"seoTitle":88,"seoDescription":78,"seoKeywords":11,"authorId":89,"author":90,"coAuthors":95,"tags":96,"categories":112,"comments":115,"_count":116,"relatedPosts":118},"f0cda6e8-230a-4618-8454-c067288d1703","developers-guide-http-caching","The Developer's Guide to HTTP Caching","Master HTTP caching headers, CDN strategies, and cache invalidation to deliver blazing-fast experiences while keeping content fresh.","## Caching Is the Easiest Performance Win\n\nA cached response takes 0ms server time. No optimization beats not doing the work at all.\n\n## Cache-Control Header\n\nThe single most important caching mechanism:\n\n```\nCache-Control: public, max-age=3600, s-maxage=86400, stale-while-revalidate=60\n```\n\nBreaking it down:\n- `public` — CDNs and browsers can cache this\n- `max-age=3600` — Browser cache: 1 hour\n- `s-maxage=86400` — CDN cache: 24 hours\n- `stale-while-revalidate=60` — Serve stale content while revalidating in background\n\n## Caching Strategies by Content Type\n\n```typescript\n\u002F\u002F server\u002Fmiddleware\u002Fcache-headers.ts\nexport default defineEventHandler((event) => {\n  const path = event.path\n\n  \u002F\u002F Static assets: cache forever (use hashed filenames)\n  if (path.match(\u002F\\.(js|css|woff2|png|jpg|svg)$\u002F)) {\n    setHeader(event, 'Cache-Control', 'public, max-age=31536000, immutable')\n    return\n  }\n\n  \u002F\u002F API data: short cache with revalidation\n  if (path.startsWith('\u002Fapi\u002Fposts') && event.method === 'GET') {\n    setHeader(event, 'Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300')\n    return\n  }\n\n  \u002F\u002F HTML pages: no browser cache, CDN caches briefly\n  if (!path.startsWith('\u002Fapi')) {\n    setHeader(event, 'Cache-Control', 'public, s-maxage=300, stale-while-revalidate=60')\n    return\n  }\n})\n```\n\n## ETag for Conditional Requests\n\n```typescript\nexport default defineEventHandler(async (event) => {\n  const post = await getPost(event)\n  const etag = `\"${hashContent(JSON.stringify(post))}\"`\n\n  setHeader(event, 'ETag', etag)\n\n  \u002F\u002F If client already has this version, return 304\n  const ifNoneMatch = getHeader(event, 'if-none-match')\n  if (ifNoneMatch === etag) {\n    setResponseStatus(event, 304)\n    return null\n  }\n\n  return post\n})\n```\n\n::callout{icon=\"i-lucide-zap\" color=\"success\"}\nETags save bandwidth — a 304 response has no body. The browser reuses its cached version, and the server avoids serializing\u002Ftransmitting the full response.\n::\n\n## Cache Invalidation\n\nThe two hard problems in computer science: cache invalidation, naming things, and off-by-one errors.\n\n**Strategy 1: Time-based (TTL)**\n```\ns-maxage=60  ← Content is at most 60 seconds stale\n```\n\n**Strategy 2: Event-based purging**\n```typescript\n\u002F\u002F After updating a post, purge CDN cache\nasync function updatePost(id: string, data: UpdateInput) {\n  const post = await prisma.post.update({ where: { id }, data })\n  await purgeCDNCache(`\u002Fapi\u002Fposts\u002F${post.slug}`)\n  await purgeCDNCache('\u002Fapi\u002Fposts') \u002F\u002F List endpoint too\n  return post\n}\n```\n\n**Strategy 3: Versioned URLs**\n```html\n\u003C!-- Hash in filename = cache forever, new file = new URL -->\n\u003Cscript src=\"\u002Fapp.a3f2b1c.js\">\u003C\u002Fscript>\n```\n\n## Common Mistakes\n\n1. **No-cache doesn't mean \"don't cache\"** — It means \"revalidate before using cache\"\n2. **Forgetting Vary header** — Different content per Accept-Encoding? Add `Vary: Accept-Encoding`\n3. **Caching authenticated responses** — Use `private` or `no-store` for user-specific data\n4. **No cache-busting for assets** — Without hashed filenames, users get stale CSS\u002FJS\n\n## Decision Quick-Reference\n\n| Content | Strategy |\n|---------|----------|\n| Hashed static assets | `immutable, max-age=1y` |\n| HTML pages | `s-maxage=5m, stale-while-revalidate=1m` |\n| Public API (lists) | `s-maxage=1m, stale-while-revalidate=5m` |\n| User-specific data | `private, no-cache` |\n| Real-time data | `no-store` |\n\nCaching done right is invisible to users and transformative for performance.\n","published","public","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1498050108023-c5249f4df085?w=1200&q=80",9,1829,"2026-04-24T06:04:33.840Z","2026-07-24T06:04:33.842Z","2026-07-28T12:15:56.850Z","The Developer's Guide to HTTP Caching | BitBlog","fddb5d93-7a2c-4d86-a06a-fa32e73a01c6",{"email":91,"bio":92,"id":89,"name":93,"avatarUrl":94},"mbeahessilfieprince@gmail.com","Fullstack Software Developer ","Mbeah Essilfie","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F93322394?v=4",[],[97,102,107],{"id":98,"name":99,"color":100,"description":101},"devops","DevOps","#ff6c37","Development and Operations",{"id":103,"name":104,"color":105,"description":106},"performance","Performance","#e535ab","Web performance optimization",{"id":108,"name":109,"color":110,"description":111},"api-design","API Design","#009688","RESTful and GraphQL API patterns",[113,114],{"id":64,"name":65,"description":66},{"id":49,"name":50,"description":51},[],{"comments":117},0,[119,127,137],{"id":120,"slug":121,"title":122,"excerpt":123,"featuredImage":82,"viewCount":124,"readingTime":71,"publishedAt":125,"author":126},"2997028f-4d22-4eb7-9d86-894a54cb559a","building-type-safe-apis-nuxt3-prisma","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.",1920,"2026-07-23T06:02:46.910Z",{"id":89,"name":93,"avatarUrl":94},{"id":128,"slug":129,"title":130,"excerpt":131,"featuredImage":132,"viewCount":133,"readingTime":134,"publishedAt":135,"author":136},"da9e4553-8b0d-411e-b4ec-f9684017e163","mastering-tailwind-css-design-systems","Mastering Tailwind CSS: From Utility Classes to Design Systems","Go beyond basic utility classes and learn how to build cohesive, maintainable design systems with Tailwind CSS.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1555066931-4365d14bab8c?w=1200&q=80",255,10,"2026-07-21T06:02:49.268Z",{"id":89,"name":93,"avatarUrl":94},{"id":138,"slug":139,"title":140,"excerpt":141,"featuredImage":142,"viewCount":143,"readingTime":144,"publishedAt":145,"author":146},"50add5fc-4026-4267-b917-7f71ea9e35b0","complete-guide-vue3-composables","The Complete Guide to Vue 3 Composables","Understand the power of Vue 3's Composition API through practical, real-world composable patterns that you can use today.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1517694712202-14dd9538aa97?w=1200&q=80",988,12,"2026-07-19T06:02:51.753Z",{"id":89,"name":93,"avatarUrl":94},{"data":148,"body":150,"toc":549},{"title":149,"description":149},"",{"type":151,"children":152},"root",[153,162,168,174,179,191,196,244,250,261,267,275,286,292,297,306,315,323,331,339,349,355,421,427,539,544],{"type":154,"tag":155,"props":156,"children":158},"element","h2",{"id":157},"caching-is-the-easiest-performance-win",[159],{"type":160,"value":161},"text","Caching Is the Easiest Performance Win",{"type":154,"tag":163,"props":164,"children":165},"p",{},[166],{"type":160,"value":167},"A cached response takes 0ms server time. No optimization beats not doing the work at all.",{"type":154,"tag":155,"props":169,"children":171},{"id":170},"cache-control-header",[172],{"type":160,"value":173},"Cache-Control Header",{"type":154,"tag":163,"props":175,"children":176},{},[177],{"type":160,"value":178},"The single most important caching mechanism:",{"type":154,"tag":180,"props":181,"children":185},"pre",{"className":182,"code":184,"language":160},[183],"language-text","Cache-Control: public, max-age=3600, s-maxage=86400, stale-while-revalidate=60\n",[186],{"type":154,"tag":187,"props":188,"children":189},"code",{"__ignoreMap":149},[190],{"type":160,"value":184},{"type":154,"tag":163,"props":192,"children":193},{},[194],{"type":160,"value":195},"Breaking it down:",{"type":154,"tag":197,"props":198,"children":199},"ul",{},[200,211,222,233],{"type":154,"tag":201,"props":202,"children":203},"li",{},[204,209],{"type":154,"tag":187,"props":205,"children":207},{"className":206},[],[208],{"type":160,"value":81},{"type":160,"value":210}," — CDNs and browsers can cache this",{"type":154,"tag":201,"props":212,"children":213},{},[214,220],{"type":154,"tag":187,"props":215,"children":217},{"className":216},[],[218],{"type":160,"value":219},"max-age=3600",{"type":160,"value":221}," — Browser cache: 1 hour",{"type":154,"tag":201,"props":223,"children":224},{},[225,231],{"type":154,"tag":187,"props":226,"children":228},{"className":227},[],[229],{"type":160,"value":230},"s-maxage=86400",{"type":160,"value":232}," — CDN cache: 24 hours",{"type":154,"tag":201,"props":234,"children":235},{},[236,242],{"type":154,"tag":187,"props":237,"children":239},{"className":238},[],[240],{"type":160,"value":241},"stale-while-revalidate=60",{"type":160,"value":243}," — Serve stale content while revalidating in background",{"type":154,"tag":155,"props":245,"children":247},{"id":246},"caching-strategies-by-content-type",[248],{"type":160,"value":249},"Caching Strategies by Content Type",{"type":154,"tag":180,"props":251,"children":256},{"className":252,"code":253,"language":254,"meta":149,"style":255},"language-typescript","\u002F\u002F server\u002Fmiddleware\u002Fcache-headers.ts\nexport default defineEventHandler((event) => {\n  const path = event.path\n\n  \u002F\u002F Static assets: cache forever (use hashed filenames)\n  if (path.match(\u002F\\.(js|css|woff2|png|jpg|svg)$\u002F)) {\n    setHeader(event, 'Cache-Control', 'public, max-age=31536000, immutable')\n    return\n  }\n\n  \u002F\u002F API data: short cache with revalidation\n  if (path.startsWith('\u002Fapi\u002Fposts') && event.method === 'GET') {\n    setHeader(event, 'Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300')\n    return\n  }\n\n  \u002F\u002F HTML pages: no browser cache, CDN caches briefly\n  if (!path.startsWith('\u002Fapi')) {\n    setHeader(event, 'Cache-Control', 'public, s-maxage=300, stale-while-revalidate=60')\n    return\n  }\n})\n","typescript","undefined",[257],{"type":154,"tag":187,"props":258,"children":259},{"__ignoreMap":149},[260],{"type":160,"value":253},{"type":154,"tag":155,"props":262,"children":264},{"id":263},"etag-for-conditional-requests",[265],{"type":160,"value":266},"ETag for Conditional Requests",{"type":154,"tag":180,"props":268,"children":270},{"className":252,"code":269,"language":254,"meta":149,"style":255},"export default defineEventHandler(async (event) => {\n  const post = await getPost(event)\n  const etag = `\"${hashContent(JSON.stringify(post))}\"`\n\n  setHeader(event, 'ETag', etag)\n\n  \u002F\u002F If client already has this version, return 304\n  const ifNoneMatch = getHeader(event, 'if-none-match')\n  if (ifNoneMatch === etag) {\n    setResponseStatus(event, 304)\n    return null\n  }\n\n  return post\n})\n",[271],{"type":154,"tag":187,"props":272,"children":273},{"__ignoreMap":149},[274],{"type":160,"value":269},{"type":154,"tag":276,"props":277,"children":280},"callout",{"color":278,"icon":279},"success","i-lucide-zap",[281],{"type":154,"tag":163,"props":282,"children":283},{},[284],{"type":160,"value":285},"ETags save bandwidth — a 304 response has no body. The browser reuses its cached version, and the server avoids serializing\u002Ftransmitting the full response.",{"type":154,"tag":155,"props":287,"children":289},{"id":288},"cache-invalidation",[290],{"type":160,"value":291},"Cache Invalidation",{"type":154,"tag":163,"props":293,"children":294},{},[295],{"type":160,"value":296},"The two hard problems in computer science: cache invalidation, naming things, and off-by-one errors.",{"type":154,"tag":163,"props":298,"children":299},{},[300],{"type":154,"tag":301,"props":302,"children":303},"strong",{},[304],{"type":160,"value":305},"Strategy 1: Time-based (TTL)",{"type":154,"tag":180,"props":307,"children":310},{"className":308,"code":309,"language":160},[183],"s-maxage=60  ← Content is at most 60 seconds stale\n",[311],{"type":154,"tag":187,"props":312,"children":313},{"__ignoreMap":149},[314],{"type":160,"value":309},{"type":154,"tag":163,"props":316,"children":317},{},[318],{"type":154,"tag":301,"props":319,"children":320},{},[321],{"type":160,"value":322},"Strategy 2: Event-based purging",{"type":154,"tag":180,"props":324,"children":326},{"className":252,"code":325,"language":254,"meta":149,"style":255},"\u002F\u002F After updating a post, purge CDN cache\nasync function updatePost(id: string, data: UpdateInput) {\n  const post = await prisma.post.update({ where: { id }, data })\n  await purgeCDNCache(`\u002Fapi\u002Fposts\u002F${post.slug}`)\n  await purgeCDNCache('\u002Fapi\u002Fposts') \u002F\u002F List endpoint too\n  return post\n}\n",[327],{"type":154,"tag":187,"props":328,"children":329},{"__ignoreMap":149},[330],{"type":160,"value":325},{"type":154,"tag":163,"props":332,"children":333},{},[334],{"type":154,"tag":301,"props":335,"children":336},{},[337],{"type":160,"value":338},"Strategy 3: Versioned URLs",{"type":154,"tag":180,"props":340,"children":344},{"className":341,"code":342,"language":343,"meta":149,"style":255},"language-html","\u003C!-- Hash in filename = cache forever, new file = new URL -->\n\u003Cscript src=\"\u002Fapp.a3f2b1c.js\">\u003C\u002Fscript>\n","html",[345],{"type":154,"tag":187,"props":346,"children":347},{"__ignoreMap":149},[348],{"type":160,"value":342},{"type":154,"tag":155,"props":350,"children":352},{"id":351},"common-mistakes",[353],{"type":160,"value":354},"Common Mistakes",{"type":154,"tag":356,"props":357,"children":358},"ol",{},[359,369,385,411],{"type":154,"tag":201,"props":360,"children":361},{},[362,367],{"type":154,"tag":301,"props":363,"children":364},{},[365],{"type":160,"value":366},"No-cache doesn't mean \"don't cache\"",{"type":160,"value":368}," — It means \"revalidate before using cache\"",{"type":154,"tag":201,"props":370,"children":371},{},[372,377,379],{"type":154,"tag":301,"props":373,"children":374},{},[375],{"type":160,"value":376},"Forgetting Vary header",{"type":160,"value":378}," — Different content per Accept-Encoding? Add ",{"type":154,"tag":187,"props":380,"children":382},{"className":381},[],[383],{"type":160,"value":384},"Vary: Accept-Encoding",{"type":154,"tag":201,"props":386,"children":387},{},[388,393,395,401,403,409],{"type":154,"tag":301,"props":389,"children":390},{},[391],{"type":160,"value":392},"Caching authenticated responses",{"type":160,"value":394}," — Use ",{"type":154,"tag":187,"props":396,"children":398},{"className":397},[],[399],{"type":160,"value":400},"private",{"type":160,"value":402}," or ",{"type":154,"tag":187,"props":404,"children":406},{"className":405},[],[407],{"type":160,"value":408},"no-store",{"type":160,"value":410}," for user-specific data",{"type":154,"tag":201,"props":412,"children":413},{},[414,419],{"type":154,"tag":301,"props":415,"children":416},{},[417],{"type":160,"value":418},"No cache-busting for assets",{"type":160,"value":420}," — Without hashed filenames, users get stale CSS\u002FJS",{"type":154,"tag":155,"props":422,"children":424},{"id":423},"decision-quick-reference",[425],{"type":160,"value":426},"Decision Quick-Reference",{"type":154,"tag":428,"props":429,"children":430},"table",{},[431,450],{"type":154,"tag":432,"props":433,"children":434},"thead",{},[435],{"type":154,"tag":436,"props":437,"children":438},"tr",{},[439,445],{"type":154,"tag":440,"props":441,"children":442},"th",{},[443],{"type":160,"value":444},"Content",{"type":154,"tag":440,"props":446,"children":447},{},[448],{"type":160,"value":449},"Strategy",{"type":154,"tag":451,"props":452,"children":453},"tbody",{},[454,472,489,506,523],{"type":154,"tag":436,"props":455,"children":456},{},[457,463],{"type":154,"tag":458,"props":459,"children":460},"td",{},[461],{"type":160,"value":462},"Hashed static assets",{"type":154,"tag":458,"props":464,"children":465},{},[466],{"type":154,"tag":187,"props":467,"children":469},{"className":468},[],[470],{"type":160,"value":471},"immutable, max-age=1y",{"type":154,"tag":436,"props":473,"children":474},{},[475,480],{"type":154,"tag":458,"props":476,"children":477},{},[478],{"type":160,"value":479},"HTML pages",{"type":154,"tag":458,"props":481,"children":482},{},[483],{"type":154,"tag":187,"props":484,"children":486},{"className":485},[],[487],{"type":160,"value":488},"s-maxage=5m, stale-while-revalidate=1m",{"type":154,"tag":436,"props":490,"children":491},{},[492,497],{"type":154,"tag":458,"props":493,"children":494},{},[495],{"type":160,"value":496},"Public API (lists)",{"type":154,"tag":458,"props":498,"children":499},{},[500],{"type":154,"tag":187,"props":501,"children":503},{"className":502},[],[504],{"type":160,"value":505},"s-maxage=1m, stale-while-revalidate=5m",{"type":154,"tag":436,"props":507,"children":508},{},[509,514],{"type":154,"tag":458,"props":510,"children":511},{},[512],{"type":160,"value":513},"User-specific data",{"type":154,"tag":458,"props":515,"children":516},{},[517],{"type":154,"tag":187,"props":518,"children":520},{"className":519},[],[521],{"type":160,"value":522},"private, no-cache",{"type":154,"tag":436,"props":524,"children":525},{},[526,531],{"type":154,"tag":458,"props":527,"children":528},{},[529],{"type":160,"value":530},"Real-time data",{"type":154,"tag":458,"props":532,"children":533},{},[534],{"type":154,"tag":187,"props":535,"children":537},{"className":536},[],[538],{"type":160,"value":408},{"type":154,"tag":163,"props":540,"children":541},{},[542],{"type":160,"value":543},"Caching done right is invisible to users and transformative for performance.",{"type":154,"tag":545,"props":546,"children":547},"style",{},[548],{"type":160,"value":149},{"title":149,"searchDepth":550,"depth":550,"links":551},2,[552,553,554,555,556,557,558],{"id":157,"depth":550,"text":161},{"id":170,"depth":550,"text":173},{"id":246,"depth":550,"text":249},{"id":263,"depth":550,"text":266},{"id":288,"depth":550,"text":291},{"id":351,"depth":550,"text":354},{"id":423,"depth":550,"text":426}]