Skip to content
全部文档

Laravel Head

简介

Laravel Head 提供流畅的 API,用于管理应用文档的 <head> 元素,包括 title 与 meta 标签、Open Graph 元数据、规范 URL、robots 指令、性能提示以及结构化数据。它可与 Blade、Livewire 和 Inertia 配合使用。

安装

你可以使用 Composer 包管理器安装 Laravel Head:

shell
composer require laravel/head

快速开始

在服务提供者中注册站点范围的默认值:

php
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;

Head::defaults(fn (HeadBuilder $head) => $head
    ->title('Laravel', suffix: ' - Laravel')
    ->description('Build something great.'));

在运行时设置页面级元数据:

php
Head::title($post->title)
    ->description($post->description);

在布局中渲染解析后的标签:

blade
<head>
    @head
</head>

解析优先级

页面元数据由五个层级解析,按优先级从低到高排列:

  1. 页面默认值
  2. 路由组元数据
  3. 路由元数据
  4. 运行时元数据
  5. 错误页元数据

更高层级会按字段覆盖更低层级。例如,运行时的 title 会替换路由上的 title,但不会替换路由上的 description。后续章节介绍如何在各层级设置元数据。关于在 Blade、Livewire 和 Inertia 中渲染已解析元数据,请参阅 渲染

定义元数据

Laravel Head 允许你通过站点范围默认值、路由元数据、运行时调用以及错误页定义来设置元数据。

默认值

在服务提供者中注册页面默认值:

php
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

路由元数据

你可以直接在路由上定义元数据,这对元数据事先已知的半静态页面尤其有用。

路由与路由组

php
Route::view('/contact', 'contact')
    ->name('contact')
    ->withHead(
        title: 'Contact Us',
        description: 'Get in touch.',
    );

共享的路由元数据可在链式调用的任意位置应用到路由组:

php
Route::withHead(robots: 'noindex, nofollow')
    ->prefix('admin')
    ->name('admin.')
    ->group(function () {
        Route::get('/dashboard', DashboardController::class)
            ->name('dashboard')
            ->withHead(title: 'Dashboard');
    });

你也可以为 resource 与 singleton 路由定义元数据:

php
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 传入:

php
Route::get('/article', ArticleController::class)->withHead(
    title: 'Article',
    extensions: ['readingTime' => 4],
);

支持的属性

受支持的路由属性与流畅构建器方法使用相同的名称:

CategoryProperties
Documenttitle, description, canonical, robots
Application metadatathemeColor, applicationName, colorScheme, referrer, viewport, appleWebAppTitle, webAppCapable, appleWebAppStatusBarStyle
Socialog, ogImage, ogVideo, ogAudio, twitter, twitterImage
Performancepreload, prefetch, preconnect, dnsPrefetch
Discoveryalternates, feed, icon, favicon, appleTouchIcon, appleTouchStartupImage, maskIcon, manifest
Structured dataschema
Custom tagsmeta, link

嵌套选项名称与流畅 API 一样使用 camelCase 命名,例如 forceHttpssiteNamesecureUrl

可重复属性(如 ogImagepreloadfeedschemaiconappleTouchStartupImage)既可接受单个值,也可接受列表。

运行时元数据

当某个值要等到请求到达才知道时(例如正在查看的文章标题),你可以在运行时设置:

php
use Laravel\Head\Facades\Head;

public function __invoke(Post $post): Response
{
    Head::title($post->title);

    // ...
}

通过 Head facade 进行的运行时调用会覆盖依赖请求的路由元数据。控制器与 action 是最常进行这些调用的地方:

php
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 即为键:

php
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);
html
<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 优先于站点范围的默认图片。

你可以使用 whenunless 方法流畅地定义条件元数据:

php
Head::title($post->title)
    ->when($post->isDraft(), fn ($head) => $head->hiddenFromRobots());

错误页

通常应在应用的 AppServiceProvider 类的 boot 方法中注册错误页元数据:

php
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.',
        );
    });
}

defaultsstatus 方法也接受与 Head::defaults() 相同的流畅构建器回调:

php
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 属性。可重复媒体可通过顶层方法添加,这些方法直接接受命名参数:

php
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,
    );

ogImageogVideoogAudio 方法以 URL 作为第一个参数,并可在 Open Graph 规范支持的情况下接受可选命名参数,例如 altwidthheighttypesecureUrl

凡是 API 接受图片 type 的地方,你都可以传入 ImageType 枚举值,例如 ImageType::SvgImageType::PngImageType::JpegImageType::Webp

