Skip to content

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):

dockerfile
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-error
  • GENERATE_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 MBUI library หลัก
react@16.14 + react-dom@16.14~150 KBcore
react-apollo@3.1 + apollo-client + apollo-link-*~250 KBGraphQL
moment@2.29 (with locale)~300 KBdate (รวม moment/locale/th)
axios~80 KBHTTP client (fallback ของ Apollo)
lodash~100 KButility (full import)
crypto-js~120 KBAES encryption ฝั่ง client
chart.js@2 + react-chartjs-2~200 KBcharts
xlsx@0.18~600 KBExcel export
react-pdf + pdfjs-dist~1.5 MBPDF preview
stimulsoft-reports-js (ถ้ามี)~5 MBreport
Misc (qrcode, react-router-dom, ...)~500 KB

รวม ~12 MB (gzip ~3-4 MB) — ใหญ่เกินมาตรฐาน (modern web bundle ปกติ gzip < 500KB)

สาเหตุที่ bundle ใหญ่

  1. ไม่มี code splitting — แทบไม่มี React.lazy + Suspense ทุกหน้า load ตั้งแต่ app boot
  2. AntD v3 full import — ไม่ได้ใช้ babel-plugin-import สำหรับ tree-shake
  3. Lodash full importimport _ from 'lodash' แทน import debounce from 'lodash/debounce'
  4. moment.js with locale — 300KB เอง (แม้ว่า modern bundle ใช้ date-fns 50KB ทำงานเทียบเท่า)
  5. PDF + chart library — โหลดตั้งแต่ต้นแม้ user ไม่ได้ใช้
  6. 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:

  1. Code splitting: const NewsDetail = React.lazy(() => import('./pages/user/News/detail'))
  2. AntD tree-shake: ลง babel-plugin-import แล้วกำหนด import { Button } from 'antd' → tree-shake auto
  3. Lodash tree-shake: เปลี่ยนทุก import เป็น single function
  4. Replace moment.js: ด้วย date-fns (50KB) หรือ dayjs (20KB)
  5. 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.jsserviceWorker.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 ช้า

  1. npm install ใหม่ทุกครั้ง — Docker layer cache miss ถ้า package*.json เปลี่ยน
  2. Webpack 4 + Babel compile ใหญ่ — ไม่มี cache (default disabled)
  3. ไม่ใช้ esbuild/swc — Babel ช้ากว่า 3-5x

Remediation

  1. Build cache: npm ci แทน npm install (faster, deterministic)
  2. Webpack cache: enable cache: { type: 'filesystem' } (Webpack 5 เท่านั้น) หรือ cache-loader (Webpack 4)
  3. Migrate Babel → swc — Babel plugin swc สำหรับเร่งความเร็ว
  4. Upgrade Webpack 4 → 5: build time ลด ~40%

nginx performance tuning

vtrc-web/nginx/default.conf ปัจจุบัน:

nginx
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:

nginx
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)

  1. Code splitting — ลด TTI ได้มากที่สุด, investment ปานกลาง
  2. AntD tree-shake — ลด bundle ~600KB, investment เล็ก
  3. Lodash tree-shake — ลด bundle ~80KB, investment เล็ก
  4. Replace moment.js — ลด bundle ~250KB, investment ปานกลาง (grep + replace ทุกที่)
  5. gzip + cache-control nginx — ลด transfer ~70%, investment นิดเดียว
  6. HTTP/2 — ลด RTT, investment นิดเดียว
  7. Webpack 5 upgrade — build time -40%, investment ใหญ่ (test ทุก config)

ขั้นตอนถัดไป

ไป บท 4.1.7 Scorecard + known bugs