Server: Assets werden nachgefragt statt jedes Mal neu geladen

Auf allem stand no-store -- im Server und noch einmal in preloadAssets().
Der Browser durfte also nichts behalten, und jeder Reload lud die vollen
Assets neu: 3 MB Screenshots, 3,3 MB Mockups, Schriften, Bibliotheken.

Am schlimmsten in uebersicht.html. Die laedt die sechs Projekte als
iframes, und weil keiner etwas behalten durfte, holte sich jeder iframe
dieselben Dateien noch einmal: 38,1 MB in 435 Anfragen.

  uebersicht.html erster Aufruf   38,1 MB  ->  2,2 MB
  uebersicht.html Reload          32,8 MB  ->  1,9 MB
  post6.html Reload                5,4 MB  ->  0,3 MB

Die Absicht hinter no-store war richtig: ein Bild, das unter demselben
Namen ausgetauscht wird, muss sofort ankommen. no-cache leistet das
billiger -- der Browser behaelt seine Kopie, fragt aber vor jeder
Benutzung nach. Nachgeprueft: unveraendert antwortet der Server mit 304
und 0 Bytes, nach einem touch mit 200 und der vollen Datei.

Seite, Stand und Auskuenfte (/bilder, /state) bleiben bei no-store. Die
sind klein und duerfen nie aus einer Kopie kommen.

Die eine Zeile stand in allen sieben Projektdateien.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7RZ6ZpLD8Dp84Rtt4KHfv
This commit is contained in:
dejanandClaude Opus 5 committed 2026-08-29 16:31:25 +02:00
1 parent e8f5686f30
commit 77734b6da2
9 files changed
+111 -20

No files matched your search

