什么是 Wayfinder
Laravel Wayfinder 是一个零摩擦地把 Laravel 后端和 TypeScript 前端桥接起来的包。它会根据控制器和路由自动生成完全类型化的 TypeScript 函数,让你在前端可以像调用普通函数一样直接调用 Laravel 的端点。
URL 硬编码、路由参数手动维护、后端变化人工同步——这些统统不需要了。
Wayfinder 目前是 Beta 版(当前 v0.1.x)。在 v1.0.0 发布前 API 可能会变化。所有重要变更都会记录在 CHANGELOG 中。
Ziggy 与 Wayfinder 的差异
什么是 Ziggy
Ziggy 是长期以来在 Laravel 生态里广泛使用的路由 helper。它把 Laravel 的路由定义暴露给 JavaScript 端,让你可以用 route('posts.show', { id: 1 }) 这样的形式生成 URL。
为什么被 Wayfinder 取代
Ziggy 用字符串来表达路由名和参数,与 TypeScript 的契合度有限。路由名的拼写错误或参数名错误只能在运行时才暴露。
Wayfinder 是 TypeScript first 的设计,会把控制器方法生成为可导入的函数 。
对比项 Ziggy Wayfinder 引用路由的方式 route('posts.show', { id: 1 })import { show } from "@/actions/..."类型安全 类型定义有限 完整的 TypeScript 类型 IDE 支持 补全能力弱 补全和类型检查完全可用 树摇 所有路由都会打进 bundle 只把用到的路由打进 bundle 生成时机 运行时注入 构建时静态生成
在基于 Inertia 的 Laravel 启动套件(React / Vue / Svelte)中,Wayfinder 已被默认采用。
1. 用 Composer 安装服务端包
composer require laravel/wayfinder
2. 用 NPM 安装 Vite 插件
npm i -D @laravel/vite-plugin-wayfinder
3. 在 vite.config.js 中加入插件
import { wayfinder } from "@laravel/vite-plugin-wayfinder" ;
import { defineConfig } from "vite" ;
import laravel from "laravel-vite-plugin" ;
export default defineConfig ({
plugins: [
laravel ({
input: [ "resources/js/app.ts" ],
refresh: true ,
}),
wayfinder (),
] ,
}) ;
加上 Vite 插件后,开发服务器运行时,只要 PHP 文件或路由文件发生变化,TypeScript 文件就会被自动重新生成。
生成 TypeScript 定义文件
用 wayfinder:generate 命令生成 TypeScript 文件。
php artisan wayfinder:generate
默认会在 resources/js 下生成 3 个目录。
resources/js/
├── actions/ # 控制器 action 的函数
│ └── App/Http/Controllers/
│ └── PostController.ts
├── routes/ # 命名路由的函数
│ └── post.ts
└── wayfinder/ # 类型定义文件
└── types.ts
生成的文件会在每次构建时完全重生成,建议把它们加入 .gitignore。可以把 wayfinder、actions、routes 3 个目录一次性排除掉。
想改输出位置时用 --path 参数。
php artisan wayfinder:generate --path=resources/js/api
也可以只生成 action 或只生成路由。
php artisan wayfinder:generate --skip-actions # 只生成路由
php artisan wayfinder:generate --skip-routes # 只生成 action
基本用法
导入并使用 action
生成 PostController 的 show 方法对应 URL 的示例。
import { show } from "@/actions/App/Http/Controllers/PostController" ;
show ( 1 );
// { url: "/posts/1", method: "get" }
只需要 URL 时用 .url()。
show . url ( 1 ); // "/posts/1"
也可以指定具体的 HTTP 方法。
show . head ( 1 ); // { url: "/posts/1", method: "head" }
参数传递方式
Wayfinder 的函数支持多种形态的参数。
import { update } from "@/actions/App/Http/Controllers/PostController" ;
// 单个参数
show ( 1 );
show ({ id: 1 });
// 多个参数
update ([ 1 , 2 ]);
update ({ post: 1 , author: 2 });
update ({ post: { id: 1 }, author: { id: 2 } });
路由上指定了键绑定(/posts/{post:slug})时,可以直接传对应的值。
// 当路由是 /posts/{post:slug} 时
show ( "my-new-post" );
show ({ slug: "my-new-post" });
一次性导入整个控制器
也可以直接把整个控制器导入,然后调用其方法。
import PostController from "@/actions/App/Http/Controllers/PostController" ;
PostController . show ( 1 );
PostController . index ();
整个控制器导入时不会享受到树摇,所有 action 都会被打进 bundle。想让最终 bundle 更小,建议逐个导入。
单一 action 控制器
单一 action 控制器(Invokable Controller)对应的导入函数可以直接调用。
import StorePostController from "@/actions/App/Http/Controllers/StorePostController" ;
StorePostController (); // { url: "/posts", method: "post" }
导入命名路由
按路由名访问时使用 routes/ 下的文件。
import { show } from "@/routes/post" ;
// 路由名是 `post.show` 时
show ( 1 ); // { url: "/posts/1", method: "get" }
查询参数
所有 Wayfinder 函数都能通过 query 选项加查询参数。
import { show } from "@/actions/App/Http/Controllers/PostController" ;
show ( 1 , { query: { page: 1 , sort_by: "name" } });
// { url: "/posts/1?page=1&sort_by=name", method: "get" }
要和当前 URL 的查询参数合并时用 mergeQuery。
// 当前 URL:/posts/1?page=1&sort_by=category&q=shirt
show . url ( 1 , { mergeQuery: { page: 2 , sort_by: "name" } });
// "/posts/1?page=2&sort_by=name&q=shirt"
// 要删除某个参数,传 null
show . url ( 1 , { mergeQuery: { sort_by: null } });
// "/posts/1?page=1&q=shirt"
表单变体
想在传统 HTML 表单中使用时,加上 --with-form 参数生成,然后用 .form 变体。
php artisan wayfinder:generate --with-form
import { store , update } from "@/actions/App/Http/Controllers/PostController" ;
// React 示例
const Page = () => (
< form { ... store . form () } >
{ /* <form action="/posts" method="post"> */ }
</ form >
);
const EditPage = () => (
< form { ... update . form ( 1 ) } >
{ /* <form action="/posts/1?_method=PATCH" method="post"> */ }
</ form >
);
Inertia 与 Wayfinder 的搭配
把 Inertia 的 form helper 与 Wayfinder 组合起来,就可以在不写任何 URL 字符串的情况下完成表单提交。
import { useForm } from "@inertiajs/react" ;
import { store } from "@/actions/App/Http/Controllers/PostController" ;
const form = useForm ({ name: "My Post" });
form . submit ( store ()); // 提交到 POST /posts
Link 组件里也可以这样用。
import { Link } from "@inertiajs/react" ;
import { show } from "@/actions/App/Http/Controllers/PostController" ;
const Nav = () => (
< Link href = { show ( 1 ) } > 查看文章 </ Link >
);
在启动套件中的采用
用 laravel new 创建新项目并选择 React / Vue / Svelte 时,Wayfinder 会作为自动配置好的组件被引入。启动套件里包含:
Composer 包 laravel/wayfinder
NPM 包 @laravel/vite-plugin-wayfinder
vite.config.js 里已加入插件配置
生成目录已经加入 .gitignore
在已有项目中按上面的步骤手动引入同样可行。
处理和保留字冲突的方法名
对 delete、import 这类和 JavaScript 保留字重名的控制器方法,会加上 Method 后缀。
// 控制器里有 delete 方法时
import { deleteMethod } from "@/actions/App/Http/Controllers/PostController" ;
deleteMethod ( 1 ); // { url: "/posts/1", method: "delete" }
当前状态(v0.1.x)
当前稳定版位于 v0.1.x 分支。截至 2026 年 3 月的最新版本是 v0.1.15 。
v0.1.x 系列主要更新记录
版本 主要内容 v0.1.15 支持 Laravel 13,修复 Blade 视图崩溃 v0.1.13 改进查询参数在 TypeScript strict 模式下的兼容性 v0.1.7 支持从前端指定 URL 默认参数 v0.1.6 引入 Vite 插件 v0.1.5 支持 PHP 8.2,兼容缓存过的路由 v0.1.0 初次发布
next 分支中开发中的下一代能力
next 分支正在开发相较 v0.1.x 大幅扩展的下一版。
next 分支可以通过 dev-next 约束安装,但 API 可能大幅变化,不建议在生产环境使用。
composer require laravel/wayfinder:dev-next
生成的 TypeScript 范围大幅扩展
v0.1.x 只针对路由和控制器 action,而下一版会把下面的东西都作为 TypeScript 生成。
class StorePostRequest extends FormRequest
{
public function rules () : array
{
return [
'title' => [ 'required' , 'string' , 'max:255' ],
'content' => [ 'required' , 'string' ],
'tags' => [ 'nullable' , 'array' ],
'tags.*' => [ 'string' ],
];
}
}
从上面的 Form Request 会生成:
export type Request = {
title : string ;
content : string ;
tags ?: string [] | null ;
};
Eloquent 模型的类型生成
class User extends Model
{
protected $casts = [
'email_verified_at' => 'datetime' ,
'is_admin' => 'boolean' ,
];
public function posts () : HasMany
{
return $this -> hasMany ( Post :: class );
}
}
上面的模型会在 types.d.ts 里生成类型:
export namespace App . Models {
export type User = {
id : number ;
name : string ;
email : string ;
email_verified_at : string | null ;
is_admin : boolean ;
posts : App . Models . Post [];
};
}
PHP Enum 转 TypeScript
enum PostStatus : string
{
case Draft = 'draft' ;
case Published = 'published' ;
case Archived = 'archived' ;
}
类型和常量都会生成:
// 类型定义(types.d.ts)
export namespace App . Enums {
export type PostStatus = "draft" | "published" | "archived" ;
}
// 常量(App/Enums/PostStatus.ts)
export const Draft = "draft" ;
export const Published = "published" ;
export const Archived = "archived" ;
export const PostStatus = { Draft , Published , Archived } as const ;
输出目录变化
v0.1.x 中会分成 actions/、routes/、wayfinder/ 3 个目录,而下一版把它们汇集到 resources/js/wayfinder 下。
resources/js/wayfinder/
├── App/Http/Controllers/
│ └── PostController.ts # action 函数(actions/ 被废弃)
├── routes/
│ └── post.ts # 命名路由(不变)
├── broadcast-channels.ts # 广播频道
├── broadcast-events.ts # 广播事件
└── types.d.ts # 全部类型定义(从 types.ts 改名)
从 v0.1.x 到 next 的主要变化
导入路径从 @/actions/... 变成 @/wayfinder/...
废弃 --skip-actions、--skip-routes、--with-form 参数,统一改到配置文件
types.ts 改名为 types.d.ts
Laravel Wayfinder 把 Ziggy 提供的“在 JavaScript 里引用 Laravel 路由”的能力用 TypeScript first 的方式重新设计。通过导入并调用生成的函数,类型安全、IDE 支持和树摇都得到了显著改善。
即便是当前的 v0.1.x,对路由和控制器 action 的类型安全引用也已经足够好用,并且在基于 Inertia 的 Laravel 启动套件中已成为标配。next 分支正在开发的下一版会把 Form Request、Eloquent 模型、Enum、Inertia 页面 props 也一起生成为 TypeScript,朝着更全面的类型安全基座演进。
Laravel Wayfinder GitHub 源代码、CHANGELOG、Issue 请查看这里。
Vite Plugin Wayfinder Vite 插件的配置项详情请查看这里。