16.5 Wave 3 — Frontend migration to Nuxt 3
Wave 3 ย้าย frontend ทั้งสองตัว (vtrc-web และ vtrc-rc-backoffice) จาก React 16 + Webpack 4 + Apollo v2 ไป Nuxt 3 (Vue 3, Vite, Feature-Sliced Design)
เป้าหมายของ Wave 3
- frontend ทั้งสองตัวรันบน Nuxt 3
- ใช้ Feature-Sliced Design (FSD) เป็นโครงสร้าง
- TypeScript เป็น default
- มี visual regression test
- Effort: 15 เดือน (30 sprint) — parallel กับ Wave 2
- ทีม: 2-3 frontend engineer
ทำไมต้อง Nuxt 3
| เหตุผล | อธิบาย |
|---|---|
| Vue 3 มี Composition API | code อ่านง่ายกว่า, reusability ดีกว่า |
| Vite | HMR เร็วกว่า Webpack 10 เท่า, build เร็วกว่า |
| Nuxt 3 มี auto-import | code สั้นลง, boilerplate น้อย |
| SSR/SSG ในตัว | โหลดเร็วขึ้น, SEO ดีขึ้น |
| ชุมชนดีในไทย | หานักพัฒนา Vue ในไทยง่ายกว่า React |
หมายเหตุ: ทีมสามารถเลือก React/Next.js แทนได้ แต่ต้องปรับ structure ตาม (หลักการ FSD ใช้ได้กับทุก framework)
โครงสร้าง Feature-Sliced Design
src/
├── app/ # config แอป (Nuxt config, plugin, middleware)
│ ├── plugins/
│ ├── middleware/
│ └── nuxt.config.ts
├── processes/ # เฉพาะ multi-step (เช่น onboarding)
├── pages/ # route page (Nuxt file-based routing)
│ ├── index.vue
│ ├── leave/
│ │ └── [id].vue
│ └── payroll/
├── widgets/ # block ใหญ่ในหน้า (LeaveRequestForm)
├── features/ # feature เล็ก (LeaveBalanceBadge)
├── entities/ # domain model (User, Leave, Payslip)
│ ├── user/
│ │ ├── api/
│ │ ├── model/
│ │ ├── ui/
│ │ └── index.ts
│ └── leave/
└── shared/ # ใช้ร่วม (UI kit, util, config)
├── ui/
├── api/
└── config/กฎ FSD:
- layer บน import layer ล่างได้ (pages → widgets → features → entities → shared)
- layer ล่างห้าม import layer บน
- แต่ละ layer มี slice (กลุ่มตาม domain) และ segment (api, model, ui, lib)
ลำดับการ migrate
migration ทำทีละ page ไม่ใช่ทั้งหมดในครั้งเดียว (ตามหลัก strangler)
Sprint 57-58: สร้าง foundation
Sprint 59-60: หน้า login + หน้าหลัก (high-value, low-risk)
Sprint 61-64: หน้า leave + payroll
Sprint 65-68: หน้า welfare + provident-fund
Sprint 69-72: หน้า admin/backoffice
Sprint 73-76: หน้า report + analytics
Sprint 77-80: ทดสอบ, cutover, decommission Reactรายละเอียด Sprint แรก (foundation)
Task 3.1 · สร้าง repo ใหม่
vtrc-web-next/
├── nuxt.config.ts
├── package.json
├── tsconfig.json
├── app.vue
└── src/ # FSD structure ข้างบนหรือใช้ vtrc-web เดิม แล้วสร้าง branch next — ขึ้นกับทีม
Task 3.2 · ตั้งค่า
- Nuxt 3 + Vite + TypeScript
@nuxtjs/apollo(สำหรับ GraphQL)@nuxtjs/tailwind(สำหรับ styling)- pinia (สำหรับ state)
- vitest (สำหรับ test)
- playwright (สำหรับ e2e test)
Task 3.3 · auth flow
ย้าย token refresh logic จาก vtrc-web/src/config/apollo.js มา Nuxt plugin:
// src/app/plugins/apollo.ts
export default defineApolloAuth((nuxtApp) => ({
httpEndpoint: useRuntimeConfig().public.graphqlEndpoint,
tokenName: 'token',
authType: 'Bearer',
onCachedError(error) {
if (error.message === 'TOKEN_EXPIRED') {
navigateTo('/login');
}
}
}));Task 3.4 · UI kit
เลือกอย่างเดียว — ห้ามใช้หลาย library (debt ปัจจุบันใช้ AntD + MUI + styled-components):
แนะนำ:
- UI Bakery / Nuxt UI (Vue 3 native) — แนะนำ
- PrimeVue — component เยอะ
- Ant Design Vue — คล้าย AntD เดิม (ทีมเคยชิน)
Task 3.5 · theme + design token
ย้าย design token ไป CSS variable:
:root {
--color-primary: #d8232a; /* แดงกาชาด */
--color-bg: #ffffff;
--color-text: #1a1a1a;
/* ... */
}ตัวอย่าง migration — หน้า Leave Request
เดิม (React)
// vtrc-web/src/pages/LeaveRequest.jsx (ประมาณ)
import { useQuery, useMutation } from '@apollo/react-hooks';
import { Form, Input, Button, DatePicker } from 'antd';
import styled from 'styled-components';
const Wrapper = styled.div`padding: 16px;`;
const LeaveRequest = () => {
const { data, loading } = useQuery(LEAVE_BALANCE);
const [submitLeave] = useMutation(SUBMIT_LEAVE);
const onSubmit = (values) => {
submitLeave({ variables: values });
};
return (
<Wrapper>
<Form onFinish={onSubmit}>
<Form.Item name="startDate" label="วันเริ่ม">
<DatePicker />
</Form.Item>
{/* ... */}
</Form>
</Wrapper>
);
};ใหม่ (Vue 3 + FSD)
src/
├── entities/
│ └── leave/
│ ├── api/
│ │ ├── queries.ts # LEAVE_BALANCE, SUBMIT_LEAVE
│ │ └── types.ts
│ ├── model/
│ │ └── useLeaveBalance.ts
│ └── index.ts
├── features/
│ └── submit-leave/
│ ├── ui/
│ │ └── SubmitLeaveForm.vue
│ ├── model/
│ │ └── useSubmitLeave.ts
│ └── index.ts
├── widgets/
│ └── leave-request/
│ └── ui/
│ └── LeaveRequestWidget.vue
└── pages/
└── leave/
└── request.vue<!-- src/features/submit-leave/ui/SubmitLeaveForm.vue -->
<script setup lang="ts">
import { useLeaveBalance } from '~/entities/leave/model/useLeaveBalance';
import { useSubmitLeave } from '../model/useSubmitLeave';
const { balance } = useLeaveBalance();
const { submit, loading } = useSubmitLeave();
</script>
<template>
<UForm @submit="submit">
<UFormField label="วันเริ่ม" name="startDate">
<UInputDate v-model="form.startDate" />
</UFormField>
<!-- ... -->
</UForm>
</template><!-- src/pages/leave/request.vue -->
<script setup lang="ts">
import { LeaveRequestWidget } from '~/widgets/leave-request';
</script>
<template>
<LeaveRequestWidget />
</template>cutover strategy
┌──────────────────────────┐
│ Nginx │
└──────────┬───────────────┘
│
┌──────────┴───────────────┐
│ │
เส้นทางเดิม เส้นทางใหม่
│ │
▼ ▼
┌────────────────┐ ┌────────────────┐
│ vtrc-web │ │ vtrc-web-next │
│ (React) │ │ (Nuxt 3) │
└────────────────┘ └────────────────┘Nginx route ตาม path:
location /leave {
proxy_pass http://vtrc-web-next:3000; # ใหม่
}
location / {
proxy_pass http://vtrc-web:3000; # เดิม
}ค่อย ๆ เพิ่ม path ที่ไป vtrc-web-next จนครบ แล้ว decommission vtrc-web
เกณฑ์การตัดสินใจว่า Wave 3 เสร็จ
- [ ] frontend ทั้งสองตัวรันบน Nuxt 3
- [ ] Visual regression test ครอบคลุมทุกหน้าหลัก
- [ ] Lighthouse score ≥ 80 (Performance, Accessibility)
- [ ] bundle size ลดลง ≥ 30%
- [ ] ไม่มี React/AntD v3/Apollo v2 ใน codebase
ความเสี่ยงของ Wave 3
| ความเสี่ยง | ผลกระทบ | Mitigation |
|---|---|---|
| ทีมไม่เคยใช้ Vue | migration ช้า | training + spike ก่อนเริ่ม |
| Design ไม่เหมือนเดิม | ผู้ใช้ complaint | visual regression test + screenshot compare |
| Apollo Client v3 → Nuxt Apollo | data flow เปลี่ยน | ใช้ ent gql หรือเขียน wrapper |
| Migration ยาวนาน | ทีมเบื่อ | migrate ทีละ page แล้ว decommission เลย |
สรุป Wave 3
Wave 3 ใช้เวลา 15 เดือน (parallel กับ Wave 2) ย้าย frontend ไป Nuxt 3 ที่ทันสมัยและ maintain ได้ง่ายกว่า
Timeline รวม Wave 0-3
เดือน 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
Wave 0 ████│││││││││││││││││││││││││││││││││││││││││││││││││││││││││││││││││
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
Wave 1 ████████████████████│││││││││││││││││││││││││││││││││││││││││││
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
Wave 2 ████████████████████████████████████│││││││││││││││││
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
Wave 3 ████████████████████████████████████████████รวม 18-24 เดือนสำหรับ modernization เต็มรูปแบบ
บทถัดไปจะเป็น playbook สำหรับ bounded context extraction (ใช้ได้กับทุก context ใน Wave 2)