--- id: wiki-2026-0508-case-study-skybound-asset-cache- title: "Case Study: Skybound Asset Cache Busting" category: 10_Wiki/Topics status: verified canonical_id: self aliases: [Cache Busting, Asset Versioning Case Study] duplicate_of: none source_trust_level: B confidence_score: 0.85 verification_status: applied tags: [case-study, caching, deployment, web-performance] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript framework: Vite --- # Case Study: Skybound Asset Cache Busting ## 매 한 줄 > **"매 stale-asset bug 매 production"**. 매 Skybound (fictional internal codename) 매 CDN-cached JS bundle 매 user device 매 days-old version 의 root-cause hunt → 매 content-hash filename + immutable cache-control headers 의 conventional fix. ## 매 핵심 ### 매 problem - 매 deploy → 매 some users 매 broken UI (stale `app.js` mismatched with new `api.js` schema). - 매 CDN-edge 매 7-day cache, browser 매 1-day cache, 매 union 매 race. ### 매 root cause - 매 fixed filename `/static/app.js` 매 invalidation 매 manual. - 매 `Cache-Control: max-age=86400` 매 too aggressive for mutable name. ### 매 fix layers 1. Content-hash in filename: `app.[hash].js`. 2. `Cache-Control: public, max-age=31536000, immutable` for hashed assets. 3. Short TTL on `index.html` (entry point) only. ## 💻 패턴 ### Vite hashed output (default) ```ts // vite.config.ts export default { build: { rollupOptions: { output: { entryFileNames: 'assets/[name].[hash].js', chunkFileNames: 'assets/[name].[hash].js', assetFileNames: 'assets/[name].[hash][extname]', }, }, }, }; ``` ### CDN cache-control (Cloudflare worker) ```js addEventListener('fetch', (e) => e.respondWith(handle(e.request))); async function handle(req) { const url = new URL(req.url); const res = await fetch(req); const r = new Response(res.body, res); if (/\.[a-f0-9]{8,}\.(js|css|png|webp)$/.test(url.pathname)) { r.headers.set('Cache-Control', 'public, max-age=31536000, immutable'); } else if (url.pathname.endsWith('.html') || url.pathname === '/') { r.headers.set('Cache-Control', 'public, max-age=60, must-revalidate'); } return r; } ``` ### Service worker cache cleanup ```ts self.addEventListener('activate', (e) => { e.waitUntil( caches.keys().then(keys => Promise.all(keys.filter(k => k !== CURRENT_VERSION).map(k => caches.delete(k))) ) ); }); ``` ### Deployment 매 atomic swap (S3 + CloudFront) ```bash aws s3 sync ./dist s3://bucket --cache-control 'public,max-age=31536000,immutable' \ --exclude 'index.html' aws s3 cp ./dist/index.html s3://bucket/index.html \ --cache-control 'public,max-age=60,must-revalidate' aws cloudfront create-invalidation --distribution-id $ID --paths '/index.html' '/' ``` ### Verification 매 post-deploy ```bash curl -I https://app.example.com/assets/app.abc123.js | grep -i cache-control # expect: cache-control: public, max-age=31536000, immutable ``` ## 매 결정 기준 | Asset type | Strategy | |---|---| | Hashed JS/CSS/images | `immutable, max-age=1y` | | HTML entry | `max-age=60, must-revalidate` | | API JSON | `no-store` or `stale-while-revalidate` | **기본값**: Vite/Webpack hashed output + 1y immutable for hashed, short TTL for HTML. ## 🔗 Graph - 변형: [[ETag]] - Adjacent: [[Vite]] ## 🤖 LLM 활용 **언제**: deploy-time stale-asset bug, CDN config debugging. **언제 X**: server-rendered no-cache responses (caching irrelevant). ## ❌ 안티패턴 - **Fixed filenames + long cache**: 매 production-broken-on-deploy guarantee. - **Cache-busting via querystring `?v=2`**: 매 some CDNs ignore query. - **Forgetting HTML cache TTL**: hashed assets 매 useless if entry HTML cached. ## 🧪 검증 / 중복 - Verified (MDN Cache-Control, Vite docs, Web.dev caching guide). - 신뢰도 B+. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — Skybound case study FULL with Vite + CDN patterns |