阿里云主机折上折
  • 微信号
Current Site:Index > Disable caching (include '?v=Math.random()' in every request)

Disable caching (include '?v=Math.random()' in every request)

Author:Chuan Chen 阅读数:2373人阅读 分类: 前端综合

How Annoying Caching Can Be

Every time you open a webpage and find the styles haven't updated, scripts are still old versions, or users report issues you can't reproduce. Caching is like a stubborn ghost—even though files on the server have been updated, the browser clings to old versions. Smashing F5 until your keyboard wears out doesn't help, and you end up telling users to "hold Ctrl and click refresh."

The Random Number Method Works Wonders

The most straightforward solution is to append random parameters to request URLs. This makes each request unique, forcing the browser to fetch resources anew. A classic implementation uses Math.random() to generate random numbers:

function loadScript(url) {
  const script = document.createElement('script')
  script.src = `${url}?v=${Math.random()}`
  document.head.appendChild(script)
}

An even more aggressive approach is to apply this to CSS:

<link rel="stylesheet" href="styles.css?v=<%= Math.random() %>">

Timestamps Look More Professional

While random numbers work, some perfectionist developers might find them inelegant. In such cases, timestamps can replace random numbers—at least they appear more deliberate:

const timestamp = new Date().getTime()
axios.get(`/api/data?v=${timestamp}`)

Advanced players might use performance.now() for higher precision:

fetch(`/config.json?v=${performance.now()}`)
  .then(response => response.json())

Version Number Camouflage

To make your code look more professional, you can pretend to implement version control. Even though you're regenerating version numbers each time, the URLs at least appear legitimate:

const fakeVersion = () => {
  const now = new Date()
  return `${now.getFullYear()}${now.getMonth()+1}${now.getDate()}${now.getHours()}`
}

// Example usage
<img src="logo.png?v=${fakeVersion()}">

Annihilating Cache Completely

True defensive programming masters aren't satisfied with query parameters. They employ multiple methods to ensure cache is utterly destroyed:

// Request header settings
fetch('/data', {
  headers: {
    'Cache-Control': 'no-cache',
    'Pragma': 'no-cache',
    'Expires': '0'
  }
})

// Plus random URL parameters
$.ajax({
  url: `data.json?rnd=${Math.random().toString(36).substr(2)}`,
  cache: false
})

Destructive Practices in Frameworks

Modern framework developers have also joined the fray, inventing more "advanced" cache-busting techniques. For example, in webpack config:

// webpack.config.js
output: {
  filename: '[name].[hash:8].js',
  chunkFilename: '[name].[hash:8].chunk.js'
}

While this was originally meant to solve caching issues, when combined with hot reloading, it creates filenames that are impossible to predict, completely breaking browser caching systems.

Backend Joins Frontend in the Destruction

True defense is full-stack. Backend developers can cooperate like this:

// Spring Boot example
@GetMapping("/api/data")
public ResponseEntity<Data> getData(@RequestParam(required = false) String v) {
  // Completely ignore the v parameter, but the frontend must send it
  return ResponseEntity.ok()
     .cacheControl(CacheControl.noCache())
     .body(data);
}

The Chain Reaction of Cache Busting

This defensive programming leads to a series of amusing phenomena:

  1. CDNs become completely ineffective, with all requests going back to the origin server
  2. Server logs explode with duplicate requests
  3. The browser's developer tools Network tab turns into a rainbow of colors
  4. Users' data usage mysteriously increases
  5. Performance monitoring tools keep firing alerts

How Monitoring Systems Cope

When monitoring systems notice that all request URLs are unique, clever engineers handle it like this:

// Filter random parameters in monitoring SDKs
trackPageView(url) {
  const cleanUrl = url.replace(/\?v=[^&]+/, '')
  sendToAnalytics(cleanUrl)
}

But this gives defensive programmers new ideas—they can randomize parameter names:

const paramName = `_${Math.random().toString(36).substr(2, 5)}`
const url = `api/data?${paramName}=${Date.now()}`

