介绍
Laravel Head 提供了一套流畅的 API,用于管理应用程序的文档 <head> 元素,包括标题和元标签、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>解析优先级
页面元数据从五个层级解析,优先级从低到高排列如下:
- 页面默认设置
- 路由组元数据
- 路由元数据
- 运行时元数据
- 错误元数据
较高层级会逐字段替换较低层级的内容。例如,运行时的标题会替换路由的标题,但不会替换路由的描述。后续章节将说明如何为每个层级设置元数据。有关在 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');
});默认设置是优先级最低的页面元数据层级。如果没有路由、运行时或错误元数据设置标题,Laravel 将原样渲染。当较高层级设置了页面标题时,会应用继承的后缀,因此 Head::title('About') 会渲染为 About - Laravel。对于应忽略继承的前缀或后缀的标题,请传入 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');
});您还可以为资源路由和单例路由定义元数据:
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],
);支持的属性
支持的路由属性与流畅构建器方法的名称一一对应:
| 类别 | 属性 |
|---|---|
| 文档 | title、description、canonical、robots |
| 应用元数据 | themeColor、applicationName、colorScheme、referrer、viewport、appleWebAppTitle、webAppCapable、appleWebAppStatusBarStyle |
| 社交 | og、ogImage、ogVideo、ogAudio、twitter、twitterImage |
| 性能 | preload、prefetch、preconnect、dnsPrefetch |
| 发现 | alternates、feed、icon、favicon、appleTouchIcon、appleTouchStartupImage、maskIcon、manifest |
| 结构化数据 | schema |
| 自定义标签 | 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 门面发起的运行时调用会覆盖路由元数据,用于处理依赖请求的数据。控制器和动作是发起这些调用的最常见位置:
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]);
}多次运行时调用会按其执行顺序进行合并。对于单值字段,例如标题、描述、规范 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,同时还支持可选的具名参数,例如 alt、width、height、type 和 secureUrl(在 Open Graph 规范支持的情况下)。
在 API 接受图像 type 参数的任何地方,都可以传入 ImageType 枚举值,例如 ImageType::Svg、ImageType::Png、ImageType::Jpeg 和 ImageType::Webp。
文档的 title 和 description 会自动填充缺失的 og:title 和 og:description 值。
对于不包含其他属性的单个 Open Graph 图像,可以向 og 方法传入 image 具名参数:
Head::og(
type: OgType::Website,
title: $page->title,
description: $page->description,
image: $page->og_image_url,
);og(image: ...) 和 ogImage(...) 调用会写入同一个底层图像列表,因此在调用处可以选择表达更清晰的方式。对于自定义的 Open Graph 扩展(如产品或文章属性),可以使用 meta 方法。
X / Twitter 卡片
要使用 Open Graph 所用的标题、描述和图像来渲染 X / Twitter 卡片,请在默认配置中注册 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 应用清单链接以及 iOS 独立模式元数据。如果提供了主题颜色、Apple 状态栏样式和 Apple 触摸图标,也会一并渲染。创建 Web 应用清单和注册 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,预加载规范要求即使是同源字体也需要此属性:
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 方法会抛出异常,因为浏览器会忽略没有此属性的预加载;而 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 方法使用 name 属性处理常规 meta 标签。对于通常使用 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 来显式选择任一属性。
Schema
内置的 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'));常见问题(FAQ)
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.',
])
);自定义 Schema
你可以显式注册自定义 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 属性与 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 属性共享:
{
"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 元素。
将 Laravel Head 添加到现有 Inertia 应用时,请移除 resources/js/app.tsx 和 resources/js/ssr.tsx 中所有标题回调,以便 Laravel Head 可以管理最终文档标题,并将 Inertia 的<Head> 组件所管理的标签迁移至 Laravel Head,确保两者不会定义相同元素。
部分重载响应会省略 head 属性,因此 Inertia 会保留上一次完整页面的 head。即时访问同样会保留当前 head,直到后台响应到达。如果你的应用已使用 head 属性,请在服务提供者中更改其名称:
use Laravel\Head\Facades\Head;
public function boot(): void
{
Head::inertia(prop: '_head');
}然后使用 serverHead: '_head' 将 Inertia 指向同一个属性。
静态 Inertia 标签
大多数标签应放在默认值、路由元数据或运行时元数据中,以便 Laravel Head 为每个页面解析正确的值。仅对于在首个 HTML 响应中渲染、且在该会话其余时间内由 Inertia 保持不变的文档标签,才应使用 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 全局变量被排除在 head 属性之外,渲染时不包含 data-inertia 归属属性,且首次响应后不会更新。这些全局变量适用于视口、配色方案、网站图标、触摸图标和清单等稳定的浏览器提示。如果某个标签是页面特定的、与 SEO 相关,或者后续可能会被覆盖,则应将其放入 defaults、路由元数据或运行时元数据中。
需要以结构化数据形式(而非渲染后的标签)获取已解析元数据的应用,可调用 Head::toArray()。返回的数据包含标题、Open Graph 值、JSON-LD 模式以及其他已解析的元数据。