Three.js 實戰專案:3D 互動網站 — 部署成完整作品
簡介
在完成了 3D 場景的開發、互動邏輯與 UI 設計之後,把作品部署上線 才是真正讓使用者體驗的關鍵一步。
現代前端開發已不僅是把 index.html、main.js 直接放到伺服器,而是需要考慮 模組打包、資源最佳化、CDN 加速、安全性與持續部署 等完整流程。
本篇文章將以 Three.js 為核心,從 開發環境建置、靜態資源管理、部署平台選擇、到 常見陷阱與最佳實踐,一步步說明如何把一個 3D 互動網站變成可供大眾直接訪問的完整作品,讓初學者也能快速上手、進階開發者能夠在實務上得到參考。
核心概念
1. 使用模組打包工具 (Vite / Webpack)
在瀏覽器原生支援 ES6 模組前,前端專案多半依賴打包工具將多個檔案合併、壓縮、轉譯。即使現在大多瀏覽器已支援模組,打包仍能提供以下好處:
- Tree‑shaking:只保留實際使用到的 Three.js 子模組,減少程式碼體積。
- 資源載入:自動把模型、貼圖、HDR 環境圖等檔案作為模組匯入,產生雜湊檔名以利快取。
- 開發伺服器:熱更新 (HMR) 讓你修改程式碼即時在瀏覽器中看到變化。
Vite 範例 (vite.config.js)
import { defineConfig } from 'vite';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
// 設定別名,方便引用 assets
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@assets': resolve(__dirname, 'src/assets')
}
},
// 讓 three.js 的 ES 模組正確被編譯
optimizeDeps: {
include: ['three']
},
build: {
// 產出目錄
outDir: 'dist',
// 壓縮程式碼
minify: 'esbuild',
// 產生雜湊檔名,利於瀏覽器快取
assetsDir: 'assets',
rollupOptions: {
output: {
// 讓模型、貼圖等檔案放在 assets 子目錄
assetFileNames: 'assets/[name].[hash][extname]'
}
}
}
});
重點:
optimizeDeps.include告訴 Vite 先預先編譯 Three.js,避免在開發階段出現Cannot find module 'three'的錯誤。
2. 動態載入大型模型與貼圖
3D 作品的模型檔案往往數 MB 甚至十幾 MB,若一次性全部載入會造成 首屏渲染卡頓。解法是使用 import() 或 Three.js 的 GLTFLoader 搭配 懶加載。
動態載入範例
// main.js
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// 建立基本場景
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(0, 1.6, 3);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
// 當使用者點擊「載入模型」按鈕時才真正下載
document.getElementById('loadBtn').addEventListener('click', async () => {
// 使用動態匯入,讓 webpack/Vite 能自動分割程式碼
const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader');
const loader = new GLTFLoader();
// 讀取 glb 檔案,檔案會自動走 CDN / 靜態伺服器
loader.load('/assets/models/room.glb', (gltf) => {
scene.add(gltf.scene);
console.log('模型載入完成');
});
});
技巧:
await import(...)只在支援 ES2020 的瀏覽器上有效,若要兼容舊版瀏覽器,可在打包時加入 polyfill。
3. 使用 CDN 加速靜態資源
把模型、HDR 環境貼圖、字體等檔案放在 CDN(例如 Cloudflare、Netlify Large Media)能大幅降低伺服器負載,並利用地理分布的 Edge 節點提升下載速度。
CDN 設定範例(Netlify netlify.toml)
[build]
publish = "dist"
command = "npm run build"
[[headers]]
# 為所有資產加上長期快取
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[redirects]]
# 把所有 404 請求導向 index.html,支援 SPA
from = "/*"
to = "/index.html"
status = 200
重點:將
Cache‑Control設為immutable可讓瀏覽器在資源未變更時直接使用快取,減少重複下載。
4. 部署平台比較
| 平台 | 免費額度 | 靜態資源上傳 | CI/CD 整合 | 特色 |
|---|---|---|---|---|
| Netlify | 每月 100 GB 帶寬、300 build minutes | 支援自動壓縮、Cache‑Control | 直接連接 GitHub/GitLab | 一鍵部署、表單、伺服器端函式 |
| Vercel | 每月 100 GB 帶寬、100 GB‑hour | Edge Functions、Smart CDN | Git 整合、預覽環境 | 適合 Next.js、Serverless |
| GitHub Pages | 無流量上限(受限於 GitHub) | 只能部署靜態檔案 | 手動或 Action 自動化 | 簡單、適合個人作品集 |
| Firebase Hosting | 每月 10 GB 下載、10 GB 儲存 | HTTPS、Cache‑Control 自訂 | CI/CD via GitHub Action | 支援多站點、即時預覽 |
建議:若專案需要 Serverless API(例如保存使用者設定),可以選擇 Netlify Functions 或 Vercel Edge Functions;若僅為純靜態 3D 網站,GitHub Pages 已足夠。
5. 加入 Service Worker 變成 PWA
將 3D 網站包裝成 Progressive Web App,使用者即使離線也能瀏覽已快取的模型。下面示範最簡單的 Service Worker 設定。
sw.js
const CACHE_NAME = 'threejs-pwa-v1';
const ASSETS_TO_CACHE = [
'/',
'/index.html',
'/main.js',
'/assets/models/room.glb',
'/assets/textures/hdri.hdr'
];
// 安裝階段:快取核心資源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS_TO_CACHE))
);
});
// 取得資源時:先從快取取,沒有再向網路請求
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
// 動態快取新資源(可自行限制)
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
return response;
});
})
);
});
註冊 Service Worker(main.js)
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(() => console.log('PWA Service Worker 已註冊'))
.catch((err) => console.error('註冊失敗', err));
}
注意:
sw.js必須放在網站根目錄,否則快取範圍會受到限制。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 模型過大 | 未壓縮的 GLTF/OBJ 可能 > 10 MB,下載時間長。 | 使用 Draco 或 Meshopt 壓縮,並在 GLTFLoader 加上 DRACOLoader。 |
| 貼圖未設定 CORS | 直接載入跨域貼圖會拋出 Cross‑origin 錯誤。 |
在 CDN 或伺服器設定 Access‑Control‑Allow‑Origin: *,或在 TextureLoader 加入 loader.setCrossOrigin('anonymous')。 |
| 開發環境與生產環境路徑不一致 | import.meta.env.BASE_URL 未正確設定導致資源 404。 |
在 Vite/webpack 中使用 process.env.PUBLIC_URL 或 import.meta.env.BASE_URL,並在 netlify.toml 或 vercel.json 中設定 rewrites。 |
| 瀏覽器記憶體泄漏 | 持續創建大量 Mesh、未釋放 dispose()。 |
在切換場景或移除物件時呼叫 geometry.dispose()、material.dispose()、texture.dispose()。 |
| HTTPS 與混合內容 | Three.js 需要載入 HDR、GLB 等二進位檔案,若網站是 HTTPS 而資源是 HTTP,會被阻擋。 | 確保所有資源均使用 HTTPS,或在 Netlify/Vercel 設定自動轉向。 |
最佳實踐
- 使用
npm run build產出最小化檔案,並檢查bundle size(建議 < 500 KB gzipped)。 - 啟用 HTTP/2 或 HTTP/3,減少多檔案請求的延遲。
- 設定合理的 Cache‑Control:HTML 1 h、JS/CSS 1 day、模型/貼圖 1 year +
immutable。 - 結合 CI/CD:每次 push 後自動執行測試、建置、部署,保持網站始終可用。
- 監控效能:使用 Lighthouse、Web Vitals,特別留意 First Contentful Paint (FCP) 與 Time to Interactive (TTI)。
實際應用場景
| 場景 | 需求 | 部署建議 |
|---|---|---|
| 產品展示 (e‑commerce) | 高畫質模型、快速切換顏色 | 使用 CDN + lazy‑load,模型分割成部件,利用 GLTFLoader 動態載入。 |
| 線上展覽/虛擬藝廊 | 大量 HDR 環境、音效、互動導覽 | 搭配 Service Worker 做離線快取,使用 Netlify Functions 提供導覽路徑 API。 |
| 教育訓練平台 | 多個教學案例、即時更新 | 透過 GitHub Actions 自動部署,利用 Vercel Preview Deployments 讓教師先行測試。 |
| 行銷活動 (微網站) | 短期活動、需要快速上線 | 使用 Firebase Hosting 的 firebase deploy,結合 Google Analytics 追蹤互動。 |
| 企業內部儀表板 | 安全性、存取控制 | 部署在 Vercel 並設定 Password Protection,或使用 Netlify Identity 做身份驗證。 |
總結
- 打包工具(Vite、Webpack)是現代 Three.js 專案不可或缺的基礎,能有效減少程式碼體積、管理資源、提供熱更新。
- 懶加載與壓縮(Draco、Meshopt)是解決大型模型下載瓶頸的關鍵技巧。
- CDN 與快取策略(Cache‑Control、immutable)讓使用者在全球任意地點都能快速取得模型與貼圖。
- 部署平台(Netlify、Vercel、GitHub Pages、Firebase)各有特色,依需求選擇最適合的方案。
- PWA 與 Service Worker 為 3D 網站提供離線體驗,提升使用者黏著度。
- 避免常見陷阱(跨域、記憶體泄漏、路徑錯誤)並遵循最佳實踐,可讓作品在正式環境中保持穩定與高效。
透過本文的步驟與範例,您已掌握從 本機開發 → 打包 → 上傳 CDN → 部署至雲端 的完整流程,現在只要把自己的 3D 互動網站推上去,讓全世界的使用者盡情探索吧! 🎉