Nova 资源类必须置于 app/Nova 目录、类名与文件名严格一致并继承 Nova\Resource;fields() 中关联字段需在 indexQuery() 预加载,自定义字段用闭包且不可搜索;列表卡顿或报错主因是未正确实现 indexQuery() 和 relatableQuery()。

Nova 不是“装完就能用”的后台模板,它依赖你对模型、字段映射和查询生命周期的明确控制——跳过 model() 实现、直接在 fields() 里写关联字段名、不重写 indexQuery(),列表页必然变卡甚至报错。
Resource 类必须放在 app/Nova 且命名严格匹配
Nova 只扫描 app/Nova 目录,不递归子目录;类名必须与文件名完全一致(含大小写),且必须继承 Nova\Resource。例如:
-
app/Nova/Post.php→ 类名必须是Post,不能是BlogPost或post - 对应模型是
App\Models\Article?没关系,Resource 类仍叫Article,model()方法里返回App\Models\Article::class - 如果放错路径(比如
app/Http/Nova/Post.php)或类名不一致,Nova 根本不会识别它,侧边栏也不会出现
fields() 里字段名 ≠ 模型属性名,嵌套字段必须预加载
写 Text::make('name') 默认读写数据库 name 字段,但一旦你要显示 user.name,就不能只靠点号语法:
-
Text::make('Author', 'user.name')看似可行,但若没预加载user关系,列表页会触发 N+1 查询 - 正确做法:在 Resource 类里重写
indexQuery(),显式加with('user') - 自定义值字段(如状态标签)要用闭包:
Text::make('Status', function () { return $this->active ? 'Online' : 'Offline'; }),但这类字段不可编辑、不参与搜索 - 字段名带空格或特殊字符?没问题,但第二个参数(数据源)必须是真实存在的属性、关系名或访问器名
列表卡顿或 500 错误?先查 indexQuery() 和 relatableQuery()
默认 indexQuery() 就是 Model::query(),没有任何优化。只要 fields() 里用了关联字段、BelongsTo 或 HasMany,就必须干预:
-
indexQuery()控制资源列表主查询:加with('category', 'author')、select()明确字段、用when()动态条件 -
relatableQuery()控制下拉选择、搜索弹窗里的可选项范围:比如限制用户只能选自己创建的分类,就在这里加where('user_id', $request->user()->id) - 别在
fields()里用BelongsTo::make('Category')->searchable()却不重写relatableQuery()——搜索时会查全表,超时或崩溃 - 调试技巧:在
indexQuery()里临时加dd($query->toSql(), $query->getBindings()),看实际生成的 SQL 是否合理
安装后登录 403 或空白页?检查 config/nova.php 和守卫配置
Nova 默认用 web 守卫,但如果你改过 config/auth.php 中的默认守卫,或用了多守卫(如 admin),Nova 就会拒绝登录:
- 确认
config/nova.php中的'guard' => 'web'与auth.guards.web配置一致 - 若用自定义守卫(如
admin),需同步改config/nova.php的guard项,并确保该守卫的provider正确指向用户模型 - 环境变量
NOVA_ENV=local仅影响部分调试行为,不影响权限逻辑;真正起作用的是gate配置项——它决定谁能看到 Nova,别漏掉App\Providers\NovaServiceProvider里的Gate::define块
最常被忽略的不是语法错误,而是查询上下文的错位:indexQuery() 返回的集合,要能支撑你在 fields() 里写的每一个点号路径;而 relatableQuery() 返回的结果,必须兼容 Nova 内部的搜索和关联逻辑——这两处不配平,其他所有配置都白搭。


















