Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions backend/src/ref_backend/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@ class SPAStaticFiles(StaticFiles):

async def get_response(self, path: str, scope: Scope) -> Response:
try:
return await super().get_response(path, scope)
response = await super().get_response(path, scope)
except HTTPException:
return await super().get_response(".", scope)
response = await super().get_response(".", scope)

# The HTML names hashed asset files that the next deploy removes, so it must be revalidated.
if response.headers.get("content-type", "").startswith("text/html"):
response.headers["Cache-Control"] = "no-cache"

return response


def build_app(settings: Settings, ref_config: Config, database: Database) -> FastAPI:
Expand Down
1 change: 1 addition & 0 deletions changelog/88.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reloads the page when a chunk fails to load, so a tab left open across a deployment recovers instead of showing an error.
35 changes: 35 additions & 0 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,41 @@ import { routeTree } from "@/routeTree.gen";

import "./styles/global.css";

// A deploy replaces the hashed asset files, so a tab left open asks for chunks that are gone.
// Reload once to pick up the new build, rate limited so a persistent failure cannot loop.
const RELOAD_KEY = "chunk-reload-at";
const RELOAD_COOLDOWN_MS = 30_000;

// Storage access throws when the browser blocks site data, so both calls are guarded.
const readReloadedAt = (): number => {
try {
return Number(sessionStorage.getItem(RELOAD_KEY) ?? 0);
} catch {
return 0;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
};

const markReloaded = (): boolean => {
try {
sessionStorage.setItem(RELOAD_KEY, String(Date.now()));
return true;
} catch {
return false;
}
};

window.addEventListener("vite:preloadError", (event) => {
if (Date.now() - readReloadedAt() < RELOAD_COOLDOWN_MS) {
return;
}
// Without a recorded timestamp there is no cooldown, so let the error surface instead of looping.
if (!markReloaded()) {
return;
}
event.preventDefault();
window.location.reload();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

client.setConfig({ baseUrl: getStoredApiEndpoint() });
const queryClient = new QueryClient();

Expand Down
Loading