Goal: keep the homepage under 1 MB (inspired by the 1MB Club). I still wanted a small Three.js scene with a 3D avatar of me, so I focused on two things:
- make the JS bundle smaller
- make the 3D asset smaller
Build flow is simple: JS with Vite, pages with Zola, then deploy.
Step 1: trim the bundle
Three.js was most of the weight, so I split it into its own chunk (using manualChunks for more info), turned on aggressive treeshaking, removed console/debug code, and shipped Brotli.
// vite.config.js (only the bits that matter for size)
import { defineConfig } from 'vite'
import compression from 'vite-plugin-compression'
import { terser } from 'rollup-plugin-terser'
export default defineConfig({
publicDir: false,
build: {
minify: 'terser',
treeshake: { moduleSideEffects: false, propertyReadSideEffects: false, tryCatchDeoptimization: false },
brotliSize: false,
rollupOptions: {
output: {
// put Three.js in its own cacheable chunk
manualChunks: (id) => id.includes('node_modules/three') ? 'three' : undefined,
},
plugins: [
terser({
compress: { drop_console: true, drop_debugger: true, dead_code: true, unused: true, passes: 3 },
mangle: { toplevel: true },
toplevel: true,
module: true,
})
]
},
},
plugins: [
compression({ algorithm: 'brotliCompress', include: /\.(js|css|html|json)$/ })
]
})
Result from my build:
- three.bundle.js ~566 KB (gzip ~139 KB)
- app entries all under ~6 KB
Step 2: trim the model
The old non-compressed avatar is on /blog/3dme/.
The first avatar I generated was big (~4.6 MB). I re-generated it with Hunyuan3D 2.1 targeting about 5k faces, then simplified and re-compressed with glTF-Transform:
gltf-transform simplify me_5000.glb compressed_me_5000.glb
info: me_5000.glb (461.27 KB) → compressed_me_5000.glb (329.47 KB)
The optimized one is embedded below.
End: Final size
For now this is good enough; if I need more I might revisit with dynamic imports or KTX2 compressed textures.