INFO

文档的 titledescription 会自动补全缺失的 og:titleog:description 值。

对于没有其他属性的单个 Open Graph 图片,你可以将 image 命名参数传给 og 方法:

php
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()

php
use Laravel\Head\Enums\TwitterCard;
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;

Head::defaults(fn (HeadBuilder $head) => $head->twitter(
    card: TwitterCard::SummaryWithLargeImage,
));

然后设置页面级元数据:

php
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 标签:

html
<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 值自定义单个页面:

php
Head::twitter(title: $post->social_title)
    ->twitterImage($post->social_image_url, alt: $post->title);

路由元数据接受 twittertwitterImage

主题色

你可以全局、按路由或在运行时设置主题色:

php
Head::themeColor('#0f172a');

这会渲染 <meta name="theme-color"> 标签。对于特定媒体条件的主题色,可以使用 Media 枚举:

php
use Laravel\Head\Enums\Media;

Head::themeColor('#ffffff', media: Media::Light)
    ->themeColor('#111827', media: Media::Dark);

Media 枚举还包含 PortraitLandscapemedia 参数也接受自定义媒体查询字符串。

路由元数据通过相同的 camelCase 键支持单个主题色:

php
Route::view('/dashboard', 'dashboard')->withHead(
    themeColor: '#0f172a',
);

应用元数据与图标

Laravel Head 提供常见浏览器与应用元数据的方法:

php
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 方法的别名,接受相同的 typesizesmedia 参数。

路由元数据使用相同的名称:

php
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> 标签:

php
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 发现:

php
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 规范也要求如此:

php
Head::preloadAsset('fonts/inter.woff2')
    ->prefetchAsset('images/next.webp');
html
<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()

php
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 标签上包含媒体查询:

php
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:),方法会自动切换:

php
Head::meta('description', 'About Laravel')
    ->meta('og:title', 'About Laravel');
html
<meta name="description" content="About Laravel">
<meta property="og:title" content="About Laravel">

你可以传入 property: trueproperty: false 以显式选择属性。

结构化数据(Schemas)

内置 schema 构建器覆盖常见的 JSON-LD 类型:

php
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)
        )
);

内置工厂方法包括 articleblogPostingproductofferbrandbreadcrumbsfaqorganizationpersonwebPagewebSite。未知的工厂方法会创建通用 schema 对象,因此你仍可表达自定义的 schema.org 类型。

当 JSON-LD schema 数据无效时,Laravel Head 在非生产环境会抛出异常,在生产环境则记录警告。

面包屑项可逐个或批量添加。位置会按添加顺序自动分配:

php
Head::schema(
    Schema::breadcrumbs()->items([
        'Home' => route('home'),
        'Shop' => route('shop.index'),
        'Shoes' => route('shop.category', 'shoes'),
    ])
);

你可以使用 item 方法追加单个面包屑项:

php
Schema::breadcrumbs()
    ->item('Home', route('home'))
    ->item('Shop', route('shop.index'));

常见问题(FAQs)

FAQ 条目遵循相同模式。你可以使用 question 方法逐个添加,或使用 questions 方法批量添加:

php
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 类型:

php
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> 中渲染累计的标签:

blade
<head>
    <meta charset="utf-8">
    @head
</head>

@head 指令同步渲染,因此应在布局渲染之前定义页面元数据。

Livewire

Livewire 应用在文档布局中使用相同的 @head 指令:

blade
<head>
    @head
</head>

<body>
    {{ $slot }}

    @livewireScripts
</body>

无需任何 Livewire 专用配置。Laravel Head 元数据按请求解析,解析器也是请求作用域。因此,每次 wire:navigate 访问都会获取新文档,其 @head 输出反映目标路由的元数据。使用 wire:navigate 访问的页面会获得相应的路由、运行时和错误页元数据,无需组件级 head 代码。

Inertia

在 Inertia 根模板中使用相同的 @head 指令,并与 Inertia 自身组件一起使用:

blade
<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 共享:

json
{
    "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 及更高版本可用:

js
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.tsxresources/js/ssr.tsx 中移除任何 title 回调,以便 Laravel Head 管理最终文档标题;并将 Inertia <Head> 组件 管理的标签迁移到 Laravel Head,避免两者定义相同元素。

head prop 不会出现在部分重载响应中,因此 Inertia 会保留上一次完整页面的 head。即时访问同样会保留当前 head,直到后台响应到达。若你的应用已使用 head prop,请在服务提供者中更改其名称:

php
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() 注册它们:

php
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 及其他已解析元数据。