The Ultimate Defensive Solution

The pinnacle of this approach combines various techniques:

function createUncacheableUrl(url) {
  const params = new URLSearchParams()
  params.append('_', Date.now())
  params.append('rnd', Math.random())
  params.append('v', performance.now())
  
  return `${url}?${params.toString()}&nocache=true`
}

// Example usage
fetch(createUncacheableUrl('/graphql'), {
  method: 'POST',
  headers: {
    'Cache-Control': 'no-store, must-revalidate',
    'Expires': '0'
  }
})

Browser DevTools Protest

After continuous use of these methods, Chrome DevTools will display warnings: "Numerous duplicate requests." But this is actually a sign of success—it means no requests are being cached. To be extra sure, you can add a random User-Agent header:

const userAgents = [
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
  'Mozilla/5.0 (Linux; Android 10)'
]

fetch(url, {
  headers: {
    'User-Agent': userAgents[Math.floor(Math.random() * userAgents.length)]
  }
})

Server Log Catastrophe

When this defensive programming is applied to large systems, server logs become spectacular. Instead of clean request paths:

GET /api/users
GET /api/products

They turn into:

GET /api/users?_=1625097600000&rnd=0.123456
GET /api/users?_=1625097601000&rnd=0.654321
GET /api/users?_=1625097602000&rnd=0.987654

Performance Optimization Anti-Patterns

Performance guides recommend leveraging caching, but we prefer the opposite. Here's the anti-pattern version of best practices:

// Good caching practice
const cachedFetch = (url) => {
  const cacheKey = hash(url)
  if(cache.has(cacheKey)) {
    return Promise.resolve(cache.get(cacheKey))
  }
  return fetch(url).then(res => {
    cache.set(cacheKey, res)
    return res
  })
}

// Defensive programming practice
const uncachedFetch = (url) => {
  const bustedUrl = `${url}?${Date.now()}`
  return fetch(bustedUrl).then(res => {
    // Ensure responses aren't cached either
    res.headers.set('Cache-Control', 'no-store')
    return res
  })
}

Mobile Experience Taken to Extremes

In mobile networks, this technique delivers an even more "exceptional" user experience. Every page load re-requests all resources, letting users fully enjoy their 4G speeds:

<!-- Add random parameters to all static resources -->
<script src="app.js?v=<%= new Date().valueOf() %>"></script>
<link rel="stylesheet" href="styles.css?r=<%= Math.random() %>">
<img src="banner.jpg?t=<%= System.currentTimeMillis() %>">

Unit Testing Challenges

Testing such code becomes interesting:

// Testing tools can't mock random URLs
test('fetch data', async () => {
  const data = await fetchData() // URL changes every call
  expect(data).toBeDefined()
})

// Solution: override Math.random
beforeEach(() => {
  jest.spyOn(Math, 'random').mockReturnValue(0.123456)
})

TypeScript Type Gymnastics

To make this pattern look more professional, use TypeScript decorators:

function nocache(target: any, key: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value
  descriptor.value = function(...args: any[]) {
    if(typeof args[0] === 'string') {
      args[0] = `${args[0]}?v=${Date.now()}`
    }
    return original.apply(this, args)
  }
}

class ApiClient {
  @nocache
  fetchData(url: string) {
    return axios.get(url)
  }
}

Defensive Web Workers

Even in Web Workers, stay vigilant:

// worker.js
self.addEventListener('message', (e) => {
  if(e.data.type === 'fetch') {
    const url = `${e.data.url}?worker=${performance.now()}`
    fetch(url).then(response => {
      self.postMessage(response)
    })
  }
})

Service Worker Showdown

Service Workers are meant to manage caching, but we can use them to break it:

// service-worker.js
self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url)
  url.searchParams.append('sw', Date.now())
  event.respondWith(fetch(url))
})

Defensive Local Storage

Even localStorage isn't safe, though it doesn't cache by default:

