fix(csp): external bootstrap script to survive strict reverse-proxy CSP (#564)

demo.picpeak.app sits behind Caddy + Cloudflare; Caddy replaces the
nginx CSP entirely with one that omits 'unsafe-inline' / hash / nonce,
so the #358 inline theme-bootstrap was being blocked there — admin
loaded a black page, the SPA bundle 404'd, link buttons did nothing.

Move the bootstrap to /public/bootstrap.js served as 'self' so the
script runs under every reasonable CSP without further coordination.
Vite copies /public/* to the dist root at build time (same pipeline
as /favicon-32x32.png), and it remains in <head> without defer/async
so it still runs before <body> paints. The OS-preference @media CSS
above still handles the first-frame dark/light baseline.
This commit is contained in:
Paul Nothaft
2026-05-31 22:35:00 +02:00
parent cc9a1ffa8f
commit dcc629cad2
2 changed files with 40 additions and 28 deletions
+8 -28
View File
@@ -39,9 +39,9 @@
<meta name="twitter:description" content="${BRAND_DESCRIPTION}" />
<!-- Pre-React theme bootstrap (#358).
The browser may paint the very first frame before our inline
<script> below runs, so we set OS-preference defaults via CSS
here in <head> — that gets applied before any paint. The
The browser may paint the very first frame before the bootstrap
<script> below fetches and runs, so we set OS-preference defaults
via CSS here in <head> — that gets applied before any paint. The
script then layers a per-gallery cache hit on top when one is
available. Without this CSS, the very first frame on first-
visit dark-OS devices flashed white briefly (see Rekoo-PS's
@@ -56,31 +56,11 @@
html { transition: background-color 200ms ease; }
</style>
<script>
/*
* Pre-React theme bootstrap (#358).
*
* The CSS @media block above handles the OS-preference default
* before paint. This script then applies a per-gallery cached
* background (written by ThemeContext on the previous visit) so
* revisits land on the exact theme background from frame one.
*/
(function () {
try {
var m = location.pathname.match(/\/gallery\/([^\/?#]+)/);
var bg = null;
if (m && m[1]) {
bg = localStorage.getItem('gallery-theme-bg-' + decodeURIComponent(m[1]));
}
if (bg) {
var root = document.documentElement;
root.style.backgroundColor = bg;
document.body && (document.body.style.backgroundColor = bg);
root.style.setProperty('--color-background', bg);
}
} catch (e) { /* never block render on a cache miss */ }
})();
</script>
<!-- Pre-React theme bootstrap (#358). External rather than inline so a
strict CSP without 'unsafe-inline' / hash / nonce — like the one
Caddy puts in front of demo.picpeak.app — doesn't block it (#564).
No defer/async: must run before <body> paints. -->
<script src="/bootstrap.js"></script>
</head>
<body>
<div id="root"></div>