morphit/apps/web/svelte.config.js

105 lines
4.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
// Fully static output — deployable anywhere, no runtime server required.
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: 'index.html',
precompress: true, // emit .gz and .br alongside every asset
strict: true
}),
// Every Morphit instance is served from its DOMAIN ROOT (nginx web
// root = the build dir), never a sub-path. SvelteKit's default
// `paths.relative: true` rewrites root-absolute links (e.g. the
// footer's `/canary.txt` / `/pgp_keys.asc`) into paths relative to
// the current prerendered page — so from `/en/` they resolved to
// `/en/canary.txt` and 404'd. `relative: false` keeps them absolute
// (`/canary.txt`) on every locale page, which is what a root-hosted
// static site wants. (cp431 — footer canary link 404.)
paths: { relative: false },
// Content Security Policy is NOT emitted here.
//
// This is a fully static (adapter-static) build served by nginx, so
// there is no SvelteKit runtime to set a response header — a
// SvelteKit-managed CSP can only be injected as a <meta http-equiv>
// tag, and that is strictly worse here:
// 1. `frame-ancestors` (our clickjacking defense) is IGNORED by
// browsers when delivered via <meta>; it only works as a
// header. X-Frame-Options + a header CSP cover it instead.
// 2. The app runs WebAssembly in the browser (argon2 KDF for the
// keystore, signing) which needs `wasm-unsafe-eval`, and ships
// inline bootstrap scripts which need `'unsafe-inline'` once
// per-page hashes aren't being computed — a hash-mode meta tag
// fought both and silently broke crypto + hydration.
// 3. A meta CSP and the nginx header CSP are INTERSECTED by the
// browser, so the stricter meta clobbered the working header,
// and operators had to `sed` the meta out of the build by hand.
//
// The canonical CSP is therefore a single nginx `add_header
// Content-Security-Policy` (see docs/RUN-A-MORPHIT-NODE.md §10 and
// docs/OPERATIONS.md §15). One source of truth, no meta tag, no
// manual stripping. If you change the client's Blurt RPC list
// (src/lib/net/config.ts) update that header's connect-src to match.
// No server-side state; prerender everything possible.
//
// `handleUnseenRoutes: 'ignore'` — dynamic-param routes
// (/<lang>/chat/[peer=account], /<lang>/explorer/tx/[id=trxid],
// /<lang>/[x+40][account=account]/[permlink=permlink], etc.)
// have no enumeration source at build time — we can't list
// every possible peer account or txid in advance. These
// routes are served at runtime via the SPA fallback
// (`fallback: 'index.html'` above), which SvelteKit's
// client router then resolves to the correct dynamic page.
// Without `handleUnseenRoutes: 'ignore'` the build errors
// out per Part 121 cp7's restructure attempt. Static
// indexable routes (17 routes × 10 locales = 170 HTMLs)
// prerender as expected.
prerender: {
handleHttpError: 'warn',
handleMissingId: 'warn',
handleUnseenRoutes: 'ignore'
},
alias: {
$lib: 'src/lib',
$components: 'src/lib/components',
$crypto: 'src/lib/crypto',
$i18n: 'src/lib/i18n',
$stores: 'src/lib/stores',
$utils: 'src/lib/utils',
$net: 'src/lib/net',
$blurt: 'src/lib/blurt',
$indexer: 'src/lib/indexer',
$seo: 'src/lib/seo',
$prices: 'src/lib/prices'
},
// Tighten default security headers
serviceWorker: {
// Auto-register the SW bundle. Update-consent (the "Load it now"
// snackbar) is driven by UpdateBanner.svelte off the waiting
// worker; the SW's APPLY_UPDATE message is the only path that
// calls skipWaiting(), so a new version never takes over silently.
register: true,
// updateViaCache:'none' — the browser must re-fetch
// /service-worker.js from the NETWORK (never its HTTP cache) on
// every update check, so a freshly-deployed worker is detected
// promptly and the snackbar can surface. Without this, a cached
// SW script can hide a deploy until the browser's own ~24h cycle.
// (Pair with a server-side `Cache-Control: no-cache` on
// /service-worker.js so an upstream proxy can't serve it stale.)
options: { updateViaCache: 'none' }
}
}
};
export default config;