Skip to content

8.4 · Routing + auth gate + dynamic menu

บทนี้อธิบายว่า vtrc-rc-backoffice จัดการ route อย่างไร ตรวจ auth อย่างไร และเมนูถูกสร้างอย่างไรจาก backend


ภาพรวม

URL (browser)

BrowserRouter

Switch (เลือก match path)

├── /login                         → Login (public)
├── /TheamCss                      → TheamCss (public, typo — ห้ามแก้)
├── /pathLogin/:token/:refreshToken → PathLogin (SSO bypass, public)
├── /                              → ถ้า isLoggedIn() === true → SiderLayout
│                                       ↓
│                                     axiosWorkforce.get("fetchgroupmenu")
│                                     axiosWorkforce.get("fetchsidemenu")
│                                       ↓
│                                     <Route> จาก routes.js ภายใน shell
├── !isLoggedIn()                  → <Redirect to="/login" />
└── (default)                      → NotFound

ไม่มี component ชื่อ PrivateRoute — gate เป็น conditional JSX ใน App


routes.js — route table ขนาดใหญ่

src/config/routes.js เป็น array ที่เก็บ route entry ส่วนใหญ่ของแอป (ประมาณ 2,550 บรรทัด / ~276 paths)

js
// shape โดยย่อ — ไม่ใช่ไฟล์ทั้งก้อน
export const routes = [
  {
    path: "/home",
    component: Home,
    exact: true,
    key: "home",
    // label / moduleKey ฯลฯ ตาม entry
  },
  // ...
];

ข้อระวังเกี่ยวกับ routes.js

  1. ไฟล์ใหญ่มาก — ควร grep หา path แทน scroll
  2. มี snapshot หลายตัว (routes_bak.js, routes_bk-prd.js, …) — ห้ามใช้เป็นต้นแบบ (ดู บท 7.1)
  3. การกรองสิทธิ์ฝั่ง frontend อ่อน — สิทธิ์จริงอยู่ที่ API

การ mount route ใน SiderLayout

src/components/SiderLayout/index.js (~1,168 บรรทัด) เป็น shell ที่ render sider + content และผูก <Route> จาก routes ภายใน Layout

concept: หลัง login สำเร็จผู้ใช้เข้า SiderLayout แล้วจึงได้เมนู + หน้าตาม path


Auth gate — isLoggedIn() + Redirect

16:31:vtrc-rc-backoffice/src/containers/App/index.js
function App () {
	console.log = console.warn = console.error = () => { };
	return (
		<BrowserRouter>
			<Switch>
				<Route path="/login" exact component={Login} />
				<Route path="/TheamCss" exact component={TheamCss} />
            <Route path="/pathLogin/:token/:refreshToken" exact component={PathLogin}/>
				{isLoggedIn() && <Route path="/" component={SiderLayout} />}
				{!isLoggedIn() && <Redirect to="/login" />}
				<Route component={NotFound} />
			</Switch>
		</BrowserRouter>
	)
}

isLoggedIn() — boolean ไม่ใช่ token string

43:49:vtrc-rc-backoffice/src/utils/auth.js
export const isLoggedIn = () => {
	if (localStorage.getItem('isRemember')) {
		return !!localStorage.getItem('token') && !!localStorage.getItem('refreshToken')
	} else {
		return !!sessionStorage.getItem('token') && !!sessionStorage.getItem('refreshToken')
	}
}

จุดสำคัญ:

  • คืน true/false เท่านั้น — ต้องมีครบทั้ง access token และ refresh token
  • ไม่ตรวจ expiry ของ JWT — token หมดอายุยังเข้า shell ได้ จนกว่า API จะตอบ TOKEN_EXPIRED / TOKEN_INVALID (ดู บท 7.3)
  • JWT ที่แนบ request มาจาก getToken() ไม่ใช่จาก isLoggedIn()

SSO bypass — /pathLogin/:token/:refreshToken

PathLogin รับ token จาก URL แล้วเก็บลง storage — ทางเข้าจากระบบอื่น

ข้อระวัง: token ใน URL อาจรั่วผ่าน history / logs / referer (ดู บท 7.9)


เมนู dynamic — axiosWorkforce

เมนูไม่ใช่ static array จาก routes.js อย่างเดียว — SiderLayout ดึงจาก workforce REST ผ่าน axiosWorkforce (src/config/axios-workforce.js)

ขั้นตอนใน componentDidMount

