feat: intégration plugin Kirby SEO
All checks were successful
Deploy / Deploy to Production (push) Successful in 22s
All checks were successful
Deploy / Deploy to Production (push) Successful in 22s
- Ajout de tobimori/kirby-seo via Composer
- snippet('seo/head') dans header.php (remplace les meta manuels)
- snippet('seo/schemas') dans footer.php pour JSON-LD
- Onglet SEO ajouté dans site.yml et tous les blueprints de pages
- Configuration SEO dans config.php (sitemap, robots, canonicalBase TODO)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
baab2fb3a1
commit
58c31ea391
133 changed files with 9201 additions and 253 deletions
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
title: Programmatic Content
|
||||
intro: Set default SEO values from page models
|
||||
---
|
||||
|
||||
Sometimes you want SEO fields to default to values from other fields, or generate them from code. A common example is using a plugin like [kirby-paparazzi](https://github.com/tobimori/kirby-paparazzi) to generate OG images for every page.
|
||||
|
||||
Add a `metaDefaults` method to a [page model](https://getkirby.com/docs/guide/templates/page-models). It returns an array of meta tag names mapped to their values. These defaults apply through the [Meta Cascade](0_getting-started/1_your-first-meta-tags) when no editor override exists.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/models/article.php
|
||||
|
||||
use Kirby\Cms\Page;
|
||||
|
||||
class ArticlePage extends Page
|
||||
{
|
||||
public function metaDefaults(string $lang = null): array
|
||||
{
|
||||
return [
|
||||
'og:image' => "{$this->url()}.png",
|
||||
'og:image:width' => 1230,
|
||||
'og:image:height' => 600,
|
||||
'description' => $this->content($lang)->summary()->value(),
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Kirby SEO picks the correct tag syntax from the name. Open Graph keys (starting with `og:`) get `property` and `content` attributes, link keys like `canonical` get `rel` and `href`, and everything else gets `name` and `content`.
|
||||
|
||||
## Custom tag attributes
|
||||
|
||||
If you need full control over a tag's output, pass an array with `tag` and `attributes`:
|
||||
|
||||
```php
|
||||
return [
|
||||
// shorthand
|
||||
'description' => 'A page about something',
|
||||
|
||||
// tag with inner content
|
||||
[
|
||||
'tag' => 'title',
|
||||
'content' => 'My Page Title',
|
||||
],
|
||||
|
||||
// tag with attributes
|
||||
[
|
||||
'tag' => 'meta',
|
||||
'attributes' => [
|
||||
'property' => 'og:image:alt',
|
||||
'content' => "An image of {$this->title()}",
|
||||
],
|
||||
],
|
||||
|
||||
// link tag
|
||||
[
|
||||
'tag' => 'link',
|
||||
'attributes' => [
|
||||
'rel' => 'preconnect',
|
||||
'href' => 'https://fonts.googleapis.com',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Global defaults via a plugin
|
||||
|
||||
Page models only apply to pages with a specific template. If you want to add meta tags to all pages, you can register a `metaDefaults` [page method](https://getkirby.com/docs/reference/plugins/extensions/page-methods) in a plugin:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/plugins/my-meta/index.php
|
||||
|
||||
Kirby::plugin('my/meta', [
|
||||
'pageMethods' => [
|
||||
'metaDefaults' => function (string $lang = null): array {
|
||||
return [
|
||||
'og:image' => "{$this->url()}.png",
|
||||
];
|
||||
},
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
title: Meta Cascade
|
||||
intro: Understand how meta values are resolved across multiple levels
|
||||
---
|
||||
|
||||
Kirby SEO is built with a cascading approach. Meta tags can be defined on multiple levels, and they are merged based on priority. If a value is empty on one level, it falls through to the next. This is how the plugin forms the final metadata for every page.
|
||||
|
||||
The default cascade, in order of priority:
|
||||
|
||||
1. **Page fields** (`fields`) -- Values the editor enters in the page's SEO blueprint fields. This is the highest priority: if an editor sets a meta description, it always wins.
|
||||
|
||||
2. **Programmatic** (`programmatic`) -- Values returned by `metaDefaults()` in [page models](2_customization/00_programmatic-content). Use this for computed defaults like generated OG images or descriptions derived from other fields.
|
||||
|
||||
3. **Parent** (`parent`) -- Inherited values from the parent page. If a parent page has "inherit settings" enabled for a field, its children pick up those values. Useful for giving all blog posts the same title template, for example.
|
||||
|
||||
4. **Fallback fields** (`fallbackFields`) -- Falls back to meta field values for Open Graph tags. If no `ogDescription` is set, the page's `metaDescription` is used instead.
|
||||
|
||||
5. **Site** (`site`) -- Global values from the site's SEO blueprint fields. These apply to all pages that don't have their own value set at a higher level.
|
||||
|
||||
6. **Options** (`options`) -- The final fallback, defined in the plugin's config defaults. These are the built-in defaults like the title template `{{ title }} - {{ site.title }}`.
|
||||
|
||||
## Configuring the cascade
|
||||
|
||||
The cascade order is configurable in your `config.php`. You can remove levels, reorder them, or add optional ones:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/config/config.php
|
||||
|
||||
return [
|
||||
'tobimori.seo' => [
|
||||
'cascade' => [
|
||||
'fields',
|
||||
'programmatic',
|
||||
'parent',
|
||||
'fallbackFields',
|
||||
'site',
|
||||
'options',
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
Remove an entry to skip that level entirely. For example, to disable parent inheritance:
|
||||
|
||||
```php
|
||||
'cascade' => [
|
||||
'fields',
|
||||
'programmatic',
|
||||
'fallbackFields',
|
||||
'site',
|
||||
'options',
|
||||
],
|
||||
```
|
||||
|
||||
## Restore the 1.x behavior
|
||||
|
||||
In 1.x, if you set an `ogDescription` at the site level, it applied to every page, even pages that had their own `metaDescription`. The page-specific description never made it into the Open Graph tags.
|
||||
|
||||
In 2.x, the `fallbackFields` level sits between `parent` and `site`, so a page's `metaDescription` is used as `ogDescription` before site-wide Open Graph values are reached.
|
||||
|
||||
To restore the 1.x behavior, remove `fallbackFields` from the cascade:
|
||||
|
||||
```php
|
||||
'cascade' => [
|
||||
'fields',
|
||||
'programmatic',
|
||||
'parent',
|
||||
'site',
|
||||
'options',
|
||||
],
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>If you used <code>fallbackFields</code> with additional mappings in 1.x</summary>
|
||||
|
||||
In 1.x, `fallbackFields` also mapped `ogTemplate` to `metaTemplate`. If you relied on this, you can restore it by extending the `Meta` class and overriding the `FALLBACK_MAP` constant:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use tobimori\Seo\Meta;
|
||||
|
||||
class MyMeta extends Meta
|
||||
{
|
||||
public const FALLBACK_MAP = [
|
||||
'ogDescription' => 'metaDescription',
|
||||
'ogTemplate' => 'metaTemplate',
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
Then register your class in the config. See [Extending the Plugin](2_customization/11_plugin-extensions) for details.
|
||||
|
||||
</details>
|
||||
66
site/plugins/kirby-seo/docs/2_customization/02_robots-txt.md
Normal file
66
site/plugins/kirby-seo/docs/2_customization/02_robots-txt.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
title: Customizing robots.txt
|
||||
intro: Add custom rules to your robots.txt
|
||||
---
|
||||
|
||||
By default, Kirby SEO generates a simple `robots.txt` that allows all crawlers and blocks the Panel. If you need to add your own rules, use the `robots.content` option.
|
||||
|
||||
## Blocking specific bots
|
||||
|
||||
Some AI providers crawl websites to use the content as training data. You can block their crawlers:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/config/config.php
|
||||
|
||||
return [
|
||||
'tobimori.seo' => [
|
||||
'robots' => [
|
||||
'content' => [
|
||||
'GPTBot' => [
|
||||
'Disallow' => ['/'],
|
||||
],
|
||||
'Google-Extended' => [
|
||||
'Disallow' => ['/'],
|
||||
],
|
||||
'CCBot' => [
|
||||
'Disallow' => ['/'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
This adds rules for each bot while keeping the default rules for all other crawlers intact.
|
||||
|
||||
## Custom rules for all crawlers
|
||||
|
||||
If you set rules for `*`, they replace the default rules entirely:
|
||||
|
||||
```php
|
||||
'content' => [
|
||||
'*' => [
|
||||
'Allow' => ['/'],
|
||||
'Disallow' => ['/panel', '/content', '/private'],
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
## Mixing rules
|
||||
|
||||
You can combine rules for all crawlers with rules for specific bots:
|
||||
|
||||
```php
|
||||
'content' => [
|
||||
'*' => [
|
||||
'Allow' => ['/'],
|
||||
'Disallow' => ['/panel', '/content'],
|
||||
],
|
||||
'GPTBot' => [
|
||||
'Disallow' => ['/'],
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
The `Sitemap:` line is added automatically if the [sitemap module](1_features/01_sitemap) is active. You can override it with the `robots.sitemap` option.
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
title: Opting Out of AI Training
|
||||
intro: Signal to AI crawlers that your content should not be used for training
|
||||
---
|
||||
|
||||
The `noai` and `noimageai` robot directives tell AI crawlers not to use your content or images for training. These are not an official standard, but were introduced by [DeviantArt and Spawning](https://www.deviantart.com/team/journal/UPDATE-All-Deviations-Are-Opted-Out-of-AI-Datasets-934500371) and are respected by some AI providers. Like all robot directives, they are signals, not hard blocks.
|
||||
|
||||
Kirby SEO has a `types` option that controls which robot directives are available. Add `ai` and `imageai` to the list:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/config/config.php
|
||||
|
||||
return [
|
||||
'tobimori.seo' => [
|
||||
'robots' => [
|
||||
'types' => ['index', 'follow', 'archive', 'imageindex', 'snippet', 'ai', 'imageai'],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
The new fields show up in the robots section of the SEO tab. If you previously disabled `robots.pageSettings`, you need to re-enable it for the fields to appear.
|
||||
|
||||
By default, all directives are set to "Yes" (allowed). To opt out of AI training, an editor needs to set the AI Training and AI Image Training fields to "No". The plugin then outputs `noai` and `noimageai` in the robots meta tag.
|
||||
|
||||
If you want to opt out for all pages at once, set it on the Site level instead of per page. Translations for the field labels are included in the plugin.
|
||||
77
site/plugins/kirby-seo/docs/2_customization/05_sitemap.md
Normal file
77
site/plugins/kirby-seo/docs/2_customization/05_sitemap.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
title: Customizing the Sitemap
|
||||
intro: Fine-tune the built-in sitemap or replace it entirely
|
||||
---
|
||||
|
||||
The built-in sitemap generator has a few options to adjust its behavior. For most sites, these are enough. If you need full control, you can replace the generator with your own.
|
||||
|
||||
## Excluding templates
|
||||
|
||||
By default, only the `error` template is excluded. To exclude more templates:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/config/config.php
|
||||
|
||||
return [
|
||||
'tobimori.seo' => [
|
||||
'sitemap' => [
|
||||
'excludeTemplates' => ['error', 'redirect', 'internal'],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Grouping by template
|
||||
|
||||
By default, all pages end up in a single sitemap. If you have many pages, you can split them into separate sitemaps per template. This creates a sitemap index at `/sitemap.xml` with links to `/sitemap-blog.xml`, `/sitemap-product.xml`, etc.
|
||||
|
||||
```php
|
||||
'sitemap' => [
|
||||
'groupByTemplate' => true,
|
||||
],
|
||||
```
|
||||
|
||||
## Change frequency and priority
|
||||
|
||||
Both `changefreq` and `priority` accept a static value or a callable:
|
||||
|
||||
```php
|
||||
'sitemap' => [
|
||||
'changefreq' => 'daily',
|
||||
'priority' => fn (Page $page) => $page->isHomePage() ? 1.0 : 0.5,
|
||||
],
|
||||
```
|
||||
|
||||
The default `changefreq` is `weekly`. The default `priority` is calculated from page depth: the homepage gets `1.0`, each level deeper subtracts `0.2`, down to `0.2`.
|
||||
|
||||
## Writing your own generator
|
||||
|
||||
If the options above aren't enough, you can replace the entire sitemap generator. The `generator` option takes a callable that receives a `SitemapIndex` instance. Here's a minimal example:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use tobimori\Seo\Sitemap\SitemapIndex;
|
||||
|
||||
return [
|
||||
'tobimori.seo' => [
|
||||
'sitemap' => [
|
||||
'generator' => function (SitemapIndex $sitemap) {
|
||||
$index = $sitemap->create('pages');
|
||||
|
||||
foreach (site()->index()->listed() as $page) {
|
||||
$index->createUrl($page->url())
|
||||
->lastmod($page->modified())
|
||||
->changefreq('weekly')
|
||||
->priority(0.8);
|
||||
}
|
||||
},
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
`$sitemap->create('key')` creates a sitemap group. `$index->createUrl($url)` adds a URL entry, and you can chain `->lastmod()`, `->changefreq()`, `->priority()`, and `->alternates()` on it.
|
||||
|
||||
The built-in generator does more: it filters by robots settings, respects `excludeTemplates`, handles `groupByTemplate`, and adds hreflang links for multilingual sites. You can find its source in `config/options/sitemap.php` as a reference for your own.
|
||||
134
site/plugins/kirby-seo/docs/2_customization/06_ai-assist.md
Normal file
134
site/plugins/kirby-seo/docs/2_customization/06_ai-assist.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
---
|
||||
title: Customizing AI Assist
|
||||
intro: Override prompts or add your own AI provider
|
||||
---
|
||||
|
||||
## Overriding prompts
|
||||
|
||||
AI Assist uses Kirby snippets for its prompts. You can override any of them by creating a snippet with the same path in your project.
|
||||
|
||||
The built-in prompt snippets are:
|
||||
|
||||
- `seo/prompts/tasks/title` - Meta title generation
|
||||
- `seo/prompts/tasks/description` - Meta description generation
|
||||
- `seo/prompts/tasks/og-description` - Open Graph description generation
|
||||
- `seo/prompts/tasks/site-description` - Site-level meta description
|
||||
- `seo/prompts/tasks/og-site-description` - Site-level OG description
|
||||
|
||||
To override the meta title prompt, create `site/snippets/seo/prompts/tasks/title.php` in your project. Kirby's snippet loading will pick up your version instead of the built-in one.
|
||||
|
||||
Each prompt snippet receives these variables:
|
||||
|
||||
- `$page` - the current page
|
||||
- `$site` - the site object
|
||||
- `$instructions` - custom instructions from the editor (if any)
|
||||
- `$edit` - the existing text when editing (if any)
|
||||
|
||||
There are also shared snippets that the task prompts include:
|
||||
|
||||
- `seo/prompts/introduction` - Defines the AI's role and rules
|
||||
- `seo/prompts/content` - Extracts the page content
|
||||
- `seo/prompts/meta` - Shows existing metadata for context
|
||||
|
||||
You can override these too. Look at the built-in prompts in `site/plugins/kirby-seo/snippets/prompts/` to understand their structure before writing your own.
|
||||
|
||||
## Adding a custom provider
|
||||
|
||||
If you need a provider that isn't built in, you can add your own. A provider has two parts: a driver class that handles the API communication, and a config entry that registers it.
|
||||
|
||||
Create a class that extends `tobimori\Seo\Ai\Driver`. The only method you need to implement is `stream`, which receives a prompt string and must yield `Chunk` objects as the response comes in.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Ai;
|
||||
|
||||
use Generator;
|
||||
use tobimori\Seo\Ai\Chunk;
|
||||
use tobimori\Seo\Ai\Driver;
|
||||
use tobimori\Seo\Ai\SseStream;
|
||||
|
||||
class MyProvider extends Driver
|
||||
{
|
||||
public function stream(string $prompt, string|null $model = null): Generator
|
||||
{
|
||||
$apiKey = $this->config('apiKey', required: true);
|
||||
$model = $model ?? $this->config('model', 'default-model');
|
||||
$endpoint = $this->config('endpoint', required: true);
|
||||
|
||||
$stream = new SseStream($endpoint, [
|
||||
'Content-Type: application/json',
|
||||
'Accept: text/event-stream',
|
||||
"Authorization: Bearer {$apiKey}",
|
||||
], [
|
||||
'model' => $model,
|
||||
'input' => $prompt,
|
||||
'stream' => true,
|
||||
], (int)$this->config('timeout', 120));
|
||||
|
||||
yield from $stream->stream(function (array $event): Generator {
|
||||
$type = $event['type'] ?? null;
|
||||
|
||||
if ($type === 'start') {
|
||||
yield Chunk::streamStart($event);
|
||||
}
|
||||
|
||||
if ($type === 'delta') {
|
||||
yield Chunk::textDelta($event['text'] ?? '', $event);
|
||||
}
|
||||
|
||||
if ($type === 'done') {
|
||||
yield Chunk::streamEnd($event);
|
||||
}
|
||||
|
||||
if ($type === 'error') {
|
||||
yield Chunk::error($event['message'] ?? 'Unknown error', $event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The driver uses `$this->config()` to read values from the provider's `config` array in `config.php`. Pass `required: true` to throw an error if a value is missing.
|
||||
|
||||
`SseStream` is a helper class included in Kirby SEO that handles the cURL request and SSE parsing. You pass it the endpoint, headers, payload, and a mapper function that converts raw SSE events into `Chunk` objects.
|
||||
|
||||
If your API doesn't use SSE, you can skip `SseStream` and yield chunks directly.
|
||||
|
||||
The chunks the Panel expects, in order:
|
||||
|
||||
1. `Chunk::streamStart()` - Signals the stream has started
|
||||
2. `Chunk::textDelta($text)` - Each piece of generated text (repeated)
|
||||
3. `Chunk::textComplete()` - The text is done
|
||||
4. `Chunk::streamEnd()` - The stream is finished
|
||||
|
||||
If something goes wrong, yield `Chunk::error($message)` at any point.
|
||||
|
||||
## Registering the provider
|
||||
|
||||
Add your driver to the config and set it as the active provider:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/config/config.php
|
||||
|
||||
return [
|
||||
'tobimori.seo' => [
|
||||
'ai' => [
|
||||
'provider' => 'myprovider',
|
||||
'providers' => [
|
||||
'myprovider' => [
|
||||
'driver' => \App\Ai\MyProvider::class,
|
||||
'config' => [
|
||||
'apiKey' => 'sk-...',
|
||||
'model' => 'my-model',
|
||||
'endpoint' => 'https://api.example.com/v1/chat',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
See the built-in drivers in `site/plugins/kirby-seo/classes/Ai/Drivers/` for complete implementations.
|
||||
45
site/plugins/kirby-seo/docs/2_customization/07_gsc-setup.md
Normal file
45
site/plugins/kirby-seo/docs/2_customization/07_gsc-setup.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: Setting up Google Search Console
|
||||
intro: Connect Search Console with your own Google OAuth credentials
|
||||
---
|
||||
|
||||
By default, the Search Console integration uses a proxy to keep setup simple. If you'd rather connect directly, you can set up your own Google OAuth credentials instead. This requires a Google Cloud project with the Search Console API enabled. The API is free to use.
|
||||
|
||||
## Create OAuth credentials
|
||||
|
||||
Go to the [Google Cloud Console](https://console.cloud.google.com/) and create a new project, or use an existing one.
|
||||
|
||||
Navigate to **APIs & Services** → **Credentials** → **Create Credentials** → **OAuth client ID** and configure it:
|
||||
|
||||
- **Application type:** Web application
|
||||
- **Name:** e.g. "Kirby SEO on example.com"
|
||||
- **Authorized redirect URIs:** your site URL followed by `/__seo/gsc/callback`, e.g. `https://example.com/__seo/gsc/callback`
|
||||
|
||||
Download the JSON file when prompted. You'll need it in the next step.
|
||||
|
||||
Then go to **APIs & Services** → **Library**, search for "Google Search Console API" and enable it. Without this, the OAuth flow will succeed but the API requests will fail.
|
||||
|
||||
## Add credentials to your config
|
||||
|
||||
Place the downloaded JSON file in your `site/config` directory (e.g. `site/config/gsc-credentials.json`), then reference it in your config:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/config/config.php
|
||||
|
||||
use Kirby\Data\Json;
|
||||
|
||||
return [
|
||||
'tobimori.seo' => [
|
||||
'searchConsole' => [
|
||||
'credentials' => Json::read(__DIR__ . '/gsc-credentials.json'),
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Connect in the Panel
|
||||
|
||||
Open the Panel and navigate to any page with the SEO tab. The Google Search Console section now shows a **Connect** button. Click it and authorize with your Google account. Make sure the Google account you use has access to the Search Console property for your site.
|
||||
|
||||
After authorizing, select which Search Console property to use. The section starts showing data once the property is selected.
|
||||
89
site/plugins/kirby-seo/docs/2_customization/08_schema-org.md
Normal file
89
site/plugins/kirby-seo/docs/2_customization/08_schema-org.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
---
|
||||
title: Schema.org (JSON-LD)
|
||||
intro: Add structured data to your pages
|
||||
---
|
||||
|
||||
Kirby SEO can output Schema.org structured data as JSON-LD. It uses the [spatie/schema-org](https://github.com/spatie/schema-org) package, which must be installed separately:
|
||||
|
||||
```bash
|
||||
composer require spatie/schema-org
|
||||
```
|
||||
|
||||
Once installed, a `WebSite` schema is generated automatically for every page with the page's title, description, and canonical URL. You can build on top of this or add your own schemas.
|
||||
|
||||
## Adding structured data
|
||||
|
||||
The plugin exposes a global store for Schema.org objects. You can access it from templates, snippets, or block snippets using `$page->schema()` and `$site->schema()`. Calling the same type twice returns the same instance, so you can build up a schema across different files.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/templates/article.php
|
||||
|
||||
$page->schema('Article')
|
||||
->headline($page->title()->value())
|
||||
->datePublished($page->date()->toDate('c'))
|
||||
->author(
|
||||
schema('Person')
|
||||
->name($page->author()->value())
|
||||
);
|
||||
```
|
||||
|
||||
`$page->schema($type)` returns the stored schema for that type, or creates a new one if it doesn't exist yet. Both also exist as `$site->schema()` and `$site->schemas()` for site-level schemas.
|
||||
|
||||
The global `schema($type)` function creates a new instance without storing it. Use it for nested objects like the `Person` above that don't need their own top-level entry.
|
||||
|
||||
## Building schemas across blocks
|
||||
|
||||
Because `$page->schema()` always returns the same instance, you can add to a schema from individual block snippets. This is useful for types like `FAQPage` where the content comes from multiple blocks:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/snippets/blocks/faq.php
|
||||
|
||||
$page->schema('FAQPage')
|
||||
->mainEntity([
|
||||
...($page->schema('FAQPage')->getProperty('mainEntity') ?? []),
|
||||
schema('Question')
|
||||
->name($block->question())
|
||||
->acceptedAnswer(
|
||||
schema('Answer')->text($block->answer())
|
||||
),
|
||||
]);
|
||||
```
|
||||
|
||||
Each block appends its question to the `mainEntity` array. The final output combines all of them:
|
||||
|
||||
```json
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
"mainEntity": [
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "How does it work?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "It works like this."
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "Can it handle multiple blocks?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "Yes, it can."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Disabling the default schema
|
||||
|
||||
If you don't want the automatic `WebSite` schema, disable it in your config:
|
||||
|
||||
```php
|
||||
'tobimori.seo' => [
|
||||
'generateSchema' => false,
|
||||
],
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: Optimizing Head Order
|
||||
intro: Place high-priority elements before stylesheets and scripts
|
||||
---
|
||||
|
||||
The order of elements in the `<head>` can affect perceived page performance. Ideally, the `<title>` element should appear early, before stylesheets and scripts, while other meta tags like Open Graph and description can go last. See [capo.js](https://rviscomi.github.io/capo.js/) for background on why this matters.
|
||||
|
||||
By default, `seo/head` outputs all tags in one block. If you want to split priority tags from the rest, use Kirby's [snippet slots](https://getkirby.com/docs/guide/templates/snippets#passing-data-to-snippets__slots):
|
||||
|
||||
```php
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<?php snippet('seo/head', slots: true) ?>
|
||||
<link rel="stylesheet" href="/assets/css/main.css">
|
||||
<script src="/assets/js/app.js" defer></script>
|
||||
<?php endsnippet() ?>
|
||||
</head>
|
||||
```
|
||||
|
||||
This outputs the `<title>` first, then your stylesheets and scripts from the slot, then the remaining meta tags (description, Open Graph, robots, etc.).
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Setup Background Processing
|
||||
intro:
|
||||
---
|
||||
|
||||
Coming soon
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: Extending the Plugin
|
||||
intro: Replace built-in classes with your own
|
||||
---
|
||||
|
||||
Kirby SEO uses a component system similar to [Kirby's own](https://getkirby.com/docs/reference/plugins/components). Every major class in the plugin can be swapped out for a custom one. This lets you change how the plugin works without forking it.
|
||||
|
||||
The built-in components are:
|
||||
|
||||
| Key | Default class | Handles |
|
||||
| ---------- | ---------------------------------- | --------------------------------- |
|
||||
| `meta` | `tobimori\Seo\Meta` | Meta tag generation and cascading |
|
||||
| `ai` | `tobimori\Seo\Ai` | AI Assist provider management |
|
||||
| `indexnow` | `tobimori\Seo\IndexNow` | IndexNow ping requests |
|
||||
| `schema` | `tobimori\Seo\SchemaSingleton` | Schema.org structured data store |
|
||||
| `gsc` | `tobimori\Seo\GoogleSearchConsole` | Google Search Console integration |
|
||||
|
||||
To replace a component, create a class that extends the original. For example, to customize meta tag output, extend the `Meta` class:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/plugins/my-seo/index.php
|
||||
|
||||
use tobimori\Seo\Meta;
|
||||
|
||||
class MyMeta extends Meta
|
||||
{
|
||||
// override any method you need
|
||||
}
|
||||
```
|
||||
|
||||
Then register your class in the config:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// site/config/config.php
|
||||
|
||||
return [
|
||||
'tobimori.seo' => [
|
||||
'components' => [
|
||||
'meta' => MyMeta::class,
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
The rest of the plugin picks up your class automatically. Page methods, hooks, routes, and sections all resolve components through the config, so your class is used everywhere the original would have been. Look at the built-in classes in `site/plugins/kirby-seo/classes/` to see what methods are available to override.
|
||||
Loading…
Add table
Add a link
Reference in a new issue