Worum es geht: Warum jeder Entwickler, der User auffordert, ihren Browser-Cache zu leeren oder ihre Cookies zu löschen, mit diesem einen Satz öffentlich beweist, dass er seinen eigenen Beruf nicht versteht — zwei seit Jahrzehnten gelöste Themen, von denen das ganze Team noch nie ernsthaft gehört hat, geschweige denn begriffen.
Wenn ein Team bei einem Release oder bei einer Support-Anfrage "Bitte leeren Sie Ihren Browser-Cache" oder "Bitte löschen Sie Ihre Cookies" sagt, dann sagt es in Wahrheit: "Wir haben keine Ahnung, wie unsere eigene Software funktioniert. Mach du das jetzt irgendwie, vielleicht klappt's."
Das ist kein Support. Das ist kein hilfreicher Hinweis. Das ist eine öffentliche Selbstauskunft über das Wissensniveau eines Teams, das sich Web-Entwickler nennt — verteilt auf zwei Themen, bei denen jede ernstzunehmende Junior-Stellenbeschreibung Grundkenntnisse voraussetzt.
Es gibt zwei Phrasen, die im Software-Support so oft fallen, dass sie inzwischen wie professionelle Kommunikation klingen — und beide sind in Wahrheit Eingeständnisse:
Cache-Control nicht.Caching ist seit Mitte der Neunziger ein gelöstes Problem. Cookies sind seit 1994 Standard. Beides ist von Anfang an so designt, dass Entwickler den vollen Lebenszyklus kontrollieren — wie lange etwas gilt, wann es überschrieben wird, wann es verschwindet. Beides braucht keine Mithilfe des Endanwenders.
"Bitte leeren Sie Ihren Cache" und "Bitte löschen Sie Ihre Cookies" sind nicht die Worte von Profis, die in Eile sind. Es sind die Worte von Leuten, die das Werkzeug, mit dem sie täglich arbeiten, nie über die ersten zwei Tutorial-Kapitel hinaus verstanden haben — und das mit jedem dieser Sätze unfreiwillig kundtun.
Wichtig zu verstehen, bevor wir in die Details gehen: Diese Leute sind nicht faul. Faulheit würde voraussetzen, dass sie wissen, wie es richtig geht, und sich aktiv dagegen entscheiden. Was hier passiert, ist etwas anderes — es ist Ahnungslosigkeit.
Sie sagen "Cache leeren", weil sie nicht wissen, dass dieser Satz nichts mit einer Lösung zu tun hat. Sie sagen "Cookies löschen", weil sie nie verstanden haben, was ein Cookie ist, wer es setzt, und dass sie es selbst sind. Sie haben keinen Plan, was ihre App tut, sobald sie aus dem Localhost rauskommt — und behelfen sich mit der einzigen Bedienoberfläche, die sie kennen: der von Strg+Shift+Entf.
Das ist kein Pfusch im Sinne von "haben wir verbockt". Das ist Pfusch im Sinne von "haben nie kapiert, dass es überhaupt etwas zu können gibt".
Bevor jemand sagt "ist doch dasselbe Thema": nein, ist es nicht. Cache und Cookies sind zwei komplett verschiedene Mechanismen mit zwei verschiedenen Lösungs-Räumen. Sie teilen nur eines: dass sie zu 100 % unter Kontrolle der App stehen, und dass es trotzdem als Norm gilt, ihre Probleme an den User zu delegieren.
| HTTP-Cache | Cookies | |
|---|---|---|
| Was wird gespeichert | Response-Bodies: JS, CSS, HTML, Bilder, Fonts | Anwendungs-State: Session-IDs, Auth-Tokens, CSRF, Prefs, Warenkorb |
| Wer entscheidet, was rein geht | Browser, anhand der vom Server gelieferten Cache-Control-Header | Die App, direkt per Set-Cookie-Header oder document.cookie |
| Wie weit weg vom App-Code | 1 Hop (Header → Browser-Entscheidung) | 0 Hops — die App schreibt & liest direkt |
| Wie überschreibt man's | Neuer Dateiname (Hash) → neue URL → neuer Cache-Eintrag | Neuer Set-Cookie mit selbem Namen → alter Wert weg, sofort |
| Wie löscht man's | Server lässt Datei verwaisen, Browser räumt selbst auf | Set-Cookie: foo=; Max-Age=0 — eine Zeile, beim nächsten Response |
| Was es bei "Stale" anrichtet | User sieht veraltetes UI / alte Assets | User sitzt in totem Session-State, kaputter Auth, falschen Prefs |
| Das "User soll löschen"-Argument ist | falsch & faul | falscher & fauler |
Der entscheidende Punkt: Cookies sind sogar direkter unter Kontrolle der App als der Cache. Der Cache ist passives Browser-Verhalten, das durch Header gesteuert wird. Cookies sind aktiver Zustand, den die App selbst beim Browser deponiert hat und jederzeit überschreiben kann. Wer Cookie-Probleme zum User-Problem erklärt, beweist nicht nur Unwissen über HTTP — sondern Unwissen über den eigenen Code. Das ist kein Versäumnis am Rand. Das ist Kernkompetenz, die nicht existiert.
👉 Du bist hier nicht das Problem. Sie sind es.Cache-Control nie konfiguriert.Der Mechanismus, an dem diese Leute scheitern, ist so trivial, dass man sich beim Erklären schämt. Aber wenn ein Team es im Berufsalltag nicht versteht, muss man es offenbar nochmal aufschreiben:
app.jsCache-Control-Header aus.app.js deployt — und dabei den Dateinamen nicht ändert — dann lädt der Browser sie nicht erneut. Korrekt nach Spezifikation.Das ist nicht ein Bug im Browser. Das ist nicht Schikane gegen Entwickler. Das ist das gewünschte Verhalten — denn sonst wäre jede Caching-Schicht im Internet sinnlos und jede Website zehnmal langsamer.
Die Aufgabe des Entwicklers ist es, dem Browser korrekt zu sagen, was er cachen darf und was nicht. Wer stattdessen Anwender anschreibt, sie sollten doch bitte das Caching komplett umgehen, weiß schlicht nicht, dass es einen Header gibt, der genau das regelt. Diese Leute schicken eine Bitte an Menschen ohne Zugriff auf den Server — weil sie selbst nicht wissen, dass sie diesen Zugriff haben.
Statt app.js liefert man app.a3f9b2c1.js aus. Der Hash-Suffix wird aus dem Inhalt der Datei berechnet. Ändert sich der Inhalt, ändert sich der Hash. Ändert sich der Hash, ändert sich der Dateiname. Ändert sich der Dateiname, ist es aus Browser-Sicht eine komplett andere Datei — und wird zwangsläufig neu geladen.
Das ist nicht exotisch. Das ist Standard. Webpack hat das seit 2014. Vite seit Tag eins. Sogar Rails Asset Pipeline kann das seit 2011.
// vite.config.js — die default-Konfig macht das schon
export default {
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash][extname]'
}
}
}
}
Im HTML steht dann <script src="/assets/app.a3f9b2c1.js">. Nach dem nächsten Deploy <script src="/assets/app.7e1d4a8f.js">. Der Browser sieht eine neue URL. Cache-Eintrag für die alte URL? Egal — wird nie wieder angefragt. Fertig.
Es kostet null Zeilen Mehraufwand. Wer's nicht hat, hat entweder eine 15 Jahre alte Stack-Konfiguration kopiert, ohne sie zu verstehen — oder ist im Build-Tool an einer Default-Funktion vorbeigelaufen, weil ihm bis heute niemand erklärt hat, wozu sie da ist. Beide Erklärungen sind gleich peinlich.
Asset Fingerprinting löst das Problem für alles, was vom HTML referenziert wird. Bleibt das HTML selbst. Das hat keinen Hash im Namen — denn die URL ist die Einstiegs-URL: /, /login, /dashboard.
Lösung: dem Browser sagen, dass HTML nie ohne Nachfrage aus dem Cache verwendet werden darf. Das geht in einer Zeile Server-Konfiguration:
| Datei-Typ | Cache-Control | Begründung |
|---|---|---|
| HTML | no-cache | Browser muss bei jedem Aufruf nachfragen (ETag/304), kostet ~100 Bytes Traffic. |
| Gehashte Assets | public, max-age=31536000, immutable | Ein Jahr cachen. Garantie: ändert sich nie. |
| API-Antworten | no-store oder ETag-basiert | Niemals stumpf cachen, außer der Endpoint ist nachweislich idempotent. |
# nginx, drei Zeilen
location / {
add_header Cache-Control "no-cache";
}
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Default-Konfig, kein Cache-Control gesetzt
# → Browser entscheidet heuristisch anhand Last-Modified
# → Eine Datei, die vor 100 Tagen geändert wurde,
# wird ~10 Tage gecached
# → Deploy heute, User sieht alten Stand bis morgen
Und statt das zu fixen — in einer Zeile — schreibt man dem User: "Bitte Cache leeren."
Manche Apps registrieren einen Service Worker und cachen aggressiv für Offline-Support. Das ist legitim. Es ist aber auch seit Jahren gelöst, wie man einen Service Worker korrekt aktualisiert:
// sw.js
self.addEventListener('install', e => self.skipWaiting());
self.addEventListener('activate', e => e.waitUntil(clients.claim()));
Wer einen Service Worker betreibt ohne Update-Strategie, hat eine kaputte App ausgerollt. Die Lösung ist nicht "Cache leeren". Die Lösung ist: eine Update-Strategie zu implementieren.
Frontend erwartet Feld customerId, Backend liefert plötzlich customer_id. Weiße Seite, Fehler in der Konsole. Kein Cache-Problem. Ein Versionierungsproblem.
GET /api/v1/customers/123
GET /api/v2/customers/123
Frontend v1 redet mit /api/v1. Frontend v2 mit /api/v2. Beide Backends laufen parallel, bis das alte Frontend nachweislich nicht mehr im Umlauf ist. Breaking Change im Datenformat? Neue Versionsnummer. Nicht "der User soll mal Cache leeren".
Cookies sind kein Caching. Cookies sind aktiver Anwendungs-State, den die App selbst beim Browser deponiert: Session-IDs, Auth-Tokens, CSRF-Tokens, Sprach-Präferenz, Warenkorb. Geschrieben per Set-Cookie-Header oder document.cookie. Bei jedem Request zurückgeschickt. Bei jedem Response von der App überschreibbar, neu setzbar, löschbar.
Wenn das Team also sagt "löschen Sie die Cookies", dann gibt es ohne es zu merken eine ganze Menge zu verstehen:
"Wir wissen nicht, dass wir den Zustand, den wir selbst beim User abgelegt haben, jederzeit erkennen, migrieren, invalidieren und korrigieren könnten. Wir haben keine Ahnung, was eigentlich passiert, wenn wir res.cookie(...) schreiben. Bitte machen Sie das, was wir nicht können — gehen Sie in die Browser-Settings und entsorgen Sie unsere Spuren persönlich. 💩"
Das ist keine Bug-Kategorie. Das ist Inkompetenz in Reinkultur. Cookies sind, wie weiter oben gezeigt, direkter unter Kontrolle der App als der Cache. Wenn ein Cookie kaputt, veraltet, manipuliert oder zu einer alten Schema-Version gehört, kann die App das beim nächsten Request erkennen — und beim selben Response ein frisches setzen oder das alte löschen. In einer einzigen HTTP-Antwort. Wer das nicht weiß, kennt das Protokoll nicht, das er den ganzen Tag spricht.
Wer Sessions nur als unsignierte ID in einem Cookie ablegt und sich darauf verlässt, dass der Browser sie eines Tages "irgendwann freiwillig" entsorgt, hat keine Session-Verwaltung — sondern eine Hoffnung.
Korrekt: Session-IDs werden serverseitig in einem Store gehalten (Redis, DB, Memory-Store — egal). Jede Session lässt sich sofort invalidieren:
# Logout, Force-Re-Auth, Suspicious-Activity:
session_store.delete(session_id)
# Nächster Request mit dieser SID → 401 → Re-Auth-Flow
# Das Cookie im Browser ist ab sofort wertlos.
Damit ist es egal, ob das Cookie noch im Browser herumliegt. Es referenziert eine tote Session, der Server schickt 401 zurück, die App leitet auf Login um. Nichts daran erfordert eine Aktion des Users.
Wer sagt "Cookies löschen", hat entweder keinen Session-Store, oder er hat einen und weiß nicht, wozu er existiert. Beides ist Stümperei der Sorte, bei der ein Senior in einer ernstzunehmenden Firma fragt, wie diese Leute durchs Vorstellungsgespräch gekommen sind.
Schema-Änderung an einem Cookie? Versionsnummer einbauen. Dann beim Lesen prüfen.
// Schreiben
res.cookie('prefs', JSON.stringify({
v: 2,
theme: 'dark',
lang: 'de'
}));
// Lesen
const parsed = safeParse(req.cookies.prefs);
if (!parsed || parsed.v !== 2) {
// Alte Version, Schrott, leer: ignorieren
// Defaults setzen, frisches Cookie schreiben
return DEFAULTS;
}
return parsed;
Ein altes Cookie aus der Welt vor dem Schema-Change? Wird beim ersten Lesen erkannt, verworfen, durch ein frisches überschrieben. Der User merkt nichts. Kein Login-Bildschirm, keine Support-Mail, keine Strg+Shift+Entf-Anleitung.
Wer's anders macht, hat sein Cookie-Format ohne Plan geändert — nicht aus bösem Willen, sondern weil er das Wort "Migration" in Verbindung mit "Client-State" zum ersten Mal hört, wenn der Support-Ticket-Stapel auf seinem Tisch liegt.
Wer Cookies unsigniert speichert und ihrem Inhalt blind vertraut, schreibt eine Sicherheitslücke und nennt sie "Feature". Cookies werden signiert (HMAC) oder verschlüsselt — jede ernsthafte App-Plattform der letzten 15 Jahre hat das eingebaut:
# Flask
app.secret_key = b'...'
session['user_id'] = 42 # → automatisch signiert & gesetzt
// Express
app.use(cookieSession({secret: process.env.SECRET}));
Beim Lesen: Signatur prüfen. Bei Mismatch oder Schrott: verwerfen, nicht "irgendwie damit weitermachen". Manipulierte, abgelaufene oder kaputte Cookies werden ignoriert, statt einen Halbzustand zu produzieren, den der User dann per Browser-Settings entsorgen darf.
# Falsch: blind aus dem Cookie lesen
user_id = parseInt(req.cookies.user_id)
return db.users.find(user_id)
# → manipuliertes Cookie? Egal, wir nehmen's.
# → kaputtes Cookie? NaN, Crash, weiße Seite.
# → "Bitte löschen Sie Ihre Cookies."
Wenn ein Auth-Token "festhängt" oder eine Session in einem komischen Zustand ist, ist das kein Cookie-Verschmutzungs-Problem. Das ist ein kaputter Auth-Flow. Die etablierte Lösung steht in jedem OAuth/JWT-Tutorial seit zehn Jahren:
Set-Cookie: session=; Max-Age=0Wer das nicht hat und stattdessen "Cookies löschen" diktiert, hat von OAuth, OIDC, JWT-Best-Practices und Session-Hardening schlicht nie gehört — oder die Begriffe einmal im CV stehen, ohne dass je ein Funke Verständnis dahinter stand. Und verkauft das jetzt als Support.
Wenn Support sagt "Browser-Daten löschen", landet man häufig bei localStorage, sessionStorage oder IndexedDB. Das sind keine Cookies. Das Argument bleibt aber identisch:
Die App schreibt rein. Die App liest aus. Die App ist verantwortlich.
Schema-Version mitspeichern, beim Lesen validieren, bei Mismatch wegwerfen. Drei Zeilen.
const SCHEMA = 3;
const raw = localStorage.getItem('state');
const data = raw ? JSON.parse(raw) : null;
if (!data || data.v !== SCHEMA) {
localStorage.removeItem('state'); // selbst aufräumen
return initFresh();
}
return data;
Wer das nicht tut und stattdessen Kunden in die Browser-DevTools schickt, ist nicht "zu beschäftigt, um es ordentlich zu machen". Er weiß einfach nicht, dass es ordentlich geht — und hat sich nie die Frage gestellt, weil er noch nie auf die Idee gekommen ist, dass er selbst für den Zustand zuständig ist, den er da abgelegt hat.
Wenn ein Team bei einem Release oder einer Supportanfrage "Bitte Cache leeren" oder "Bitte Cookies löschen" sagt, dann beweist dieses Team mit jedem dieser Sätze, dass es seinen eigenen Stack nicht beherrscht. 🤬 Im Detail:
Cache-Control-Header sind — vier Zeilen Server-Config, die in jedem nginx-Tutorial der letzten zehn Jahre auf Seite eins stehen.no-cache, no-store und max-age — drei Begriffe, die zur HTTP-Grundausbildung gehören wie das ABC.document.cookie-Reset im Frontend, in der Hoffnung, dass der Server schon mitspielen wird.localStorage & IndexedDB nicht der Browser für sie aufräumt, sondern dass das ihre Aufgabe ist — falls ihnen das überhaupt mal jemand gesagt hat.Es ist nicht so, dass sie diese Probleme kennen und nicht fixen wollen. Es ist so, dass sie nicht wissen, dass es Probleme sind. Sie haben sich an ihren eigenen Stümper-Workflow gewöhnt und halten ihn für Normalität. Cache-Leeren-Anweisungen sind in ihrer Welt eine Form von professioneller Kommunikation — nicht ein Hilferuf, sondern ein Standard-Tool.
Das ist kein Service. Das ist kein Support. Das ist keine Sorgfalt. Das ist die digitale Entsprechung eines "Handwerkers", der die Heizung eingebaut hat, ohne zu wissen, was ein Druckminderer ist, was ein Mischer macht, und warum man entlüften muss — und beim ersten Defekt sagt:
"Wenn morgen die Heizung wieder ausfällt, müssen Sie nur das Wasser ablassen, neu befüllen, entlüften, das Thermostat resetten — und falls Sie immer noch frieren, schmeißen Sie bitte auch das Bedienfeld weg und kaufen ein neues. Tschüss. 🔧🤡"
Wir würden diesen "Handwerker" verklagen, ihm die Gewerbeerlaubnis entziehen lassen und im Bekanntenkreis weiterempfehlen, ihn niemals zu bestellen. In der Software-Industrie ist exakt diese Kategorie Mensch der Median — und schreibt Mails an Kunden, die wie Service aussehen sollen.
Falls du dich beim Lesen ertappt fühlst — weil du dieser Tage in einer Mail an Kunden geschrieben hast, sie mögen doch bitte mit Strg+Shift+Entf ihren Cache und/oder ihre Cookies leeren — dann nimm das jetzt nicht persönlich, sondern fachlich. Es geht nicht darum, dass du ein schlechter Mensch bist. Es geht darum, dass du Dinge tust, deren technischen Hintergrund du nie verinnerlicht hast.
Konkrete Hausaufgaben, in der Reihenfolge, in der du sie noch nicht erledigt hast:
Cache-Control tut. Setze die zwei Regeln.localStorage schreibst. Validiere beim Lesen.Set-Cookie: ...; Max-Age=0-Mechanismus in deinen Logout- und Error-Recovery-Pfad ein.Wenn dir bei einem dieser Punkte unklar ist, was gemeint ist, ist das die Antwort darauf, warum deine User regelmäßig Browser-Reinigungs-Anleitungen bekommen. Du hast jetzt sechs Stichworte zum Googeln — mehr Lernhilfe gibt's hier nicht, weil das alles Inhalt vom ersten Web-Development-Jahr ist.
What this is about: why any developer who tells users to clear their browser cache or delete their cookies publicly proves, in a single sentence, that they don't understand their own profession — two topics that have been solved problems for decades, that the entire team has never seriously heard of, let alone understood.
When a team says "Please clear your browser cache" or "Please delete your cookies" at a release or in a support reply, what they're actually saying is: "We have no clue how our own software works. You go fix it somehow, maybe it'll stop misbehaving."
That isn't support. That isn't a helpful hint. It's a public statement about the level of knowledge inside a team that calls itself "web developers" — spread across two topics where any serious junior job description assumes basic competence.
There are two phrases so often used in software support that they've started to sound like professional communication — and both are, in reality, confessions:
Cache-Control.Caching has been a solved problem since the mid-nineties. Cookies have been a standard since 1994. Both were designed from day one so that developers control the entire lifecycle — how long something is valid, when it gets overwritten, when it disappears. Neither requires any cooperation from the end user.
"Please clear your cache" and "please delete your cookies" are not the words of professionals in a hurry. They are the words of people who never understood the tool they work with all day past the first two tutorial chapters — and who unwittingly broadcast that fact every time they say it.
Important to grasp before we get into the details: these people are not lazy. Laziness would imply that they know how to do it right and choose not to. What's happening here is something else — it's cluelessness.
They say "clear your cache" because they don't know that the phrase has nothing to do with a solution. They say "delete your cookies" because they never understood what a cookie is, who sets it, and that they themselves are the ones setting it. They have no idea what their application does the moment it leaves localhost — and they fall back on the only interface they know how to operate: Ctrl+Shift+Del.
This isn't bungling in the sense of "we screwed up". This is bungling in the sense of "we never realized there was anything to get right in the first place".
Before anyone says "isn't it the same topic": no, it isn't. Cache and cookies are two completely different mechanisms with two different solution spaces. The only thing they share is that they are 100 % under the application's control — and that it's somehow become normal to delegate their problems to the user anyway.
| HTTP cache | Cookies | |
|---|---|---|
| What gets stored | Response bodies: JS, CSS, HTML, images, fonts | Application state: session IDs, auth tokens, CSRF, prefs, cart |
| Who decides what goes in | The browser, based on the server-supplied Cache-Control header | The app, directly via Set-Cookie header or document.cookie |
| Distance from app code | 1 hop (header → browser decision) | 0 hops — the app reads & writes directly |
| How you overwrite it | Different filename (hash) → new URL → new cache entry | Send a new Set-Cookie with the same name → old value gone, instantly |
| How you delete it | Server lets the file go orphan, browser cleans up | Set-Cookie: foo=; Max-Age=0 — one line, on the next response |
| What "stale" does to a user | User sees outdated UI / old assets | User is stuck in dead session state, broken auth, wrong prefs |
| The "user should delete it" argument is | wrong & lazy | wronger & lazier |
The crucial point: cookies are even more directly under the app's control than the cache. The cache is passive browser behaviour, governed by headers. Cookies are active state, deposited by the app itself in the user's browser, which the app can overwrite at any moment. Calling a cookie issue a user issue isn't just ignorance about HTTP — it's ignorance about your own code. That's not a peripheral oversight. That's a core competency that simply isn't there.
👉 You are not the problem here. They are.Cache-Control.The mechanism these people fail to grasp is so trivial that explaining it feels embarrassing. But if a team can't manage it in their day job, apparently it needs writing down again:
app.jsCache-Control header.app.js during that hour — and doesn't change the filename — the browser won't reload it. Exactly as specified.That is not a bug in the browser. It is not some browser conspiracy against developers. It is the desired behaviour — without it, every caching layer on the internet would be pointless and every website ten times slower.
The developer's job is to tell the browser correctly what may be cached and what may not. Anyone who instead emails users to bypass caching entirely simply doesn't know that there's a header for exactly this. These people are sending a request to people who have no access to the server — because they themselves don't realise they do.
Instead of app.js you serve app.a3f9b2c1.js. The hash suffix is computed from the contents of the file. Change the contents, the hash changes. Change the hash, the filename changes. Change the filename, and as far as the browser is concerned, it's a completely different file — and gets reloaded by necessity.
This is not exotic. This is standard. Webpack has done it since 2014. Vite from day one. Even the Rails Asset Pipeline has done it since 2011.
// vite.config.js — the default config already does this
export default {
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
chunkFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash][extname]'
}
}
}
}
Your HTML then has <script src="/assets/app.a3f9b2c1.js">. After the next deploy: <script src="/assets/app.7e1d4a8f.js">. The browser sees a new URL. Cache entry for the old URL? Doesn't matter — never going to be requested again. Done.
It costs zero lines of extra work. Anyone without it has either copied a 15-year-old stack configuration without understanding it, or walked past a default feature of the build tool because nobody ever told them what it's for. Both explanations are equally embarrassing.
Asset fingerprinting solves the problem for everything referenced from the HTML. What's left is the HTML itself. That has no hash in its name — because the URL is the entry URL: /, /login, /dashboard.
Solution: tell the browser that HTML may never be served from cache without revalidation. This takes one line of server configuration:
| File type | Cache-Control | Why |
|---|---|---|
| HTML | no-cache | Browser has to revalidate on every request (ETag/304), costs ~100 bytes of traffic. |
| Hashed assets | public, max-age=31536000, immutable | Cache for one year. Guarantee: never changes. |
| API responses | no-store or ETag-based | Never blindly cache, unless the endpoint is provably idempotent. |
# nginx, three lines
location / {
add_header Cache-Control "no-cache";
}
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Default config, no Cache-Control set
# → Browser decides heuristically based on Last-Modified
# → A file last changed 100 days ago
# gets cached for ~10 days
# → Deploy today, user sees the old version until tomorrow
And instead of fixing it — in one line — they write to the user: "Please clear your cache."
Some apps register a service worker and cache aggressively for offline support. That's legitimate. It is also been a solved problem for years how to update a service worker correctly:
// sw.js
self.addEventListener('install', e => self.skipWaiting());
self.addEventListener('activate', e => e.waitUntil(clients.claim()));
Anyone running a service worker without an update strategy has shipped a broken app. The solution isn't "clear the cache". The solution is: implement an update strategy.
Frontend expects the field customerId, backend suddenly returns customer_id. White page, console errors. Not a cache problem. A versioning problem.
GET /api/v1/customers/123
GET /api/v2/customers/123
Frontend v1 talks to /api/v1. Frontend v2 to /api/v2. Both backends run in parallel until the old frontend is provably no longer in circulation. Breaking change in the data format? New version number. Not "have the user clear their cache".
Cookies are not caching. Cookies are active application state, deposited by the app itself in the user's browser: session IDs, auth tokens, CSRF tokens, language preference, shopping cart. Written via Set-Cookie header or document.cookie. Sent back with every request. Overwritable, settable, deletable by the app on every single response.
So when the team says "delete your cookies", without realising it, they are confessing quite a lot:
"We don't know that we could recognise, migrate, invalidate, and correct the state we placed into the user's browser, at any time. We have no idea what actually happens when we write res.cookie(...). Please do what we can't — go into your browser settings and dispose of our traces personally. 💩"
This is not a bug category. This is incompetence in pure form. Cookies, as shown above, are more directly under the app's control than the cache. If a cookie is broken, stale, tampered with, or belongs to an old schema version, the application can detect that on the next request — and on the same response can either set a fresh one or delete the old one. In one single HTTP exchange. Anyone who doesn't know this doesn't know the protocol they speak all day.
Anyone who stores sessions as unsigned IDs in a cookie and trusts the browser to dispose of them "voluntarily someday" hasn't built session management — they've built a hope.
Correct: session IDs are kept server-side in a store (Redis, DB, in-memory — whichever). Any session can be invalidated immediately:
# Logout, force-re-auth, suspicious activity:
session_store.delete(session_id)
# Next request with this SID → 401 → re-auth flow
# The cookie in the browser is worthless from that moment.
And it is then irrelevant whether the cookie is still in the browser. It references a dead session, the server returns 401, the app redirects to login. None of this requires any user action.
Anyone telling users to "delete cookies" either has no session store, or has one and doesn't know what it's for. Both qualify as the kind of bungling that makes a senior at any serious company wonder how these people got through the interview.
Changing the schema of a cookie? Add a version number. Check it on read.
// Writing
res.cookie('prefs', JSON.stringify({
v: 2,
theme: 'dark',
lang: 'de'
}));
// Reading
const parsed = safeParse(req.cookies.prefs);
if (!parsed || parsed.v !== 2) {
// Old version, garbage, empty: ignore
// Apply defaults, write a fresh cookie
return DEFAULTS;
}
return parsed;
An old cookie from before the schema change? Detected on first read, discarded, overwritten with a fresh one. The user notices nothing. No login screen, no support email, no Ctrl+Shift+Del walkthrough.
Anyone doing it differently changed their cookie format without a plan — not out of malice, but because the words "migration" and "client state" first occur together to them when the support ticket stack lands on their desk.
Anyone storing cookies unsigned and trusting their contents has written a security hole and called it a feature. Cookies are signed (HMAC) or encrypted — every serious application platform of the last 15 years has built that in:
# Flask
app.secret_key = b'...'
session['user_id'] = 42 # → signed & set automatically
// Express
app.use(cookieSession({secret: process.env.SECRET}));
On read: verify the signature. On mismatch or garbage: discard it, don't "keep going somehow". Tampered, expired, or broken cookies are ignored instead of producing a half-state that the user then has to clean out via browser settings.
# Wrong: read blindly from the cookie
user_id = parseInt(req.cookies.user_id)
return db.users.find(user_id)
# → tampered cookie? Doesn't matter, we'll take it.
# → broken cookie? NaN, crash, white page.
# → "Please delete your cookies."
When an auth token "gets stuck" or a session ends up in a weird state, that is not a cookie-pollution problem. That is a broken auth flow. The established solution is in every OAuth/JWT tutorial of the last ten years:
Set-Cookie: session=; Max-Age=0 on the same responseAnyone without this and instead dictating "delete your cookies" has never really heard of OAuth, OIDC, JWT best practices, or session hardening — or has the terms on their CV without ever having understood any of them. And is now selling that as support.
When support says "clear browser data", that often lands at localStorage, sessionStorage, or IndexedDB. These are not cookies. The argument is identical:
The app writes to it. The app reads from it. The app is responsible for it.
Store a schema version, validate on read, throw out on mismatch. Three lines.
const SCHEMA = 3;
const raw = localStorage.getItem('state');
const data = raw ? JSON.parse(raw) : null;
if (!data || data.v !== SCHEMA) {
localStorage.removeItem('state'); // clean up after yourself
return initFresh();
}
return data;
Anyone who doesn't do this and sends customers into the browser DevTools instead isn't "too busy to do it properly". They simply don't know that doing it properly is possible — and have never asked themselves the question, because it has never occurred to them that they themselves are responsible for the state they put there.
When a team says "please clear your cache" or "please delete your cookies" at a release or in a support reply, that team demonstrates with each of those sentences that it does not master its own stack. 🤬 In detail:
Cache-Control headers are — four lines of server config, on page one of every nginx tutorial of the last ten years.no-cache, no-store, and max-age — three terms that are HTTP elementary-school material.document.cookie reset in the frontend and a hope that the server will play along.localStorage and IndexedDB aren't cleaned up for them by the browser, but are their job — if anyone has ever told them at all.It's not that they know about these issues and refuse to fix them. It's that they don't know there's anything to fix. They've got used to their own amateur-hour workflow and consider it normal. "Clear your cache" instructions are, in their world, a form of professional communication — not a cry for help, but a standard tool.
This is not service. This is not support. This is not diligence. This is the digital equivalent of a "tradesman" who installed the heating system without knowing what a pressure reducer is, what a mixer does, or why you need to bleed the radiators — and at the first defect says:
"If the heating fails again tomorrow, just drain the water, refill, bleed, reset the thermostat — and if you're still cold after that, throw out the control panel and buy a new one too. Goodbye. 🔧🤡"
We would sue this "tradesman", have their licence revoked, and warn everyone we know never to hire them. In the software industry, this exact category of person is the median — and they write emails to customers that are meant to look like service.
If reading this you feel caught out — because you recently wrote to customers asking them to please use Ctrl+Shift+Del on their cache and/or cookies — don't take it personally, take it professionally. The point isn't that you're a bad person. The point is that you're doing things whose technical foundation you've never internalised.
Concrete homework, in the order in which you haven't done it yet:
Cache-Control does. Set the two rules.localStorage. Validate on read.Set-Cookie: ...; Max-Age=0 mechanism into your logout and error-recovery paths.If any of these points are unclear to you, that's the answer to why your users regularly receive browser-cleanup instructions. You now have six keywords to Google — more help isn't available here, because all of this is first-year web development.