When a new version is deployed, open tabs no longer silently run stale code until a manual F5. - Server stamps every response with X-App-Version (src/middleware/version.ts, onSend hook in server.ts). - Client bakes its build version via Vite define (__APP_VERSION__); apiFetch compares the header to it (no polling — piggybacks existing traffic) and latches a mismatch in a small store (appVersion.ts). - UpdateBanner: a persistent bottom Snackbar "Je k dispozici nová verze aplikace. — Obnovit" → location.reload(). User-initiated, so an in-progress form is never interrupted. - lazyWithReload: every React.lazy page reloads once if its hashed chunk 404s after a deploy (sessionStorage-guarded against loops), fixing the "old chunk gone" error screen. Approach chosen after researching current SPA practice (version header + banner beats polling / a service worker for a non-PWA admin app). Tests: +4 (server header, client store latch/notify). Full suite 669 pass, tsc -b clean, lint 0. Verified live via Playwright (header emitted; banner appears on a simulated mismatch). Detection applies to deploys from the NEXT release onward (current clients lack the detection code). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
44 lines
1.1 KiB
TypeScript
44 lines
1.1 KiB
TypeScript
import { defineConfig } from "vite";
|
|
import react from "@vitejs/plugin-react";
|
|
import { readFileSync } from "node:fs";
|
|
|
|
// Bake the package.json version into the bundle so the running SPA can compare
|
|
// it to the server's X-App-Version header and detect a new deploy.
|
|
const appVersion = (
|
|
JSON.parse(readFileSync("./package.json", "utf8")) as { version: string }
|
|
).version;
|
|
|
|
export default defineConfig({
|
|
plugins: [react()],
|
|
root: ".",
|
|
publicDir: "public",
|
|
define: {
|
|
__APP_VERSION__: JSON.stringify(appVersion),
|
|
},
|
|
server: {
|
|
port: 3000,
|
|
},
|
|
build: {
|
|
outDir: "dist-client",
|
|
emptyOutDir: true,
|
|
sourcemap: false,
|
|
target: "es2020",
|
|
rollupOptions: {
|
|
output: {
|
|
manualChunks(id: string) {
|
|
if (
|
|
id.includes("node_modules/react") ||
|
|
id.includes("node_modules/react-dom") ||
|
|
id.includes("node_modules/react-router")
|
|
) {
|
|
return "vendor-react";
|
|
}
|
|
if (id.includes("node_modules/framer-motion")) {
|
|
return "vendor-animation";
|
|
}
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|