Laravel Head
简介
Laravel Head 提供流畅的 API,用于管理应用文档的 <head> 元素,包括 title 与 meta 标签、Open Graph 元数据、规范 URL、robots 指令、性能提示以及结构化数据。它可与 Blade、Livewire 和 Inertia 配合使用。
安装
你可以使用 Composer 包管理器安装 Laravel Head:
composer require laravel/head快速开始
在服务提供者中注册站点范围的默认值:
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;
Head::defaults(fn (HeadBuilder $head) => $head
->title('Laravel', suffix: ' - Laravel')
->description('Build something great.'));在运行时设置页面级元数据:
Head::title($post->title)
->description($post->description);在布局中渲染解析后的标签:
<head>
@head
</head>解析优先级
页面元数据由五个层级解析,按优先级从低到高排列:
- 页面默认值
- 路由组元数据
- 路由元数据
- 运行时元数据
- 错误页元数据
更高层级会按字段覆盖更低层级。例如,运行时的 title 会替换路由上的 title,但不会替换路由上的 description。后续章节介绍如何在各层级设置元数据。关于在 Blade、Livewire 和 Inertia 中渲染已解析元数据,请参阅 渲染。
定义元数据
Laravel Head 允许你通过站点范围默认值、路由元数据、运行时调用以及错误页定义来设置元数据。
默认值
在服务提供者中注册页面默认值:
use Laravel\Head\Enums\OgType;
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;
Head::defaults(function (HeadBuilder $head) {
$head
->title('Laravel', suffix: ' - Laravel')
->description('Build something great.')
->canonical()
->og(siteName: 'Laravel', type: OgType::Website)
->searchableByRobots()
->preconnect('https://fonts.example.com');
});默认值是优先级最低的页面元数据层。若路由、运行时或错误页元数据都未设置 title,则 Laravel 会原样渲染。当更高层级设置了页面 title 时,会应用继承的后缀,因此 Head::title('About') 会渲染为 About - Laravel。若 title 应忽略继承的前缀或后缀,请传入 exact: true。
调用 Head::canonical() 会使用当前请求 URL 渲染规范 URL。若要设置明确 URL,可传入字符串,例如 Head::canonical('/about')。规范 URL 默认会规范化为 https;传入 forceHttps: false 可保留请求的协议。
Robots 指令可以是原始字符串、RobotsRule 枚举值,或两者混合的列表。列表会渲染为逗号分隔的指令,因此 Head::robots([RobotsRule::NoIndex, RobotsRule::NoFollow]) 会渲染为 noindex, nofollow。
为方便起见,searchableByRobots 方法会渲染 all,而 hiddenFromRobots 方法会渲染 none。
路由元数据
你可以直接在路由上定义元数据,这对元数据事先已知的半静态页面尤其有用。
路由与路由组
Route::view('/contact', 'contact')
->name('contact')
->withHead(
title: 'Contact Us',
description: 'Get in touch.',
);共享的路由元数据可在链式调用的任意位置应用到路由组:
Route::withHead(robots: 'noindex, nofollow')
->prefix('admin')
->name('admin.')
->group(function () {
Route::get('/dashboard', DashboardController::class)
->name('dashboard')
->withHead(title: 'Dashboard');
});你也可以为 resource 与 singleton 路由定义元数据:
Route::resource('posts', PostController::class)->withHead(
robots: 'index, follow',
);
Route::singleton('profile', ProfileController::class)->withHead(
title: 'Your Profile',
);withHead 方法通过 Laravel 原生路由元数据 API 存储普通数组。它等价于调用 metadata 方法,并将属性嵌套在 head 键下,因此元数据仍与缓存路由兼容。
命名参数刻意限制为 Laravel Head 内置的路由属性,以便编辑器与静态分析能发现拼写错误。自定义标签构建器注册的路由属性可通过 extensions 传入:
Route::get('/article', ArticleController::class)->withHead(
title: 'Article',
extensions: ['readingTime' => 4],
);支持的属性
受支持的路由属性与流畅构建器方法使用相同的名称:
| Category | Properties |
|---|---|
| Document | title, description, canonical, robots |
| Application metadata | themeColor, applicationName, colorScheme, referrer, viewport, appleWebAppTitle, webAppCapable, appleWebAppStatusBarStyle |
| Social | og, ogImage, ogVideo, ogAudio, twitter, twitterImage |
| Performance | preload, prefetch, preconnect, dnsPrefetch |
| Discovery | alternates, feed, icon, favicon, appleTouchIcon, appleTouchStartupImage, maskIcon, manifest |
| Structured data | schema |
| Custom tags | meta, link |
嵌套选项名称与流畅 API 一样使用 camelCase 命名,例如 forceHttps、siteName 和 secureUrl。
可重复属性(如 ogImage、preload、feed、schema、icon 和 appleTouchStartupImage)既可接受单个值,也可接受列表。
运行时元数据
当某个值要等到请求到达才知道时(例如正在查看的文章标题),你可以在运行时设置:
use Laravel\Head\Facades\Head;
public function __invoke(Post $post): Response
{
Head::title($post->title);
// ...
}通过 Head facade 进行的运行时调用会覆盖依赖请求的路由元数据。控制器与 action 是最常进行这些调用的地方:
use App\Models\Post;
use Laravel\Head\Facades\Head;
public function show(Post $post)
{
Head::title($post->title)
->description($post->description);
return view('posts.show', ['post' => $post]);
}多次运行时调用会按执行顺序合并。对于 title、description、规范 URL、robots 指令等单值字段,后一次调用优先。可重复字段会保留多条记录,但再次添加相同键会更新先前条目。对于 ogImage 方法,URL 即为键:
Head::ogImage('/images/cover.jpg', alt: 'Draft cover')
->ogImage('/images/gallery.jpg', alt: 'Gallery image')
->ogImage('/images/cover.jpg', alt: 'Final cover', width: 1200, height: 630);<meta property="og:image" content="/images/cover.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Final cover">
<meta property="og:image" content="/images/gallery.jpg">
<meta property="og:image:alt" content="Gallery image">从默认值继承的 Open Graph 媒体会作为回退。当路由、运行时或错误页元数据定义了同类型媒体时,默认媒体会被替换而非合并,因此页面的 og:image 优先于站点范围的默认图片。
你可以使用 when 和 unless 方法流畅地定义条件元数据:
Head::title($post->title)
->when($post->isDraft(), fn ($head) => $head->hiddenFromRobots());错误页
通常应在应用的 AppServiceProvider 类的 boot 方法中注册错误页元数据:
use Laravel\Head\ErrorPages;
use Laravel\Head\Facades\Head;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Head::errors(function (ErrorPages $errors) {
$errors->defaults(robots: 'noindex, follow');
$errors->status(
404,
title: 'Page Not Found',
description: 'The page you are looking for could not be found.',
);
});
}defaults 和 status 方法也接受与 Head::defaults() 相同的流畅构建器回调:
use Laravel\Head\ErrorPages;
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;
Head::errors(function (ErrorPages $errors) {
$errors->status(404, fn (HeadBuilder $head) => $head
->title('Page Not Found')
->description('The page you are looking for could not be found.'));
});当为已注册的错误状态渲染响应时,该元数据优先于所有其他层级。
当渲染错误视图或执行响应阶段钩子(例如 Inertia 的 handleExceptionsUsing() 方法)时,Laravel 会自动检测响应状态。若你在 $exceptions->render() 回调中渲染错误响应,请在渲染前调用 Head::status(404),以便应用错误页元数据。
Open Graph
你可以使用 og 方法设置 Open Graph 属性。可重复媒体可通过顶层方法添加,这些方法直接接受命名参数:
use Laravel\Head\Enums\ImageType;
use Laravel\Head\Enums\OgType;
Head::og(type: OgType::Article, title: $post->title)
->ogImage($post->hero_image_url)
->ogImage(
$post->gallery_image_url,
alt: $post->gallery_image_alt,
width: 1200,
height: 630,
type: ImageType::Jpeg,
);ogImage、ogVideo 和 ogAudio 方法以 URL 作为第一个参数,并可在 Open Graph 规范支持的情况下接受可选命名参数,例如 alt、width、height、type 和 secureUrl。
凡是 API 接受图片 type 的地方,你都可以传入 ImageType 枚举值,例如 ImageType::Svg、ImageType::Png、ImageType::Jpeg 和 ImageType::Webp。
INFO
文档的 title 和 description 会自动补全缺失的 og:title 和 og:description 值。
对于没有其他属性的单个 Open Graph 图片,你可以将 image 命名参数传给 og 方法:
Head::og(
type: OgType::Website,
title: $page->title,
description: $page->description,
image: $page->og_image_url,
);og(image: ...) 与 ogImage(...) 写入同一底层图片列表,因此可在调用处选用更清晰的写法。自定义 Open Graph 扩展(例如 product 或 article 属性)可使用 meta 方法。
X / Twitter Cards
若要用与 Open Graph 相同的 title、description 和图片渲染 X / Twitter Cards,请在默认值中注册 twitter():
use Laravel\Head\Enums\TwitterCard;
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;
Head::defaults(fn (HeadBuilder $head) => $head->twitter(
card: TwitterCard::SummaryWithLargeImage,
));然后设置页面级元数据:
Head::title('Introducing Laravel Head')
->description('A fluent API for Laravel document head metadata.')
->ogImage('https://example.com/social.jpg', alt: 'Introducing Laravel Head');这会渲染对应的 Twitter 标签:
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Introducing Laravel Head">
<meta name="twitter:description" content="A fluent API for Laravel document head metadata.">
<meta name="twitter:image" content="https://example.com/social.jpg">
<meta name="twitter:image:alt" content="Introducing Laravel Head">你也可以用显式的 Twitter 值自定义单个页面:
Head::twitter(title: $post->social_title)
->twitterImage($post->social_image_url, alt: $post->title);路由元数据接受 twitter 和 twitterImage。
主题色
你可以全局、按路由或在运行时设置主题色:
Head::themeColor('#0f172a');这会渲染 <meta name="theme-color"> 标签。对于特定媒体条件的主题色,可以使用 Media 枚举:
use Laravel\Head\Enums\Media;
Head::themeColor('#ffffff', media: Media::Light)
->themeColor('#111827', media: Media::Dark);Media 枚举还包含 Portrait 和 Landscape。media 参数也接受自定义媒体查询字符串。
路由元数据通过相同的 camelCase 键支持单个主题色:
Route::view('/dashboard', 'dashboard')->withHead(
themeColor: '#0f172a',
);应用元数据与图标
Laravel Head 提供常见浏览器与应用元数据的方法:
use Laravel\Head\Enums\ImageType;
use Laravel\Head\Enums\Media;
Head::applicationName('Laravel')
->colorScheme('light dark')
->referrer('strict-origin-when-cross-origin')
->viewport('width=device-width, initial-scale=1')
->appleWebAppTitle('Laravel')
->webAppCapable()
->appleWebAppStatusBarStyle('black')
->favicon('/favicon.svg', type: ImageType::Svg)
->icon('/favicon-32x32.png', type: ImageType::Png, sizes: '32x32')
->appleTouchIcon('/apple-touch-icon.png', sizes: '180x180')
->appleTouchStartupImage('/launch.png', media: Media::Portrait)
->maskIcon('/safari-pinned-tab.svg', color: '#111827')
->manifest('/site.webmanifest');favicon 方法是 icon 方法的别名,接受相同的 type、sizes 和 media 参数。
路由元数据使用相同的名称:
use Laravel\Head\Enums\ImageType;
use Laravel\Head\Enums\Media;
Route::view('/dashboard', 'dashboard')->withHead(
applicationName: 'Laravel',
colorScheme: 'light dark',
appleWebAppTitle: 'Laravel',
webAppCapable: true,
appleWebAppStatusBarStyle: 'black',
favicon: [
['href' => '/favicon.svg', 'type' => ImageType::Svg],
['href' => '/favicon-32x32.png', 'type' => ImageType::Png, 'sizes' => '32x32'],
],
appleTouchIcon: ['href' => '/apple-touch-icon.png', 'sizes' => '180x180'],
appleTouchStartupImage: ['href' => '/launch.png', 'media' => Media::Portrait],
manifest: '/site.webmanifest',
);渐进式 Web 应用
pwa 方法会配置可安装 Web 应用所需的常见文档 <head> 标签:
Head::pwa(
name: 'Laravel',
manifest: '/site.webmanifest',
themeColor: '#0f172a',
appleTouchIcon: '/apple-touch-icon.png',
appleWebAppStatusBarStyle: 'black',
);这会渲染应用名称、Web 应用 manifest 链接以及 iOS 独立模式元数据。若提供了主题色、Apple 状态栏样式和 Apple touch 图标,也会一并渲染。创建 Web 应用 manifest 与注册 service worker 仍由你的应用负责。
你可以在默认值或运行时元数据中使用 pwa 方法。路由元数据支持上文所示的各个属性。
性能与发现
Laravel Head 可渲染性能提示、分页链接、语言备选链接以及 feed 发现:
Head::preload(asset('fonts/inter.woff2'), as: 'font', crossorigin: true)
->prefetch(asset('images/next.webp'))
->preconnect('https://cdn.example.com')
->dnsPrefetch('https://analytics.example.com')
->paginate($posts)
->alternates([
'en' => 'https://example.com/en/about',
'fr' => 'https://example.com/fr/about',
'x-default' => 'https://example.com/about',
])
->feed('/feed', title: 'Laravel RSS')
->feed('/feed.atom', type: 'atom', title: 'Laravel Atom');对于本地资源,preloadAsset() 和 prefetchAsset() 会通过 asset() 辅助函数解析 URL,并根据文件扩展名检测 as 属性。字体预加载会自动包含 crossorigin,即使同源字体,preload 规范也要求如此:
Head::preloadAsset('fonts/inter.woff2')
->prefetchAsset('images/next.webp');<link rel="preload" href="https://example.com/fonts/inter.woff2" as="font" crossorigin>
<link rel="prefetch" href="https://example.com/images/next.webp" as="image">你可以显式传入 as 以覆盖检测结果。当无法从扩展名检测 as 属性时,preloadAsset 会抛出异常,因为浏览器会忽略没有该属性的 preload;而 prefetchAsset 则只会省略该属性。
自定义标签
对于没有专用方法的标签,请使用 meta() 和 link():
Head::meta('format-detection', 'telephone=no')
->meta('article:author', $post->author->name)
->link('search', '/opensearch.xml', [
'type' => 'application/opensearchdescription+xml',
'title' => 'Laravel Search',
])
->link('me', 'https://social.example.com/@laravel');当浏览器仅应在匹配条件下应用标签时,你可以在 meta 标签上包含媒体查询:
use Laravel\Head\Enums\Media;
Head::meta('theme-color', '#ffffff', media: Media::Light)
->meta('theme-color', '#111827', media: Media::Dark);meta 方法对普通 meta 标签使用 name 属性。对于通常使用 property 属性的键(例如 Open Graph 的 og: 或文章元数据的 article:),方法会自动切换:
Head::meta('description', 'About Laravel')
->meta('og:title', 'About Laravel');<meta name="description" content="About Laravel">
<meta property="og:title" content="About Laravel">你可以传入 property: true 或 property: false 以显式选择属性。
结构化数据(Schemas)
内置 schema 构建器覆盖常见的 JSON-LD 类型:
use Laravel\Head\Enums\OfferAvailability;
use Laravel\Head\Facades\Schema;
Head::schema(
Schema::product()
->name($product->name)
->offers(
Schema::offer()
->price($product->price)
->currency('USD')
->availability(OfferAvailability::InStock)
)
);内置工厂方法包括 article、blogPosting、product、offer、brand、breadcrumbs、faq、organization、person、webPage 和 webSite。未知的工厂方法会创建通用 schema 对象,因此你仍可表达自定义的 schema.org 类型。
当 JSON-LD schema 数据无效时,Laravel Head 在非生产环境会抛出异常,在生产环境则记录警告。
面包屑
面包屑项可逐个或批量添加。位置会按添加顺序自动分配:
Head::schema(
Schema::breadcrumbs()->items([
'Home' => route('home'),
'Shop' => route('shop.index'),
'Shoes' => route('shop.category', 'shoes'),
])
);你可以使用 item 方法追加单个面包屑项:
Schema::breadcrumbs()
->item('Home', route('home'))
->item('Shop', route('shop.index'));常见问题(FAQs)
FAQ 条目遵循相同模式。你可以使用 question 方法逐个添加,或使用 questions 方法批量添加:
Head::schema(
Schema::faq()->questions([
'What is Laravel Head?' => 'A fluent API for managing the document head.',
'Is it free?' => 'Yes, it is open source.',
])
);自定义 Schemas
你可以显式注册自定义 schema 类型:
use DateTimeInterface;
use Laravel\Head\Facades\Schema;
use Laravel\Head\Schema\SchemaObject;
use Laravel\Head\SchemaType;
#[SchemaType('JobPosting')]
class JobPosting extends SchemaObject
{
public function title(string $title): static
{
return $this->set('title', $title);
}
public function datePosted(DateTimeInterface|string $date): static
{
return $this->date('datePosted', $date);
}
}
Schema::register(JobPosting::class);
Head::schema(
Schema::jobPosting()
->title('Senior Laravel Developer')
->datePosted(now())
);渲染
Laravel Head 会将页面元数据解析为当前响应的标签。如何渲染这些标签取决于你的应用技术栈。
HTML 渲染器驱动 @head 指令,以及 Laravel Head 通过 head prop 与 Inertia 共享的已渲染元素。数组渲染器驱动 Head::toArray(),供需要将已解析元数据作为结构化数据使用的应用。
Blade
使用 @head 指令在布局的 <head> 中渲染累计的标签:
<head>
<meta charset="utf-8">
@head
</head>@head 指令同步渲染,因此应在布局渲染之前定义页面元数据。
Livewire
Livewire 应用在文档布局中使用相同的 @head 指令:
<head>
@head
</head>
<body>
{{ $slot }}
@livewireScripts
</body>无需任何 Livewire 专用配置。Laravel Head 元数据按请求解析,解析器也是请求作用域。因此,每次 wire:navigate 访问都会获取新文档,其 @head 输出反映目标路由的元数据。使用 wire:navigate 访问的页面会获得相应的路由、运行时和错误页元数据,无需组件级 head 代码。
Inertia
在 Inertia 根模板中使用相同的 @head 指令,并与 Inertia 自身组件一起使用:
<html>
<head>
<meta charset="utf-8">
@head
@viteReactRefresh
@vite(['resources/css/app.css', 'resources/js/app.tsx'])
<x-inertia::head />
</head>
<body>
<x-inertia::app />
</body>
</html>安装 Inertia 后,Laravel Head 会自动将页面管理的 head 作为已渲染元素字符串数组,通过每个页面对象上的 head prop 共享:
{
"props": {
"head": [
"<title data-inertia=\"title\">Dashboard - Laravel</title>",
"<meta data-inertia=\"description\" name=\"description\" content=\"Your application overview.\">"
]
}
}在应用调用 createInertiaApp() 的地方启用 Inertia 的 serverHead 选项。该选项在 Inertia 3.5 及更高版本可用:
createInertiaApp({
// ...
serverHead: true,
});每个页面管理的元素都有稳定的 data-inertia 键。@head 指令渲染初始文档,随后 Inertia 接管这些元素,并在标准访问、即时访问以及前进/后退导航中保持同步。这些元素存在于初始 HTML 响应中,因此爬虫和链接预览机器人无需执行 JavaScript 即可读取。无需客户端 <Head> 组件。
无论是否使用服务端渲染(SSR),这都能工作。若应用有独立的 SSR 入口,也请在那里启用 serverHead。Laravel Head 会自动在 @head 与 <x-inertia::head /> 之间去重页面管理的元素(无论顺序如何),同时保留 JavaScript SSR 产生的其他 head 元素。
INFO
将 Laravel Head 添加到现有 Inertia 应用时,请从 resources/js/app.tsx 和 resources/js/ssr.tsx 中移除任何 title 回调,以便 Laravel Head 管理最终文档标题;并将 Inertia <Head> 组件 管理的标签迁移到 Laravel Head,避免两者定义相同元素。
head prop 不会出现在部分重载响应中,因此 Inertia 会保留上一次完整页面的 head。即时访问同样会保留当前 head,直到后台响应到达。若你的应用已使用 head prop,请在服务提供者中更改其名称:
use Laravel\Head\Facades\Head;
public function boot(): void
{
Head::inertia(prop: '_head');
}然后用 serverHead: '_head' 让 Inertia 指向同一 prop。
静态 Inertia 标签
大多数标签应放在默认值、路由元数据或运行时元数据中,以便 Laravel Head 为每个页面解析正确的值。Inertia globals 仅应用于在首次 HTML 响应中渲染、且在会话其余时间由 Inertia 保持不变的文档标签。
在服务提供者中使用 Head::inertiaGlobals() 注册它们:
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;
Head::inertiaGlobals(function (HeadBuilder $head) {
$head
->viewport('width=device-width, initial-scale=1')
->colorScheme('light dark')
->icon('/favicon.svg', type: 'image/svg+xml')
->appleTouchIcon('/apple-touch-icon.png', sizes: '180x180')
->manifest('/site.webmanifest');
});Inertia globals 不会进入 head prop,渲染时不含 data-inertia 所有权属性,且在首次响应后永不更新。这些 globals 适合稳定的浏览器提示,例如 viewport、配色方案、favicon、touch 图标和 manifest。若标签是页面特定的、与 SEO 相关,或可能稍后被覆盖,请改放入 defaults、路由元数据或运行时元数据。
若应用需要将已解析元数据作为结构化数据而非已渲染标签,可调用 Head::toArray()。返回的数据包含 titles、Open Graph 值、JSON-LD schemas 及其他已解析元数据。