function setLocalStorage(key, value) {
  const timestamp = Date.now()
  localStorage.setItem(`${key}_${timestamp}`, value)
}

function getLocalStorage(key) {
  // Find the key with the latest timestamp
  const keys = Object.keys(localStorage)
    .filter(k => k.startsWith(key))
    .sort()
  return localStorage.getItem(keys[keys.length - 1])
}

Internationalized Defense

Multilingual sites should follow the same principle:

function getTranslation(key) {
  const lang = navigator.language || 'en-US'
  return fetch(`/i18n/${lang}.json?t=${Date.now()}`)
    .then(res => res.json())
    .then(translations => translations[key])
}

The Art of Image Loading

Image loading best showcases the value of defensive programming:

<img 
  src="placeholder.jpg" 
  data-src="real-image.jpg" 
  onload="this.src=this.dataset.src+'?v='+Date.now()">

WebSocket Defense

Even WebSocket connections need cache protection:

const ws = new WebSocket(`wss://example.com/ws?token=${token}&nocache=${Math.random()}`)

Third-Party Library Defense

When using third-party libraries, ensure they don't secretly cache:

// Override axios request config
axios.interceptors.request.use(config => {
  config.url = config.url.includes('?') 
    ? `${config.url}&_=${performance.now()}`
    : `${config.url}?_=${performance.now()}`
  return config
})

Mobile Apps Aren't Safe Either

React Native developers can play too:

<Image 
  source={{ 
    uri: `https://example.com/image.jpg?rn=${Date.now()}`,
    cache: 'reload'
  }} 
/>

Mini-Program Tricks

In WeChat Mini Programs, ensure no caching like this:

wx.downloadFile({
  url: `https://example.com/file?t=${new Date().getTime()}`,
  header: {
    'Cache-Control': 'no-cache'
  }
})

Build-Time Defense

Prevent caching at build time:

// gulpfile.js
gulp.task('build', () => {
  return gulp.src('src/*.js')
    .pipe(rename(path => {
      path.basename += `-${Date.now()}`
    }))
    .pipe(gulp.dest('dist'))
})

Documentation Defense

Even API docs need cache protection:

Call this endpoint for fresh data:
`GET /api/data?v=${new Date().toISOString()}`

Error Monitoring Defense

Error reporting must ensure fresh requests:

window.onerror = (msg, url, line, col, error) => {
  const reportUrl = new URL('https://error-tracker.com/api')
  reportUrl.searchParams.append('msg', msg)
  reportUrl.searchParams.append('t', Date.now())
  navigator.sendBeacon(reportUrl.toString())
}

Frontend Routing Defense

SPA route changes need cache protection:

router.beforeEach((to, from, next) => {
  if(to.query.v === undefined) {
    next({
      ...to,
      query: {
        ...to.query,
        v: Date.now()
      }
    })
  } else {
    next()
  }
})

GraphQL Defense

Even GraphQL requests (usually POST) need protection:

const query = `
  query GetData($timestamp: String!) {
    data(timestamp: $timestamp) {
      id
      value
    }
  }
`

client.query({
  query,
  variables: {
    timestamp: new Date().toISOString()
  },
  fetchPolicy: 'no-cache'
})

Lazy Loading Defense

Lazy-loaded components must refetch:

const LazyComponent = React.lazy(() => 
  import(`./components/Heavy.js?update=${Date.now()}`)
)

Font Loading Defense

Web fonts need cache protection:

@font-face {
  font-family: 'MyFont';
  src: url('myfont.woff2?v=123') format('woff2');
}

Video Streaming Defense

Video playback needs protection:

<video>
  <source src="video.mp4?start=<%= new Date().getTime() %>" type="video/mp4">
</video>

Preload Defense

Even preload needs cache protection:

<link rel="preload" href="critical.css?v=<%= Math.random() %>" as="style">

The Ultimate Browser Cache Showdown

Finally, to be absolutely sure, add defense in HTML meta tags:

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.