Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

Laravel Head

介绍

Laravel Head 提供了一个流畅的 API,用于管理应用程序文档的 <head> 元素,包括标题和元标签、Open Graph 元数据、规范网址、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. 错误元数据

更高层级会逐字段替换低层级的设置。例如,运行时标题会替换路由标题,但不会替换路由描述。接下来的章节将描述如何在每个层级设置元数据。有关在 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');
});

默认值是优先级最低的页面元数据层级。如果没有路由、运行时或错误元数据设置标题,则 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

路由元数据

你可以直接在路由上定义元数据,这对于元数据可提前确定的半静态页面特别有用。

路由与路由组

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

你也可以为资源路由和单例路由定义元数据:

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

支持的属性

支持的路由属性与流畅构建器方法同名:

类别属性
文档titledescriptioncanonicalrobots
应用元数据themeColorapplicationNamecolorSchemereferrerviewportappleWebAppTitlewebAppCapableappleWebAppStatusBarStyle
社交ogogImageogVideoogAudiotwittertwitterImage
性能preloadprefetchpreconnectdnsPrefetch
发现alternatesfeediconfaviconappleTouchIconappleTouchStartupImagemaskIconmanifest
结构化数据schema
自定义标签metalink

嵌套选项名称使用与流畅 API 相同的 camelCase 命名,例如 forceHttpssiteNamesecureUrl

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

运行时元数据

当某个值直到请求到达时才能确定(例如正在浏览的文章标题)时,你可以在运行时设置它:

php
use Laravel\Head\Facades\Head;

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

    // ...
}

通过 Head 门面进行的运行时调用会覆盖路由元数据,用于依赖于请求的数据。控制器和操作是进行这些调用的最常见位置:

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]);
}

多次运行时调用会按执行顺序合并。对于单值字段,如标题、描述、规范网址和 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());

错误页面

通常,你应该在应用程序 AppServiceProviderboot 方法中注册错误元数据:

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

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

当为已注册的错误状态码渲染响应时,该元数据优先于所有其他层级。

Laravel 在渲染错误视图或执行响应阶段钩子(例如 Inertia 的 handleExceptionsUsing() 方法)时会自动检测响应状态码。如果你在 $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 的任何地方将图片 MIME 类型作为 ImageType 枚举案例传递,例如 ImageType::SvgImageType::PngImageType::JpegImageType::Webp

NOTE

文档 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(...) 调用写入相同的底层图片列表,因此你可以在调用处选择更具表现力的方式。你可以使用 meta 方法设置自定义 Open Graph 扩展,例如产品属性或文章属性。

X / Twitter 卡片

要使用 Open Graph 使用的相同标题、描述和图片渲染 X / Twitter 卡片,请在默认设置中注册 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 应用清单链接以及 iOS 独立模式元数据。如果提供,还会渲染主题颜色、Apple 状态栏样式和 Apple 触摸图标。创建 Web 应用清单和注册 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,这是预加载规范的要求,即使对于同源字体也是如此:

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 方法会抛出异常,因为浏览器会忽略没有此属性的预加载;而 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 来显式选择属性。

结构化数据

内置的结构化数据构建器涵盖了常见的 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.org 类型。

当 JSON-LD 结构化数据无效时,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'));

常见问题

常见问题条目遵循相同的模式。你可以使用 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.',
    ])
);

自定义结构化数据

你可以显式注册自定义结构化数据类型:

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 元素。

NOTE

将 Laravel Head 添加到现有 Inertia 应用程序时,请从 resources/js/app.tsxresources/js/ssr.tsx 中移除所有标题回调,以便 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 能为每个页面解析出正确的值。仅在首次 HTML 响应中渲染,并在会话剩余时间内保持不变由 Inertia 管理的文档标签,才应使用 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 全局设置会被排除在 head prop 之外,渲染时不带 data-inertia 所有权属性,并且在首次响应后永不更新。这些全局设置适用于稳定的浏览器提示,例如 viewport、颜色方案、favicon、触摸图标和清单。如果某个标签是页面特定的、与 SEO 相关或可能在之后被覆盖,请将其放在 defaults、路由元数据或运行时元数据中。

需要将解析后的元数据作为结构化数据而非渲染标签的应用程序,可以调用 Head::toArray()。返回的数据包括标题、Open Graph 值、JSON-LD 结构化数据以及其他已解析的元数据。