ใน Blade เราเขียน route('posts.edit', $post) จนเป็นนิสัย เปลี่ยน path ใน routes/web.php แล้วลิงก์ทุกที่ก็ตามไปเอง พอย้ายมาเขียนหน้าเว็บด้วย React ผ่าน Inertia นิสัยนี้หายไป เพราะ JavaScript ไม่รู้จัก route ของ Laravel ที่ผ่านมาเราก็มักแก้ด้วยการพิมพ์ URL เป็น string ไว้ในหน้า React หรือส่ง URL มาเป็น props จาก controller ก็ใช้ได้ แต่ string ที่พิมพ์ไว้กระจายอยู่ทั่วโปรเจกต์ และไม่มีใครเตือนเราตอนที่ path ฝั่ง PHP เปลี่ยน
Laravel Wayfinder เป็นแพ็กเกจของทีม Laravel ที่เอา route() กลับมาให้ฝั่ง JavaScript
Wayfinder สร้างฟังก์ชันจาก route ให้เรา
Wayfinder อ่าน route ทั้งหมดของแอป แล้วเขียนไฟล์ TypeScript ออกมาสองชุด
resources/js/routes/จัดตามชื่อ route เช่น route ชื่อadmin.posts.editอยู่ที่@/routes/admin/postsในชื่อeditresources/js/actions/จัดตาม controller เช่น@/actions/App/Http/Controllers/PostControllerมีฟังก์ชันตามชื่อเมธอด
เว็บนี้ใช้ชุด routes เป็นหลัก เพราะชื่อ route สั้นและอ่านง่ายกว่า ไฟล์ที่ได้หน้าตาประมาณนี้ (ตัดคอมเมนต์ออกบางส่วน)
/**
* @see \App\Http\Controllers\Blog\BlogController::index
* @route '/blog'
*/
export const index = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({
url: index.url(options),
method: 'get',
})
แต่ละฟังก์ชันคืนค่าเป็น object ที่มีทั้ง url และ method และมีคอมเมนต์บอกว่ามาจาก controller ไหน บรรทัดที่เท่าไร editor จึงกดตามไปดูฝั่ง PHP ได้
ใช้ใน Inertia ได้ทั้ง Link, router และ form
ฟังก์ชันที่ได้ส่งเข้า Inertia ได้ตรง ๆ เพราะ Inertia v3 รับ object ที่มี url กับ method อยู่แล้ว ตัวอย่างจากหน้า admin ของเว็บนี้
import { edit } from '@/routes/admin/posts';
import { destroy, update } from '@/routes/admin/leads';
// ลิงก์ไปหน้าแก้ไข ส่ง id เป็นพารามิเตอร์ของ route
<Link href={edit(post.id)}>{post.title}</Link>
// ลบรายการ method เป็น DELETE ตามที่ route กำหนดไว้
router.delete(destroy(lead.id), visit);
// ส่งฟอร์มด้วย useForm
form.submit(update(item.id), { preserveScroll: true });
ถ้าต้องใช้แค่ URL เป็น string ก็เรียก .url() หรืออ่าน .url จากผลลัพธ์ ส่วน query string ใส่ผ่าน query
<Button as={Link} href={review.url({ query: { type: 'blog' } })}>ตรวจคิว</Button>
// ได้ /admin/posts/review?type=blog
route ที่มี model binding อย่าง /posts/{post:slug} รับได้ทั้งค่าตรง ๆ และ object ที่มี key ตรงชื่อพารามิเตอร์ ส่วน <Form> ของ Inertia ใช้ {...store.form()} ได้ถ้าเปิด wayfinder({ formVariants: true }) ใน Vite plugin (หรือ --with-form ถ้าสั่งเอง) ดูรายละเอียดทั้งหมดได้ใน README ของ Wayfinder
Vite plugin สร้างไฟล์ให้ทุกครั้งที่แก้ route
ติดตั้งสองฝั่ง แล้วเพิ่ม plugin ใน vite.config.js
composer require laravel/wayfinder
npm install -D @laravel/vite-plugin-wayfinder
import { wayfinder } from '@laravel/vite-plugin-wayfinder';
export default defineConfig({
plugins: [
laravel({ input: ['resources/css/app.css', 'resources/js/app.jsx'] }),
react(),
wayfinder(),
],
});
plugin รัน php artisan wayfinder:generate ตอนเริ่ม npm run dev และ npm run build ระหว่าง dev ก็รันใหม่ทุกครั้งที่ไฟล์ใน routes/ หรือ controller ใต้ app/Http เปลี่ยน เพิ่ม route ใหม่แล้ว import ใช้ในหน้า React ได้ทันทีโดยไม่ต้องสั่งอะไรเพิ่ม
ไฟล์ที่สร้างเป็นของที่สร้างใหม่ได้เสมอ เราจึงใส่ไว้ใน .gitignore ไม่ commit
/resources/js/actions
/resources/js/routes
/resources/js/wayfinder
ถ้าใช้ ESLint หรือเครื่องมือสแกนโค้ด ให้ข้ามสามโฟลเดอร์นี้ไปด้วย เพราะเป็นโค้ดที่เราไม่ได้เขียนเอง
build จะล้มถ้าหน้าเว็บยังเรียก route ที่ลบไปแล้ว
เว็บนี้เขียน React เป็น .jsx ไม่ได้ใช้ TypeScript เต็มรูปแบบ แต่ Wayfinder ยังช่วยได้มาก เพราะชื่อ route กลายเป็นชื่อ export ของไฟล์ ถ้าเราลบหรือเปลี่ยนชื่อ route ฝั่ง PHP แล้วยังมีหน้าที่ import ชื่อเดิมอยู่ Vite 8 จะหยุด build ด้วย error แบบนี้
[MISSING_EXPORT] "edit" is not exported by "resources/js/routes/admin/posts/index.ts".
URL ที่พิมพ์เป็น string ไม่มีอะไรเตือน ผู้ใช้กดแล้วเจอ 404 บน production ส่วน Wayfinder ทำให้ CI ที่รัน build ก่อน deploy ล้มให้เห็นก่อน ถ้าใช้ TypeScript ก็ได้เพิ่มอีกชั้น คือ editor เตือนเมื่อส่งพารามิเตอร์ผิดหรือขาด
ต้องมี PHP ทุกที่ที่รัน build
plugin เรียก php artisan จริง เครื่องที่รัน npm run build จึงต้องมี PHP และ boot แอปได้ เรื่องนี้มีผลสามที่
- ใน CI ต้องติดตั้ง PHP และ
composer installก่อนขั้น build ฝั่ง JavaScript ถ้าเดิมเราแยก job build frontend ไว้บน runner ที่มีแค่ Node ต้องย้ายมาอยู่ต่อจาก composer - ตอน deploy ถ้ามี route cache ค้างอยู่ Wayfinder จะอ่าน route จาก cache นั้น workflow ของเว็บนี้จึงรัน
php artisan route:clearก่อนnpm run buildแล้วค่อยสร้าง cache ใหม่หลัง build เสร็จ ลำดับเต็มอยู่ในโพสต์ลำดับขั้นตอน deploy - build ครั้งเดียวใช้หลายที่ ถ้า build ใน CI แล้วส่งไฟล์ไป server หลายตัว ให้ build ด้วย
.envที่APP_ENV=productionเพื่อให้ route ที่สร้างตรงกับที่ production ใช้จริง วิธี build ครั้งเดียวแล้วส่งไปทั้งก้อนอยู่ในโพสต์ deploy แบบไม่มี downtime
ทีมที่ทำงานหลายคนก็มีจุดเล็ก ๆ ที่เจอบ่อย คือดึงโค้ดที่เพิ่ม route ใหม่มาแล้วยังไม่ได้รัน npm run dev Vite จะฟ้องว่า import ไม่เจอ ซึ่งอ่านแล้วไม่ชี้ไปที่ routes เลย เราแก้เรื่องนี้ด้วย git hook ที่รัน wayfinder:generate ให้หลัง git pull
Wayfinder ทำให้ route ใน Laravel เป็นที่เดียวที่บอกว่า URL ของแอปคืออะไร ทั้งฝั่ง Blade และฝั่ง React ส่วนเรื่องจัดรูปแบบโค้ด React ในโปรเจกต์เดียวกัน ลองอ่านต่อได้ที่ ESLint และ Prettier สำหรับ Inertia กับ React ครับ






