perf: improve loading times with better code splitting

- Improve Vite chunk splitting (Board.js 7.3MB → 5.6MB, 23% smaller)
- Add separate chunks for codemirror, onnx, daily-video, sanitizers
- Enable gzip for wasm and octet-stream in nginx
- Add dns-prefetch and preconnect hints for worker URLs
- Increase gzip compression level to 6

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-12-24 23:36:57 -05:00
parent 6f606995a4
commit ccb5acc164
3 changed files with 77 additions and 18 deletions

View File

@ -9,8 +9,13 @@
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="Permissions-Policy" content="midi=*, microphone=*, camera=*, autoplay=*">
<!-- Preconnect to critical origins for faster loading -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="dns-prefetch" href="https://jeffemmett-canvas.jeffemmett.workers.dev" />
<link rel="dns-prefetch" href="https://jeffemmett-canvas-dev.jeffemmett.workers.dev" />
<link rel="preconnect" href="https://jeffemmett-canvas.jeffemmett.workers.dev" crossorigin />
<link rel="preconnect" href="https://jeffemmett-canvas-dev.jeffemmett.workers.dev" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,MONO@-15..0,300..1000,0..1,0..1,0..1&display=swap"
rel="stylesheet">

View File

@ -4,12 +4,25 @@ server {
root /usr/share/nginx/html;
index index.html;
# Gzip compression
# Gzip compression (fallback for clients that don't support Brotli)
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript application/json;
gzip_comp_level 6;
gzip_min_length 256;
gzip_proxied any;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/wasm
application/octet-stream
image/svg+xml
font/woff2;
gzip_disable "MSIE [1-6]\.";
# Security headers

View File

@ -128,30 +128,71 @@ export default defineConfig(({ mode }) => {
rollupOptions: {
output: {
// Manual chunk splitting for large libraries to improve load times
manualChunks: {
// Core React libraries
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
manualChunks(id) {
// Core React libraries - load first
if (id.includes('node_modules/react') ||
id.includes('node_modules/react-dom') ||
id.includes('node_modules/react-router')) {
return 'react-vendor';
}
// tldraw - large drawing library (split into separate chunk)
'tldraw': ['tldraw', '@tldraw/tldraw', '@tldraw/tlschema'],
// tldraw core - split from shapes
if (id.includes('node_modules/tldraw') ||
id.includes('node_modules/@tldraw')) {
return 'tldraw';
}
// Automerge - CRDT sync library
'automerge': [
'@automerge/automerge',
'@automerge/automerge-repo',
'@automerge/automerge-repo-react-hooks'
],
if (id.includes('node_modules/@automerge')) {
return 'automerge';
}
// AI SDKs (lazy load)
'ai-sdks': ['@anthropic-ai/sdk', 'openai', 'ai'],
if (id.includes('node_modules/@anthropic-ai') ||
id.includes('node_modules/openai') ||
id.includes('node_modules/ai/')) {
return 'ai-sdks';
}
// ML/transformers (VERY large, lazy loaded)
'ml-libs': ['@xenova/transformers'],
if (id.includes('node_modules/@xenova')) {
return 'ml-libs';
}
// Markdown editors
'markdown': ['@uiw/react-md-editor', 'cherry-markdown', 'marked', 'react-markdown'],
if (id.includes('node_modules/@uiw/react-md-editor') ||
id.includes('node_modules/cherry-markdown') ||
id.includes('node_modules/marked') ||
id.includes('node_modules/react-markdown')) {
return 'markdown';
}
// Note: gun, webnative, holosphere removed - stubbed for future Nostr integration
// CodeMirror (used by markdown editors)
if (id.includes('node_modules/@codemirror') ||
id.includes('node_modules/codemirror')) {
return 'codemirror';
}
// Daily video chat
if (id.includes('node_modules/@daily-co')) {
return 'daily-video';
}
// html2canvas (screenshots)
if (id.includes('node_modules/html2canvas')) {
return 'html2canvas';
}
// ONNX runtime (ML inference)
if (id.includes('node_modules/onnxruntime')) {
return 'onnx';
}
// DOMPurify and sanitizers
if (id.includes('node_modules/dompurify') ||
id.includes('node_modules/isomorphic-dompurify')) {
return 'sanitizers';
}
},
},
},