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
24 changes: 23 additions & 1 deletion server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,31 @@ const webDist = path.join(PROJECT_ROOT, 'web', 'dist')
if (fs.existsSync(webDist)) {
// wildcard:true serves from disk per request, so a web rebuild with new
// hashed asset names does not require a server restart.
await app.register(fastifyStatic, { root: webDist, wildcard: true })
//
// The two kinds of file want opposite caching, and serving both as
// max-age=0 got both wrong: the shell could still be held stale by a
// phone browser after a deploy, while two megabytes of fingerprinted
// JavaScript were revalidated on every single load. A file under
// /assets carries its content hash in its name, so its bytes can never
// change and it is cached for a year and never revalidated; the HTML
// shell that names those files is never cached, so a deploy is picked
// up on the next load without anyone clearing anything.
await app.register(fastifyStatic, {
root: webDist,
wildcard: true,
// The plugin writes its own Cache-Control (max-age=0) after
// setHeaders runs, so its handling has to be off for these to stand.
cacheControl: false,
setHeaders: (res, filePath) => {
const immutable = /[\\/]assets[\\/]/.test(filePath) && /-[A-Za-z0-9_-]{8,}\.[a-z0-9]+$/.test(filePath)
res.setHeader('Cache-Control', immutable
? 'public, max-age=31536000, immutable'
: 'no-store, must-revalidate')
},
})
app.setNotFoundHandler((req, reply) => {
if (req.url.startsWith('/api/')) return reply.status(404).send({ error: 'not found' })
reply.header('Cache-Control', 'no-store, must-revalidate')
return reply.sendFile('index.html')
})
}
Expand Down
9 changes: 7 additions & 2 deletions web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1640,8 +1640,13 @@ code, pre, kbd, .text-body, .viewer-path, .sql-box, [data-streamdown='code-block
page-level scroll keeps the bar on the bottom edge; the views
scroll inside their own containers, which is where scrolling
belongs. */
html, body, #root { height: 100dvh; overflow: hidden; }
.app { height: 100dvh; }
html, body, #root { height: 100%; overflow: hidden; }
/* Pinned to the visual viewport rather than sized by a chain of
percentages: a fixed element with dvh is the one arrangement iOS
keeps correct while its toolbars slide in and out. Out of flow, so
the page itself has nothing to scroll and the bottom bar cannot be
carried off-screen; the views scroll inside themselves. */
.app { position: fixed; inset: 0; height: 100dvh; }
body { overflow-x: hidden; }
/* Clear of the home indicator, on the phones that have one. */
.sidenav, .sidenav.collapsed { padding-bottom: max(2px, env(safe-area-inset-bottom)); }
Expand Down
Loading