Compare commits

..

1 Commits

Author SHA1 Message Date
bymyself
a9ce45279e fix: use no-store cache headers to prevent stale frontend chunks
After a frontend update (e.g. nightly build), browsers could load
outdated cached index.html and JS/CSS chunks, causing dynamically
imported modules to fail with MIME type errors and vite:preloadError.

Hard refresh (Ctrl+Shift+R) was insufficient to fix the issue because
Cache-Control: no-cache still allows the browser to cache and
revalidate via ETags. aiohttp's FileResponse auto-generates ETags
based on file mtime+size, which may not change after pip reinstall,
so the browser gets 304 Not Modified and serves stale content.

Clearing ALL site data in DevTools did fix it, confirming the HTTP
cache was the root cause.

The fix changes:
- index.html: no-cache -> no-store, must-revalidate
- JS/CSS/JSON entry points: no-cache -> no-store

no-store instructs browsers to never cache these responses, ensuring
every page load fetches the current index.html with correct chunk
references. This is a small tradeoff (~5KB re-download per page load)
for guaranteed correctness after updates.
2026-03-12 17:23:15 -07:00
6 changed files with 14 additions and 14 deletions

View File

@@ -272,7 +272,7 @@ class VideoFromFile(VideoInput):
has_first_frame = False has_first_frame = False
for frame in frames: for frame in frames:
offset_seconds = start_time - frame.pts * audio_stream.time_base offset_seconds = start_time - frame.pts * audio_stream.time_base
to_skip = max(0, int(offset_seconds * audio_stream.sample_rate)) to_skip = int(offset_seconds * audio_stream.sample_rate)
if to_skip < frame.samples: if to_skip < frame.samples:
has_first_frame = True has_first_frame = True
break break
@@ -280,7 +280,7 @@ class VideoFromFile(VideoInput):
audio_frames.append(frame.to_ndarray()[..., to_skip:]) audio_frames.append(frame.to_ndarray()[..., to_skip:])
for frame in frames: for frame in frames:
if self.__duration and frame.time > start_time + self.__duration: if frame.time > start_time + self.__duration:
break break
audio_frames.append(frame.to_ndarray()) # shape: (channels, samples) audio_frames.append(frame.to_ndarray()) # shape: (channels, samples)
if len(audio_frames) > 0: if len(audio_frames) > 0:

View File

@@ -1,3 +1,3 @@
# This file is automatically generated by the build process when version is # This file is automatically generated by the build process when version is
# updated in pyproject.toml. # updated in pyproject.toml.
__version__ = "0.17.0" __version__ = "0.16.4"

View File

@@ -32,7 +32,7 @@ async def cache_control(
) )
if request.path.endswith(".js") or request.path.endswith(".css") or is_entry_point: if request.path.endswith(".js") or request.path.endswith(".css") or is_entry_point:
response.headers.setdefault("Cache-Control", "no-cache") response.headers.setdefault("Cache-Control", "no-store")
return response return response
# Early return for non-image files - no cache headers needed # Early return for non-image files - no cache headers needed

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "ComfyUI" name = "ComfyUI"
version = "0.17.0" version = "0.16.4"
readme = "README.md" readme = "README.md"
license = { file = "LICENSE" } license = { file = "LICENSE" }
requires-python = ">=3.10" requires-python = ">=3.10"

View File

@@ -310,7 +310,7 @@ class PromptServer():
@routes.get("/") @routes.get("/")
async def get_root(request): async def get_root(request):
response = web.FileResponse(os.path.join(self.web_root, "index.html")) response = web.FileResponse(os.path.join(self.web_root, "index.html"))
response.headers['Cache-Control'] = 'no-cache' response.headers['Cache-Control'] = 'no-store, must-revalidate'
response.headers["Pragma"] = "no-cache" response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "0" response.headers["Expires"] = "0"
return response return response

View File

@@ -28,31 +28,31 @@ CACHE_SCENARIOS = [
}, },
# JavaScript/CSS scenarios # JavaScript/CSS scenarios
{ {
"name": "js_no_cache", "name": "js_no_store",
"path": "/script.js", "path": "/script.js",
"status": 200, "status": 200,
"expected_cache": "no-cache", "expected_cache": "no-store",
"should_have_header": True, "should_have_header": True,
}, },
{ {
"name": "css_no_cache", "name": "css_no_store",
"path": "/styles.css", "path": "/styles.css",
"status": 200, "status": 200,
"expected_cache": "no-cache", "expected_cache": "no-store",
"should_have_header": True, "should_have_header": True,
}, },
{ {
"name": "index_json_no_cache", "name": "index_json_no_store",
"path": "/api/index.json", "path": "/api/index.json",
"status": 200, "status": 200,
"expected_cache": "no-cache", "expected_cache": "no-store",
"should_have_header": True, "should_have_header": True,
}, },
{ {
"name": "localized_index_json_no_cache", "name": "localized_index_json_no_store",
"path": "/templates/index.zh.json", "path": "/templates/index.zh.json",
"status": 200, "status": 200,
"expected_cache": "no-cache", "expected_cache": "no-store",
"should_have_header": True, "should_have_header": True,
}, },
# Non-matching files # Non-matching files