+27
View File
@@ -534,6 +534,33 @@ serve.py Statischer Server + /save, /state, /upload, /bilder, /anim
rules.md Gestaltungsregeln, Farbwerte, Export-Wissen
```
### Assets werden nachgefragt, nicht neu geladen
Ein Bild, das unter demselben Namen ausgetauscht wird, muss sofort ankommen —
sonst arbeitet man gegen eine alte Kopie. Dafür stand auf allem `no-store`:
im Server und noch einmal in `preloadAssets()`. Der Browser durfte nichts
behalten.
Das Ziel war richtig, der Preis zu hoch. Jeder Reload lud alles neu, und weil
`uebersicht.html` die sechs Projekte als iframes lädt und jeder iframe für
sich vorlud, kam die Seite auf **38 MB in 435 Anfragen**.
`no-cache` leistet dasselbe billiger: der Browser behält seine Kopie, fragt
aber vor jeder Benutzung nach. Eine ausgetauschte Datei kommt genauso sofort
an, eine unveränderte kostet ein 304 ohne Rumpf — und die sechs iframes teilen
sich eine Kopie statt sechs zu ziehen.
| | vorher | jetzt |
|---|---|---|
| `uebersicht.html`, erster Aufruf | 38,1 MB | 2,2 MB |
| `uebersicht.html`, Reload | 32,8 MB | 1,9 MB |
| `post6.html`, Reload | 5,4 MB | 0,3 MB |
Nachgeprüft ist beides: unverändert antwortet der Server mit `304` und
0 Bytes, nach einem `touch` mit `200` und der vollen Datei. **Seite, Stand und
Auskünfte (`/bilder`, `/state`) bleiben bei `no-store`** — die sind klein und
dürfen nie aus einer Kopie kommen.
## Regeln
**`rules.md` ist die Arbeitsgrundlage.** Farbwerte, Geometrie, Typografie,
+8 -2
View File
@@ -1945,7 +1945,13 @@ if (location.search.indexOf("nur=streifen") >= 0)
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
for (const path of [...new Set(paths)]) {
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
uebersicht.html, die sechs Projekte als iframes laedt, waren das
38 MB und 21 Sekunden. */
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) {
if (optional.has(path)) continue;
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
@@ -2511,7 +2517,7 @@ if (location.search.indexOf("nur=streifen") >= 0)
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
async function bildBereitstellen(pfad) {
if (assets[pfad]) return true;
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) return false;
const blob = await res.blob();
assets[pfad] = await new Promise((ok, fail) => {
+8 -2
View File
@@ -3891,7 +3891,13 @@ if (location.search.indexOf("nur=anim") >= 0)
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
for (const path of [...new Set(paths)]) {
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
uebersicht.html, die sechs Projekte als iframes laedt, waren das
38 MB und 21 Sekunden. */
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) {
if (optional.has(path)) continue;
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
@@ -4469,7 +4475,7 @@ if (location.search.indexOf("nur=anim") >= 0)
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
async function bildBereitstellen(pfad) {
if (assets[pfad]) return true;
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) return false;
const blob = await res.blob();
assets[pfad] = await new Promise((ok, fail) => {
+8 -2
View File
@@ -3519,7 +3519,13 @@ if (location.search.indexOf("nur=anim") >= 0)
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
for (const path of [...new Set(paths)]) {
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
uebersicht.html, die sechs Projekte als iframes laedt, waren das
38 MB und 21 Sekunden. */
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) {
if (optional.has(path)) continue;
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
@@ -4090,7 +4096,7 @@ if (location.search.indexOf("nur=anim") >= 0)
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
async function bildBereitstellen(pfad) {
if (assets[pfad]) return true;
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) return false;
const blob = await res.blob();
assets[pfad] = await new Promise((ok, fail) => {
+8 -2
View File
@@ -3191,7 +3191,13 @@ if (location.search.indexOf("nur=anim") >= 0)
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
for (const path of [...new Set(paths)]) {
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
uebersicht.html, die sechs Projekte als iframes laedt, waren das
38 MB und 21 Sekunden. */
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) {
if (optional.has(path)) continue;
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
@@ -3816,7 +3822,7 @@ if (location.search.indexOf("nur=anim") >= 0)
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
async function bildBereitstellen(pfad) {
if (assets[pfad]) return true;
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) return false;
const blob = await res.blob();
assets[pfad] = await new Promise((ok, fail) => {
+8 -2
View File
@@ -4025,7 +4025,13 @@ if (location.search.indexOf("nur=anim") >= 0)
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
for (const path of [...new Set(paths)]) {
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
uebersicht.html, die sechs Projekte als iframes laedt, waren das
38 MB und 21 Sekunden. */
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) {
if (optional.has(path)) continue;
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
@@ -4603,7 +4609,7 @@ if (location.search.indexOf("nur=anim") >= 0)
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
async function bildBereitstellen(pfad) {
if (assets[pfad]) return true;
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) return false;
const blob = await res.blob();
assets[pfad] = await new Promise((ok, fail) => {
+8 -2
View File
@@ -4267,7 +4267,13 @@ if (location.search.indexOf("nur=anim") >= 0)
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
for (const path of [...new Set(paths)]) {
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
uebersicht.html, die sechs Projekte als iframes laedt, waren das
38 MB und 21 Sekunden. */
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) {
if (optional.has(path)) continue;
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
@@ -4845,7 +4851,7 @@ if (location.search.indexOf("nur=anim") >= 0)
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
async function bildBereitstellen(pfad) {
if (assets[pfad]) return true;
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) return false;
const blob = await res.blob();
assets[pfad] = await new Promise((ok, fail) => {
+11 -2
View File
@@ -5001,8 +5001,17 @@ if (location.search.indexOf("nur=anim") >= 0)
// Optional: the layout falls back to a hint if the file is not there yet.
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
/* no-cache und nicht no-store: der Browser darf seine Kopie behalten,
muss aber vor jeder Benutzung nachfragen. Eine ausgetauschte Datei
kommt damit genauso sofort an wie vorher, eine unveraenderte kostet
ein 304 ohne Rumpf statt ihrer vollen Groesse.
Mit no-store lud jeder Reload rund 6 MB neu -- 3 MB Screenshots,
3,3 MB Mockups -- obwohl sich nichts davon geaendert hatte. Gemessen
an 61 Anfragen je Aufruf. Die Auskuenfte (/bilder) und der Stand
bleiben bei no-store: die sind klein und duerfen nie alt sein. */
for (const path of [...new Set(paths)]) {
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) {
if (optional.has(path)) continue;
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
@@ -5580,7 +5589,7 @@ if (location.search.indexOf("nur=anim") >= 0)
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
async function bildBereitstellen(pfad) {
if (assets[pfad]) return true;
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
if (!res || !res.ok) return false;
const blob = await res.blob();
assets[pfad] = await new Promise((ok, fail) => {
+25 -6
View File
@@ -267,12 +267,31 @@ class Handler(http.server.SimpleHTTPRequestHandler):
self.wfile.write(payload)
def end_headers(self):
# Werkzeug, kein Auslieferungsserver: Assets werden hier laufend
# ausgetauscht, und ein gecachter Screenshot unter gleichem Dateinamen
# kostet mehr Zeit als jeder erneute Ladevorgang.
self.send_header("Cache-Control", "no-store, must-revalidate")
self.send_header("Pragma", "no-cache")
self.send_header("Expires", "0")
# Werkzeug, kein Auslieferungsserver: ein Asset, das unter demselben
# Namen ausgetauscht wird, muss sofort ankommen.
#
# Dafuer stand hier no-store auf allem. Das Ziel war richtig, der
# Preis zu hoch: no-store verbietet dem Browser die Kopie ueberhaupt,
# und damit lud jeder Reload die vollen rund 6 MB neu -- 3 MB
# Screenshots, 3,3 MB Mockups, die Schriften, die Bibliotheken. Am
# Ende wartete man beim Neuladen auf Dateien, die sich seit Wochen
# nicht geaendert hatten.
#
# no-cache leistet dasselbe billiger: der Browser darf die Kopie
# behalten, muss aber vor jeder Benutzung nachfragen. Eine
# ausgetauschte Datei kommt damit genauso sofort an, eine
# unveraenderte kostet ein 304 ohne Rumpf. Geprueft: der Handler
# beantwortet If-Modified-Since bereits mit 304 und 0 Bytes.
#
# Seite, Stand und Auskuenfte bleiben bei no-store. Die sind klein,
# und sie duerfen nie aus einer Kopie kommen.
pfad = urllib.parse.urlparse(self.path).path
if pfad.startswith(("/assets/", "/vendor/", "/animation/")):
self.send_header("Cache-Control", "no-cache")
else:
self.send_header("Cache-Control", "no-store, must-revalidate")
self.send_header("Pragma", "no-cache")
self.send_header("Expires", "0")
super().end_headers()
def log_message(self, fmt, *args):