honeystone / laravel-seo
Laravel的SEO元数据和JSON-LD包。
Requires
- php: ^8.2
- ext-zip: *
- illuminate/contracts: ^10.0|^11.0
- laravel/prompts: ^0.1.25
- spatie/laravel-package-tools: ^1.16.4
Requires (Dev)
- nunomaduro/collision: ^8.0
- nunomaduro/larastan: ^2.9
- nunomaduro/phpinsights: ^2.11
- orchestra/testbench: ^9.2
- pestphp/pest: ^2.0
- pestphp/pest-plugin-laravel: ^2.4
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^1.2
- phpstan/phpstan-phpunit: ^1.4
- phpunit/phpunit: ^10.0
- spatie/laravel-ray: ^1.37
- spatie/schema-org: ^3.23
README
Honeystone SEO包使您可以从Laravel应用的任何位置轻松配置SEO元数据。
包含通用元数据、X(原Twitter)卡、Open Graph、JSON-LD架构和Favicon(使用RealFaviconGenerator生成)的元数据生成器。
此包旨在具有可扩展性,因此也可以轻松添加您自己的自定义元数据生成器。
支持我们
我们致力于提供高质量的由Honeystone团队维护的开源包。如果您想支持我们的努力,只需使用我们的包、推荐它们并贡献。
如果您在项目中需要任何帮助或需要任何定制开发,请联系我们。
安装
composer require honeystone/laravel-seo
使用以下命令发布配置文件
php artisan vendor:publish --tag=honeystone-seo-config
用法
该包提供了一个辅助函数seo()
和一些Blade指令@metadata
和@openGraphPrefix
。如果您更喜欢使用依赖注入,还可以类型提示Honeystone\Seo\MetadataDirector
。
设置元数据就像链式调用方法一样简单
seo() ->title('A fantastic blog post', 'My Awesome Website!') ->description('Theres really a lot of great stuff in here...') ->images( 'https://mywebsite.com/images/blog-1/cover-image.webp', 'https://mywebsite.com/images/blog-1/another-image.webp', );
一旦设置了元数据,您可以使用以下方式渲染它
seo()->generate();
或者,您也可以使用@metadata
Blade指令。
渲染结果将类似于以下内容
<title>A fantastic blog post - My Awesome Website!</title> <meta name="description" content="Theres really a lot of great stuff in here..."> <link rel="canonical" href="https://mywebsite.com/blog/a-fantastic-blog-post"> <!-- Twitter Cards --> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:title" content="A fantastic blog post"> <meta name="twitter:description" content="Theres really a lot of great stuff in here..."> <meta name="twitter:image" content="https://mywebsite.com/images/blog-1/cover-image.webp"> <!-- Open Graph --> <meta property="og:type" content="website"> <meta property="og:title" content="A fantastic blog post"> <meta property="og:description" content="Theres really a lot of great stuff in here..."> <meta property="og:image" content="https://mywebsite.com/images/blog-1/cover-image.webp"> <meta property="og:image" content="https://mywebsite.com/images/blog-1/another-image.webp"> <meta property="og:url" content="https://mywebsite.com/blog/a-fantastic-blog-post"> <!-- JSON-LD --> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "WebPage", "name": "A fantastic blog post", "description": "Theres really a lot of great stuff in here...", "image": [ "https://mywebsite.com/images/blog-1/cover-image.webp", "https://mywebsite.com/images/blog-1/another-image.webp" ], "url": "https://mywebsite.com" } </script>
默认方法
提供给默认方法的值将自动传播到所有配置的元数据生成器。
以下默认方法可用
seo() ->locale('en_GB') ->title('A fantastic blog post', template: '🔥🔥 {title} 🔥🔥') ->description('Theres really a lot of great stuff in here...') ->keywords('foo', 'bar', 'baz') ->url('https://mywebsite.com/blog/a-fantastic-blog-post') //defaults to the current url ->canonical('https://mywebsite.com/blog/a-fantastic-blog-post') //by default url and canonical are in sync, see config ->canonicalEnabled(true) //enabled by default, see config ->images( 'https://mywebsite.com/images/blog-1/cover-image.webp', 'https://mywebsite.com/images/blog-1/another-image.webp', ) ->robots('🤖', '🤖', '🤖');
完整的基线如下所示
<title>🔥🔥 A fantastic blog post 🔥🔥</title> <meta name="description" content="Theres really a lot of great stuff in here..."> <meta name="keywords" content="foo,bar,baz"> <link rel="canonical" href="https://mywebsite.com/blog/a-fantastic-blog-post"> <meta name="robots" content="🤖,🤖,🤖"> <!-- Twitter Cards --> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:title" content="A fantastic blog post"> <meta name="twitter:description" content="Theres really a lot of great stuff in here..."> <meta name="twitter:image" content="https://mywebsite.com/images/blog-1/cover-image.webp"> <!-- Open Graph --> <meta property="og:site_name" content="My Website"> <meta property="og:title" content="A fantastic blog post"> <meta property="og:description" content="Theres really a lot of great stuff in here..."> <meta property="og:image" content="https://mywebsite.com/images/blog-1/cover-image.webp"> <meta property="og:image" content="https://mywebsite.com/images/blog-1/another-image.webp"> <meta property="og:url" content="https://mywebsite.com/blog/a-fantastic-blog-post"> <meta property="og:locale" content="en_GB"> <!-- JSON-LD --> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "WebPage", "name": "A fantastic blog post", "description": "Theres really a lot of great stuff in here...", "image": [ "https://mywebsite.com/images/blog-1/cover-image.webp", "https://mywebsite.com/images/blog-1/another-image.webp" ], "url": "https://mywebsite.com/blog/a-fantastic-blog-post" } </script>
对于您的首页,您可能希望禁用标题模板
seo()->title('My Awesome Website!', template: false);
元数据方法
元数据方法由Honeystone\Seo\Generators\MetaGenerator
类提供。
以下是完整列表
seo() ->metaTitle('A fantastic blog post') ->metaTitleTemplate('🔥🔥 {title} 🔥🔥') ->metaDescription('Theres really a lot of great stuff in here...') ->metaKeywords('foo', 'bar', 'baz') ->metaCanonical('https://mywebsite.com/blog/a-fantastic-blog-post') ->metaCanonicalEnabled(true) ->metaRobots('🤖', '🤖', '🤖');
所有这些都由默认方法提供并通过元数据生成器传播。
如果您只想渲染元数据生成器,请使用seo()->generate('meta')
或@metadata('meta')
Twitter方法
元数据方法由Honeystone\Seo\Generators\TwitterGenerator
类提供。
以下是完整列表
seo() ->twitterEnabled(true) //enabled by default, see config ->twitterSite('@MyWebsite') ->twitterCreator('@MyTwitter') ->twitterTitle('A fantastic blog post') //defaults to title() ->twitterDescription('Theres really a lot of great stuff in here...') //defaults to description() ->twitterImage('https://mywebsite.com/images/blog-1/cover-image.webp'); //defaults to the first in images()
Open Graph方法
元数据方法由Honeystone\Seo\Generators\TwitterGenerator
类提供。
以下是完整列表
seo() ->openGraphEnabled(true) //enabled by default, see config ->openGraphSite('My Website') ->openGraphType('website') //defaults to website, see config ->openGraphTitle('A fantastic blog post') //defaults to title() ->openGraphDescription('Theres really a lot of great stuff in here...') //defaults to description() ->openGraphImage('https://mywebsite.com/images/blog-1/cover-image.webp') ->openGraphImages([ 'https://mywebsite.com/images/blog-1/cover-image.webp', 'https://mywebsite.com/images/blog-1/another-image.webp', ]) //defaults to images() ->openGraphUrl('https://mywebsite.com/blog/a-fantastic-blog-post') //defaults to url() ->openGraphAudio([ 'https://mywebsite.com/music/song1.mp3', 'https://mywebsite.com/music/song2.mp3', ]) ->openGraphVideo('https://mywebsite.com/films/video1.mp4') ->openGraphVideos([ 'https://mywebsite.com/films/video1.mp4', 'https://mywebsite.com/films/video2.mp4', ]) ->openGraphDeterminer(OpenGraphGenerator::DETERMINER_A) ->openGraphLocale('en_GB') //defaults to locale() ->openGraphAlternateLocales(['en_US']) ->openGraphProperty('custom:property', '💀');
您还可以使用以下非垂直支持类型
use Honeystone\Seo\OpenGraph\ArticleProperties; use Honeystone\Seo\OpenGraph\BookProperties; use Honeystone\Seo\OpenGraph\ProfileProperties; //article seo() ->openGraphType(new ArticleProperties( publishedTime: new DateTime('now'), modifiedTime: new DateTime('now'), expirationTime: null, author: new ProfileProperties( username: 'PiranhaGeorge', ), section: 'Foo', tag: 'Bar', )); //book seo() ->openGraphType(new BookProperties( author: [ new ProfileProperties( firstName: 'Erich', lastName: 'Gamma', ), new ProfileProperties( firstName: 'Richard', lastName: 'Helm', ), new ProfileProperties( firstName: 'Ralph', lastName: 'Johnson', ), new ProfileProperties( firstName: 'John', lastName: 'Vlissides', ), ], isbn: '978-0201633610', releaseDate: new DateTime('14 March 1995'), tag: ['1st', 'GoF'], )); //profile seo() ->openGraphType(new ProfileProperties( username: 'PiranhaGeorge' firstName: 'George', lastName: 'Palmer', gender: 'male', ));
您可以使用它们各自的属性类提供更多图像、音频和视频数据
use Honeystone\Seo\OpenGraph\AudioProperties; use Honeystone\Seo\OpenGraph\ImageProperties; use Honeystone\Seo\OpenGraph\VideoProperties; seo()->openGraphAudio(new AudioProperties( url: 'http://foo.bar/song.mp3', secureUrl: 'https://foo.bar/song.mp3', type: 'audio/mpeg', )); seo()->openGraphImage(new ImageProperties( url: 'http://foo.bar/img.png', alt: 'Foo', width: '800', height: '450', secureUrl: 'https://foo.bar/img.png', type: 'image/png', )); seo()->openGraphVideo(new VideoProperties( url: 'http://foo.bar/movie.mp4', alt: 'Foo', width: '1920', height: '1080', secureUrl: 'https://foo.bar/movie.mp4', type: 'video/mp4', ));
以下是一个使用ArticleProperties
和ImageProperties
的示例
<!-- Open Graph --> <meta property="og:site_name" content="My Website"> <meta property="og:type" content="article"> <meta property="article:published_time" content="2024-07-25T21:39:40+00:00"> <meta property="article:modified_time" content="2024-07-25T21:39:40+00:00"> <meta property="article:author:username" content="PiranhaGeorge"> <meta property="article:section" content="Foo"> <meta property="article:tag" content="Bar"> <meta property="og:title" content="A fantastic blog post"> <meta property="og:description" content="Theres really a lot of great stuff in here..."> <meta property="og:image" content="http://foo.bar/img.png"> <meta property="og:image:alt" content="Foo"> <meta property="og:image:width" content="800"> <meta property="og:image:height" content="450"> <meta property="og:image:secure_url" content="https://foo.bar/img.png"> <meta property="og:image:type" content="image/png"> <meta property="og:url" content="https://mywebsite.com"> <meta property="og:determiner" content="a"> <meta property="og:locale" content="en_GB"> <meta property="og:locale:alternate" content="en_US"> <meta property="custom:property" content="💀">
要设置前缀,您可以使用@openGraphPrefix
Blade指令或seo()->openGraphPrefix()
,如下所示
<head prefix="@openGraphPrefix"> ... </head>
JSON-LD方法
元数据方法由Honeystone\Seo\Generators\JsonLdGenerator
类提供。
以下是完整列表
seo() ->jsonLdEnabled(true) //enabled by default, see config ->jsonLdType('WebPage') //defaults to WebPage, see config ->jsonLdName('A fantastic blog post') //defaults to title() ->jsonLdDescription('Theres really a lot of great stuff in here...') //defaults to description() ->jsonLdImage('https://mywebsite.com/images/blog-1/cover-image.webp') ->jsonLdImages([ 'https://mywebsite.com/images/blog-1/cover-image.webp', 'https://mywebsite.com/images/blog-1/another-image.webp', ]) //defaults to images() ->jsonLdUrl('https://mywebsite.com/blog/a-fantastic-blog-post') //defaults to url() ->jsonLdNonce('some-value') //sets a nonce value for your content security policy ->jsonLdProperty('alternateName', 'Foo');
输出如下
<!-- JSON-LD --> <script type="application/ld+json" nonce="some-value"> { "@context": "https://schema.org", "@type": "WebPage", "name": "A fantastic blog post", "description": "Theres really a lot of great stuff in here...", "image": [ "https://mywebsite.com/images/blog-1/cover-image.webp", "https://mywebsite.com/images/blog-1/another-image.webp" ], "url": "https://mywebsite.com/blog/a-fantastic-blog-post", "alternateName": "Foo" } </script>
但是,还有更多!
而不是重新发明轮子,此包对令人难以置信的spatie/schema-org包提供了支持。您可以使用jsonLdImport()
方法导入现有模式,或使用流畅接口构建您的模式。
//graph seo()->jsonLdGraph() ->organization('honeystone') ->name('Honeystone') ->legalName('Honeystone Consulting Ltd.'); //or a MultiTypedEntity seo()->jsonLdMulti() ->organization('honeystone') ->name('Honeystone') ->legalName('Honeystone Consulting Ltd.');
但是,不要忘记安装spatie/schema-org
包以使用此功能。
期望/检查
您很可能会从应用程序的许多位置构建您的图,例如中间件、控制器、视图组合器、视图组件等。
这就是期望发挥作用的地方。只需指定您的期望,然后确保应用程序的其他部分进行检查。如果某些内容未能检查,则将抛出异常。相反,如果某些意外内容进行了检查,也将抛出异常。
//perhaps in a controller seo() ->title('Something awesome') ->jsonLdExpect('featured-tags', 'gallery', 'contact'); //maybe in a view composer or component seo() ->jsonLdCheckIn('gallery') ->jsonLdGraph() ->imageGallery() ->image([ ... ]);
如果“推荐标签”或“联系方式”未成功签到,您将立即收到警告。
此功能完全可选。只需不要设定任何期望或签到,就不会抛出任何异常。
图标生成
使用RealFaviconGenerator API,您现在可以使用此包生成图标。只需申请API密钥并将其放入您的配置文件中,配置您的源图像,然后运行命令
php artisan seo:generate-favicons
模型集成
此包不包含任何用于集成模型的特定功能。最终,您始终需要将您的模型属性映射到此包。例如,如果您的模型有一个meta_description
属性,您需要将其映射到description
,否则此包将不知道如何消费它。
考虑到这一点,我们有一个简单的模式,应该能满足您的需求。
首先,向您的模型添加一个新方法,并在其中使用模型属性设置元数据
use Honeystone\Seo\MetadataDirector; use Illuminate\Database\Eloquent\Model; class Page extends Model { public function seo(): MetadataDirector { return seo() ->title($this->meta_title) ->description($this->meta_description) ->jsonLdExpect('featured-items'); } }
然后在您的控制器中,只需调用该方法并链式调用任何附加的元数据
use Illuminate\Contracts\View\View; class PageController { public function __invoke(Page $page): View { $page->seo() ->jsonLdCheckIn('featured-items') ->jsonLdGraph() ->itemList() ->name('Featured items') ->itemListElement([ //... ]); } }
自定义生成器
要创建自定义生成器,只需实现Honeystone\Seo\Contracts\MetadataGenerator
合约并将其添加到您的配置文件中的生成器部分。您也可以在这里指定生成器的任何配置。
配置
以下是完整的配置文件
<?php declare(strict_types=1); use Honeystone\Seo\Generators; return [ 'generators' => [ Generators\MetaGenerator::class => [ 'title' => env('APP_NAME'), 'titleTemplate' => '{title} - '.env('APP_NAME'), 'description' => '', 'keywords' => [], 'canonicalEnabled' => true, 'canonical' => null, //null to use current url 'robots' => [], 'custom' => [ //[ // 'greeting' => 'Hey, thanks for checking out the source code of our website. '. // 'Hopefully you find what you are looking for 👍' //], //[ // 'google-site-verification' => 'xxx', //], ], ], Generators\TwitterGenerator::class => [ 'enabled' => true, 'site' => '', //@twitterUsername 'creator' => '', 'title' => '', 'description' => '', 'image' => '', ], Generators\OpenGraphGenerator::class => [ 'enabled' => true, 'site' => env('APP_NAME'), 'type' => 'website', 'title' => '', 'description' => '', 'images' => [], 'audio' => [], 'videos' => [], 'determiner' => '', 'url' => null, //null to use current url 'locale' => '', 'alternateLocales' => [], 'custom' => [], ], Generators\JsonLdGenerator::class => [ 'enabled' => true, 'pretty' => env('APP_DEBUG'), 'type' => 'WebPage', 'name' => '', 'description' => '', 'images' => [], 'url' => null, //null to use current url 'custom' => [], //determines if the configured json-ld is automatically placed on the graph 'place-on-graph' => true, ], Generators\RealFaviconGenerator::class => [ 'enabled' => true, 'apiKey' => env('REAL_FAVICON_KEY'), 'image' => '', //the source image path, relative to /resources //see https://realfavicongenerator.net/api/non_interactive_api#favicon_design 'design' => [ 'ios' => [ 'picture_aspect' => 'no_change', 'app_name' => env('APP_NAME'), 'assets' => [ 'ios6_and_prior_icons' => false, 'ios7_and_prior_icons' => false, 'precomposed_icons' => false, 'declare_only_default_icon' => true, ], ], 'windows' => [ 'picture_aspect' => 'no_change', 'background_color' => '#222', 'app_name' => env('APP_NAME'), 'assets' => [ 'windows_80_ie_10_tile' => false, 'windows_10_ie_11_edge_tiles' => [ 'small' => false, 'medium' => true, 'big' => false, 'rectangle' => false, ], ], ], 'firefox_app' => [ 'picture_aspect' => 'no_change', 'manifest' => [ 'app_name' => env('APP_NAME'), 'app_description' => '', 'developer_name' => '', 'developer_url' => '', ], ], 'android_chrome' => [ 'picture_aspect' => 'no_change', 'manifest' => [ 'name' => env('APP_NAME'), 'display' => 'browser', 'theme_color' => '#222', ], 'assets' => [ 'legacy_icon' => false, 'low_resolution_icons' => false, ], ], 'safari_pinned_tab' => [ 'picture_aspect' => 'silhouette', 'theme_color' => '#222', ], ], //see https://realfavicongenerator.net/api/non_interactive_api#settings 'settings' => [ 'compression' => 3, 'scaling_algorithm' => 'Mitchell', ], ], ], 'sync' => [ 'url-canonical' => true, 'keywords-tags' => false, ], ];
变更日志
更改列表可以在CHANGELOG.md文件中找到。