**nginx served the bundle uncompressed and uncacheable.** The base image
ships gzip commented out and nothing set `Cache-Control`, so every load
carried the whole thing and every reload cost a 304 per asset. Measured on
the built image: the entry chunk 838 kB → 311 kB, the Monaco chunk 2.66 MB
→ 832 kB, and the ~50 content-hashed assets are now immutable for a year.
`index.html` is explicitly `no-cache`, since it is what names the rest.
**A widget that throws no longer blanks the screen.** There was one error
boundary in the app, on the root route, so anything that threw replaced
everything including the navigation — on `/view/{name}`, an unattended wall
panel with no way back. Each tile has its own boundary now, and the app
shell has one inside it so a screen that fails leaves the sidebar standing.
`react-error-boundary` was already a dependency and imported nowhere.
**`localStorage` cannot take the app down.** Reaching it raises where the
browser blocks site data, and `setItem` raises once the origin's quota is
full — which the flow editor's node clipboard, carrying whole Python
sources, can genuinely reach. Thrown from a key handler that escaped to
`window.onerror`, which the single root boundary then turned into a blank
page. `lib/safeStorage.ts` is the guarded pair the pre-paint theme script in
`index.html` was already using; a copy too large to store now says so.
Queries default to `staleTime: 5000` — below every poll interval on any
screen, so nothing polls less often than it did, but a route mounting twice
in a few seconds stops refetching everything it touches. Window-focus
refetching is off: the socket pushes what changes and a reconnect
invalidates what it feeds, so a focus event has nothing of its own to say.
Home alone reads about ten queries on every one of those.
Render cost, two that showed up in the audit:
- `LogsPanel` was rendered unconditionally by the dock and decided inside
itself whether to draw, so with the panel *shut* it still subscribed to
the log store and re-filtered five hundred lines per line a flow
published. It returns before any of that now.
- `HealthActivity` subscribed to the whole engine-event array and used one
number from it, so a flapping node re-rendered the component that draws
Home's two uPlot charts — each of which rebuilds its series on every
render by design. It subscribes to that number.
- the global search bucketed the index nine times per keystroke, once per
group. One pass, and each group offers at most twenty.
43 lines
1.2 KiB
Nginx Configuration File
43 lines
1.2 KiB
Nginx Configuration File
server {
|
|
listen 80;
|
|
|
|
# The bundle is ~770 kB of JavaScript and ~85 kB of CSS, and nginx's base
|
|
# image ships gzip commented out — so every first load shipped all of it
|
|
# uncompressed. Roughly a third of the bytes with this on.
|
|
gzip on;
|
|
gzip_vary on;
|
|
gzip_min_length 1024;
|
|
gzip_proxied any;
|
|
gzip_types
|
|
application/javascript
|
|
application/json
|
|
application/manifest+json
|
|
application/wasm
|
|
image/svg+xml
|
|
text/css
|
|
text/plain;
|
|
|
|
location / {
|
|
root /usr/share/nginx/html;
|
|
index index.html index.htm;
|
|
try_files $uri /index.html =404;
|
|
}
|
|
|
|
# Vite puts a content hash in every asset's name, so one can never change
|
|
# under its URL. Without this each of the ~50 of them cost a 304 round trip
|
|
# on every reload.
|
|
location /assets/ {
|
|
root /usr/share/nginx/html;
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
}
|
|
|
|
# The one file that must not be cached: it is what names the current
|
|
# assets, so a stale copy pins the browser to the previous build.
|
|
location = /index.html {
|
|
root /usr/share/nginx/html;
|
|
add_header Cache-Control "no-cache";
|
|
}
|
|
|
|
include /etc/nginx/extra-conf.d/*.conf;
|
|
}
|