4.1.6 · Performance + Build pipeline
บทนี้วิเคราะห์ performance characteristics ของ vtrc-web + อธิบาย build pipeline (Webpack 4 + multi-stage Docker)
Build pipeline
Webpack 4 (ejected CRA)
vtrc-web เริ่มจาก CRA แต่ eject แล้ว ทำให้มี Webpack config เต็มใน config/webpack.config.dev.js + config/webpack.config.prod.js
ข้อดี: ปรับแต่งได้ทุกอย่าง ข้อเสีย: maintain เองทุก Webpack version migration
Dockerfile
vtrc-web/Dockerfile (multi-stage):
FROM node:14 AS stage-1
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN CI=false GENERATE_SOURCEMAP=false npm run build
FROM nginx:1.24.0 AS stage-2
COPY ./nginx/default.conf /etc/nginx/conf.d/
COPY --from=stage-1 /usr/src/app/build/ /usr/share/nginx/html/จุดสำคัญ:
- Node 14 (EOL April 2023) — ควร upgrade เป็น 18 หรือ 20
CI=false— ปิด CRA warning-as-errorGENERATE_SOURCEMAP=false— ปิด source map เพื่อลด build time + ป้องกัน source code leak- nginx serve — production image ไม่ใช่ Node + serve
- ไม่ได้ใช้
--legacy-peer-deps— ต่างจากvtrc-rc-backoffice
nginx config
vtrc-web/nginx/default.conf — SPA fallback ผ่าน try_files $uri $uri/ /index.html
Bundle size analysis
ขนาดประมาณการ
จากการประเมิน package.json dependencies:
| Library | ขนาดประมาณการ | หมายเหตุ |
|---|---|---|
antd@3.x (full import) | ~1.1 MB | UI library หลัก |
react@16.14 + react-dom@16.14 | ~150 KB | core |
react-apollo@3.1 + apollo-client + apollo-link-* | ~250 KB | GraphQL |
moment@2.29 (with locale) | ~300 KB | date (รวม moment/locale/th) |
axios | ~80 KB | HTTP client (fallback ของ Apollo) |
lodash | ~100 KB | utility (full import) |
crypto-js | ~120 KB | AES encryption ฝั่ง client |
chart.js@2 + react-chartjs-2 | ~200 KB | charts |
xlsx@0.18 | ~600 KB | Excel export |
react-pdf + pdfjs-dist | ~1.5 MB | PDF preview |
stimulsoft-reports-js (ถ้ามี) | ~5 MB | report |
Misc (qrcode, react-router-dom, ...) | ~500 KB | — |
รวม ~12 MB (gzip ~3-4 MB) — ใหญ่เกินมาตรฐาน (modern web bundle ปกติ gzip < 500KB)
สาเหตุที่ bundle ใหญ่
- ไม่มี code splitting — แทบไม่มี
React.lazy+Suspenseทุกหน้า load ตั้งแต่ app boot - AntD v3 full import — ไม่ได้ใช้
babel-plugin-importสำหรับ tree-shake - Lodash full import —
import _ from 'lodash'แทนimport debounce from 'lodash/debounce' - moment.js with locale — 300KB เอง (แม้ว่า modern bundle ใช้
date-fns50KB ทำงานเทียบเท่า) - PDF + chart library — โหลดตั้งแต่ต้นแม้ user ไม่ได้ใช้
- Stimulsoft ถ้ามี — 5MB ใช้เฉพาะ report
Performance issues
PERF-VTRC-WEB-01 · Bundle 12MB — Time to Interactive ช้า
ที่: package.json + ไม่มี code splitting
Impact:
- First Contentful Paint (FCP): ~2-3s บน 4G
- Time to Interactive (TTI): ~5-8s บน 4G
- ผู้ใช้ที่ network ชม (3G, rural) อาจรอ 15-20s
Reproduction: เปิด https://vtrc.redcross.or.th/ บน Chrome DevTools → Network tab → ดู total transfer
Remediation:
- Code splitting:
const NewsDetail = React.lazy(() => import('./pages/user/News/detail')) - AntD tree-shake: ลง
babel-plugin-importแล้วกำหนดimport { Button } from 'antd'→ tree-shake auto - Lodash tree-shake: เปลี่ยนทุก import เป็น single function
- Replace moment.js: ด้วย
date-fns(50KB) หรือdayjs(20KB) - Lazy-load Stimulsoft: แยก chunk ใหญ่
PERF-VTRC-WEB-02 · fetchPolicy: 'cache-first' default — ข้อมูลเก่า
ที่: src/config/apollo.js ApolloClient config
Evidence: ไม่ได้ตั้ง defaultOptions ทำให้ใช้ Apollo default = cache-first
Impact: ผู้ใช้ทำ action (เช่น approve leave) แล้วกลับไป list — list ยังแสดงข้อมูลเก่าจาก cache
Remediation:
- ทั้ง client:
defaultOptions: { watchQuery: { fetchPolicy: 'cache-and-network' } } - หรือต่อ query:
fetchPolicy: 'network-only'สำหรับ list page
PERF-VTRC-WEB-03 · No service worker — ไม่มี offline support
ที่: src/serviceWorker.js — serviceWorker.unregister()
Impact: ถ้า network ไม่เสถียร — ทุก navigation ต้องโหลดใหม่ทั้งหมด
Remediation: ถ้าต้องการ PWA-style — implement Workbox service worker
PERF-VTRC-WEB-04 · Image ไม่ optimize
ที่: public/ + src/assets/images/ — รูปถูก copy ตรงไป bundle
Impact: รูปขนาดใหญ่ (เช่น banner 1920x1080 JPEG ~500KB) โหลดช้า
Remediation:
- ใช้
responsive-loaderหรือ Webpack image optimizer - แปลงเป็น WebP/AVIF
- ใช้
<img loading="lazy">สำหรับรูปที่ไม่ใช่ above-the-fold
PERF-VTRC-WEB-05 · No HTTP/2 push
ที่: nginx config
Impact: browser ต้องส่ง request แต่ละ asset แยก (round-trip latency)
Remediation: enable HTTP/2 ใน nginx (ต้องมี HTTPS ก่อน)
Build time analysis
Local dev (npm start)
- Cold start: ~30-45 วินาที (Webpack compile ใหญ่)
- Hot reload: ~2-5 วินาทีต่อ change
Docker build (docker build)
- Cold: ~5-8 นาที (
npm install+ Webpack compile) - Warm (cache hit): ~1-2 นาที
สาเหตุที่ build ช้า
npm installใหม่ทุกครั้ง — Docker layer cache miss ถ้าpackage*.jsonเปลี่ยน- Webpack 4 + Babel compile ใหญ่ — ไม่มี cache (default disabled)
- ไม่ใช้ esbuild/swc — Babel ช้ากว่า 3-5x
Remediation
- Build cache:
npm ciแทนnpm install(faster, deterministic) - Webpack cache: enable
cache: { type: 'filesystem' }(Webpack 5 เท่านั้น) หรือcache-loader(Webpack 4) - Migrate Babel → swc — Babel plugin swc สำหรับเร่งความเร็ว
- Upgrade Webpack 4 → 5: build time ลด ~40%
nginx performance tuning
vtrc-web/nginx/default.conf ปัจจุบัน:
server {
listen 80;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
}Missing optimization:
- gzip ไม่ได้ enable — bundle 12MB ส่งแบบไม่ compressed (~12MB transfer) แทน gzip (~3-4MB)
- cache-control header ไม่ได้ตั้ง — browser ไม่ cache ทำให้ load ซ้ำทุกครั้ง
- HTTP/2 ไม่ได้ enable — แม้ nginx รองรับ
Recommended config:
server {
listen 443 ssl http2;
gzip on;
gzip_types text/css application/javascript application/json;
gzip_min_length 1024;
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache";
}
}Performance scorecard
| Dimension | สถานะ | เหตุผล |
|---|---|---|
| Bundle size | 🔴 | ~12MB — ใหญ่มาก |
| Code splitting | 🔴 | แทบไม่มี |
| First Contentful Paint | 🔴 | ~2-3s บน 4G |
| Time to Interactive | 🔴 | ~5-8s บน 4G |
| Image optimization | 🔴 | ไม่มี |
| HTTP/2 + gzip | 🟡 | HTTPS มี, แต่ HTTP/2 และ gzip ไม่ชัวร์ |
| Cache strategy | 🟡 | Apollo cache มี แต่ใช้ cache-first ทำให้ข้อมูลเก่า |
| Service worker | 🟢 | ไม่มี (แต่ not critical สำหรับ internal app) |
| Build time | 🟡 | ~5-8 นาที cold (ช้ากว่า modern CRA) |
Modernization priority (ROI-weighted)
- Code splitting — ลด TTI ได้มากที่สุด, investment ปานกลาง
- AntD tree-shake — ลด bundle ~600KB, investment เล็ก
- Lodash tree-shake — ลด bundle ~80KB, investment เล็ก
- Replace moment.js — ลด bundle ~250KB, investment ปานกลาง (grep + replace ทุกที่)
- gzip + cache-control nginx — ลด transfer ~70%, investment นิดเดียว
- HTTP/2 — ลด RTT, investment นิดเดียว
- Webpack 5 upgrade — build time -40%, investment ใหญ่ (test ทุก config)