911:923:vtrc-rc-backoffice/src/components/SiderLayout/index.js
    const dataMenu = await axiosWorkforce.get("fetchgroupmenu");
    console.log("dataMenu", dataMenu)
    if (dataMenu.data) {
      let MainMenus = MainMenu;
      for (var data of dataMenu.data.data) {
        MainMenus = [...MainMenus, data];
      }
      this.setState({
        fetchMainMenu: MainMenus,
      });
    }

    const dataSideMenu = await axiosWorkforce.get("fetchsidemenu");
ขั้นการเรียกผล
1axiosWorkforce.get("fetchgroupmenu")กลุ่มเมนูหลัก รวมเข้ากับ MainMenu คงที่ในไฟล์
2axiosWorkforce.get("fetchsidemenu")รายการเมนูย่อย (filter menuLevel === 2 + children)
3setStaterender AntD Menu / ลิงก์ไป path

base URL ของ axiosWorkforce คือ .../workforce-api/api/restful (prod/UAT ตาม origin) — header Authorization + apiKey ถูกแนบโดย interceptor ของ AxiosBO (getToken() + apiKeyNews())

ไม่ใช่ axios.post("/api/fetchgroupmenu") และ ไม่มี getApikey() ใน flow นี้

ข้อดี / ข้อเสีย

  • เพิ่มเมนูได้จาก backend โดยไม่ต้อง deploy frontend เสมอ
  • โหลดช้าขึ้น (รอ 2 GET) และถ้า workforce REST ล่ม เมนูไม่ขึ้น
  • debug ต้องดู Network ของ fetchgroupmenu / fetchsidemenu ไม่ใช่แค่ routes.js

checkShowMenu — กรองเมนูฝั่ง frontend อ่อน

300:318:vtrc-rc-backoffice/src/components/SiderLayout/index.js
  checkShowMenu = (key) => {
    const { fetchMenuByRole } = this.state;

    let checkMenu = true;
    if (key == "set_rule") {


    } else if (key == "ShowTitleConfig" || key == "ShowTitleCreate") {
      checkMenu = true;
    } else if (fetchMenuByRole.length !== 0) {
      {
        fetchMenuByRole
          .map((routesNew) => (checkMenu = true));
      }
    }

    return checkMenu;
  };

ในทางปฏิบัติมักคืน true — สิทธิ์จริงอยู่ที่ backend (GraphQL @auth / REST ของแต่ละ service)


โครงสร้างของ SiderLayout

ไฟล์ใหญ่ (~1,168 บรรทัด) รวม shell layout, ดึง profile, เมนู, breadcrumb, ลิงก์เปิดแท็บใหม่

คำแนะนำเมื่อแก้

  • อย่า refactor ทั้งไฟล์ในครั้งเดียว
  • ถ้าแก้เมนู ให้ grep fetchgroupmenu / fetchsidemenu / axiosWorkforce
  • มี snapshot _bk ในโฟลเดอร์เดียวกัน — ห้ามใช้เป็นต้นแบบ

React Router v5

APIใช้ทำอะไร
BrowserRouterroot router
Switchเลือก match path แรกที่ตรง
Routedefine path + component
Redirectส่งไป /login เมื่อยังไม่ login
Link / withRouterนำทางภายในแอป

ไม่ใช่ React Router v6 — ไม่มี <Routes> / useNavigate


เปรียบเทียบกับ vtrc-web (ดู Volume 8 บท 6.4)

ด้านvtrc-rc-backofficevtrc-web
React Routerv5v5
Route tableroutes.js ขนาดใหญ่routes.js
เมนูdynamic (axiosWorkforce GET)static เป็นหลัก
Auth gateisLoggedIn() boolean + Redirectpattern คล้ายกัน
SSO bypass/pathLogin/:token/:refreshToken(ดู Volume 6)
PrivateRouteไม่มี(ดู Volume 6)

เคล็ดลับตอนทำงานจริง

ตอนเพิ่มหน้าใหม่

  1. สร้างหน้าใน src/pages/...
  2. เพิ่ม entry ใน src/config/routes.js
  3. ถ้าต้องโชว์ในเมนู — ต้องมีข้อมูลเมนูฝั่ง workforce/backend ที่ fetchgroupmenu / fetchsidemenu ส่งกลับ
  4. ทดสอบเข้า path ตรง ๆ ก่อน

ตอนเมนูไม่แสดง

  1. Network: fetchgroupmenu / fetchsidemenu สำเร็จหรือไม่
  2. ตรวจว่า token + apiKey ถูกแนบโดย AxiosBO
  3. เทียบ pathUrl / menuNameEn กับ entry ใน routes.js

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

ไป บท 7.5 GraphQL catalog