Laravel Head
介绍
Laravel Head 提供了一个流畅的 API,用于管理应用程序文档的 <head> 元素,包括标题和元标签、Open Graph 元数据、规范网址、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,可传递字符串,例如 Head::canonical('/about')。规范网址默认标准化为 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]);
}多次运行时调用会按执行顺序合并。对于单值字段,如标题、描述、规范网址和 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;
/**
* 引导任何应用程序服务。
*/
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.'));
});当为已注册的错误状态码渲染响应时,该元数据优先于所有其他层级。
Laravel 在渲染错误视图或执行响应阶段钩子(例如 Inertia 的 handleExceptionsUsing() 方法)时会自动检测响应状态码。如果你在 $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 的任何地方将图片 MIME 类型作为 ImageType 枚举案例传递,例如 ImageType::Svg、ImageType::Png、ImageType::Jpeg 和 ImageType::Webp。
NOTE
文档 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(...) 调用写入相同的底层图片列表,因此你可以在调用处选择更具表现力的方式。你可以使用 meta 方法设置自定义 Open Graph 扩展,例如产品属性或文章属性。
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 方法对常规 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 来显式选择属性。
结构化数据
内置的结构化数据构建器涵盖了常见的 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.org 类型。
当 JSON-LD 结构化数据无效时,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'));常见问题
常见问题条目遵循相同的模式。你可以使用 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.',
])
);自定义结构化数据
你可以显式注册自定义结构化数据类型:
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 元素。
NOTE
将 Laravel Head 添加到现有 Inertia 应用程序时,请从 resources/js/app.tsx 和 resources/js/ssr.tsx 中移除所有标题回调,以便 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 能为每个页面解析出正确的值。仅在首次 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 prop 之外,渲染时不带 data-inertia 所有权属性,并且在首次响应后永不更新。这些全局设置适用于稳定的浏览器提示,例如 viewport、颜色方案、favicon、触摸图标和清单。如果某个标签是页面特定的、与 SEO 相关或可能在之后被覆盖,请将其放在 defaults、路由元数据或运行时元数据中。
需要将解析后的元数据作为结构化数据而非渲染标签的应用程序,可以调用 Head::toArray()。返回的数据包括标题、Open Graph 值、JSON-LD 结构化数据以及其他已解析的元数据。
