วางโครงสร้าง Blade Template ใน Laravel ให้ Scale ได้จริง
แนวทางจัดโครงสร้าง Blade Template แบบ Domain-based ที่ Senior Developer ใช้จริง พร้อมเทคนิค Route::view() สำหรับ Prototype UI ก่อนต่อ Controller

ศิริชัย ธีรภัทรสกุล
เปิดโปรเจกต์ Laravel ที่ทำมา 1 ปีแล้วหา view ไม่เจอไหมครับ? ถ้าเคยเจอปัญหานี้ คุณไม่ได้เป็นคนเดียว
โปรเจกต์ Laravel ส่วนใหญ่เริ่มต้นด้วยการโยนไฟล์ Blade ทั้งหมดไว้ใน resources/views ตรงๆ พอ Feature เพิ่มขึ้น ทีมโตขึ้น ไฟล์ก็เริ่มปนกันจนหา component เฉพาะของแต่ละส่วนไม่เจอ
จากประสบการณ์ของเราในการดูแลโปรเจกต์ Laravel ระดับ SaaS และ E-commerce บทความนี้จะพาไปดูวิธีจัดโครงสร้าง resources/views/web แบบ Domain-based ที่ดูแลระยะยาวง่าย พร้อมเทคนิค Route::view() สำหรับ Prototype UI ก่อนเขียน Controller จริง
สารบัญ
- ทำไมโครงสร้าง Default ของ Laravel ไม่พอสำหรับโปรเจกต์ใหญ่
- หลักการแบ่ง 4 ระดับสำหรับ resources/views/web
- กฎทอง: Component เฉพาะ Domain ต้องอยู่ใน Domain นั้น
- Route::view() เทคนิค Prototype UI ก่อนต่อ Controller
- คำถามที่พบบ่อย
ทำไมโครงสร้าง Default ของ Laravel ไม่พอสำหรับโปรเจกต์ใหญ่
Laravel ให้ resources/views มาแบบเปิดกว้าง ไม่บังคับรูปแบบใดๆ ข้อดีคือยืดหยุ่น แต่ข้อเสียคือทีมมักแยกไฟล์ตาม "ประเภท" เช่น รวม Blade ทุกหน้าไว้ระดับเดียวกัน โดยไม่แยกตามหน้าที่การใช้งาน
พอโปรเจกต์มี Business Logic เยอะขึ้น เช่น ระบบ Order, Product, Customer ปนกันในโฟลเดอร์เดียว การแก้ไขหรือเพิ่ม Feature ใหม่จะใช้เวลาค้นหาไฟล์นานขึ้นเรื่อยๆ ทีมที่เข้ามาทำงานต่อก็ต้องไล่เปิดทีละไฟล์เพื่อทำความเข้าใจ
ทางออกคือแยกโครงสร้างตาม หน้าที่ของ View และ Business Domain ตั้งแต่ต้น แทนที่จะแยกตามประเภทไฟล์อย่างเดียว
หลักการแบ่ง 4 ระดับสำหรับ resources/views/web
แนวทางที่ใช้ได้ผลจริงคือแบ่งเป็น 4 ระดับ:
1. layouts/ — โครงสร้างหลักของหน้า เช่น app.blade.php, guest.blade.php รับผิดชอบส่วนที่ทุกหน้าต้องมีร่วมกัน อย่าง Navbar, Footer และจุด @yield('content')
2. components/ui และ components/common — UI ที่ใช้ซ้ำทั่วทั้งระบบ เช่น Button, Modal, Alert, Pagination สิ่งเหล่านี้ไม่ผูกกับ Domain ไหนโดยเฉพาะ จึงเก็บไว้ที่ระดับกลางได้
3. pages/ — หน้า Static หรือ Landing เช่น Home, About, Contact ที่ไม่มี Logic ซับซ้อน ควรประกอบด้วย Component มากกว่าเขียน HTML ซ้ำเอง
4. Domain folders — โฟลเดอร์แยกตาม Business Domain เช่น products/, orders/, blog/, customers/ นี่คือส่วนที่สำคัญที่สุดสำหรับโปรเจกต์ที่มีแนวโน้มโต เพราะเมื่อกลับมาเปิดโปรเจกต์อีก 1 ปี ทีมจะรู้ทันทีว่าไฟล์ไหนอยู่ Domain ไหน
กฎทอง: Component เฉพาะ Domain ต้องอยู่ใน Domain นั้น
หลักที่แนะนำเสมอคือ ถ้า Component ใช้เฉพาะกับ Domain ใด อย่าเอาไปกองไว้ใน components/ กลาง
ตัวอย่างเช่น product-card.blade.php ไม่ควรอยู่ที่ components/product-card.blade.php แต่ควรอยู่ที่ products/components/product-card.blade.php แบบนี้จะได้โครงสร้าง:
products/components/เก็บ card, gallery, price, filter ของ Product โดยเฉพาะorders/components/เก็บ order-card, order-status, order-summaryblog/components/เก็บ post-card, post-meta, related-posts
ผลลัพธ์คือทุก Domain ดูแลตัวเองได้ (Self-contained) และเวลาลบหรือย้าย Feature ทั้งหมด ก็แค่จัดการทั้งโฟลเดอร์เดียว ไม่ต้องไล่หาไฟล์กระจัดกระจาย
Route::view() เทคนิค Prototype UI ก่อนต่อ Controller
อีกเทคนิคที่ช่วยให้ทำงานเร็วขึ้นคือใช้ Route::view() ในช่วงออกแบบ UI ก่อนที่จะมี Controller จริง
Route::view('/products', 'web.products.index');
Route::view('/products/show', 'web.products.show');
วิธีนี้ทำให้ทีม Frontend ออกแบบ Layout และ UX ให้เสร็จก่อนได้ โดยไม่ต้องรอ Controller, Model หรือ Query ให้พร้อม พอ Business Logic เริ่มชัดเจน ค่อย Migrate ไปเป็น:
Route::get('/products', [ProductController::class, 'index']);
Route::get('/products/{product:slug}', [ProductController::class, 'show']);
การแยก Phase แบบนี้ช่วยให้ทีม Backend และ Frontend ทำงานคู่ขนานกันได้ โดยไม่ติดคอขวดที่ต้องรอกัน
คำถามที่พบบ่อย
ควรแยก Component ตาม Domain ตั้งแต่เริ่มโปรเจกต์เลยไหม?
ควรเริ่มตั้งแต่ต้น โดยเฉพาะถ้ารู้อยู่แล้วว่าโปรเจกต์จะมี Business Logic หลาย Domain การรื้อโครงสร้างทีหลังเมื่อไฟล์เยอะแล้วจะเสียเวลากว่ามาก
resources/views/web ต่างจาก resources/views ปกติยังไง
การเพิ่มโฟลเดอร์ web/ เป็นการแยก Frontend ฝั่งเว็บหลักออกจากส่วนอื่น เช่น Admin Panel หรือ Email Template ทำให้แต่ละส่วนไม่ปนกันเมื่อโปรเจกต์มีหลาย Interface
Route::view() ใช้กับ Dynamic Parameter ได้ไหม
ใช้ได้ในระดับพื้นฐาน แต่ถ้าต้องการนำ Parameter เช่น {slug} ไปแสดงผลใน Blade จะต้องเปลี่ยนไปใช้ Closure หรือ Controller แทน เพราะ Route::view() ไม่รองรับการส่ง Parameter เข้า View โดยตรง
เมื่อไหร่ควรย้ายจาก Route::view() ไป Controller
ทันทีที่หน้านั้นเริ่มต้องดึงข้อมูลจาก Database หรือมี Logic ตรวจสอบเงื่อนไข ควรย้ายไป Controller เพื่อแยก Concern ให้ชัดเจน
โครงสร้างนี้ใช้กับ Livewire หรือ Inertia ได้ไหม
ได้ครับ หลักการแบ่งตาม Domain และแยก Component ยังใช้ได้เหมือนกัน เพียงแต่ปรับโฟลเดอร์ให้รองรับไฟล์ .blade.php ของ Livewire Component หรือ Page ของ Inertia เพิ่มเติม
เคล็ดลับ
- อย่าแยกไฟล์ Blade ตามประเภทอย่างเดียว ให้แยกตามหน้าที่และ Business Domain
- แบ่งโครงสร้างเป็น 4 ระดับ: layouts, components กลาง, pages, และ Domain folders
- Component เฉพาะ Domain ต้องอยู่ใน Domain นั้น ไม่กองรวมใน components/ กลาง
- ใช้ Route::view() เพื่อ Prototype UI ก่อนต่อ Controller ในช่วงเริ่มโปรเจกต์
- ย้ายจาก Route::view() ไป Controller ทันทีที่มี Logic หรือ Database เข้ามาเกี่ยวข้อง
สรุป
การวางโครงสร้าง Blade Template ตั้งแต่ต้นด้วยแนวคิด Domain-based ไม่ได้ทำให้เขียนโค้ดเร็วขึ้นในวันแรก แต่จะเห็นผลชัดเจนเมื่อโปรเจกต์โตขึ้นและทีมขยาย
ลองนำโครงสร้างนี้ไปปรับใช้กับโปรเจกต์ Laravel ที่กำลังทำอยู่ แล้วจะรู้สึกได้ว่าการดูแลโค้ดในระยะยาวง่ายขึ้นจริงๆ
