You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+24Lines changed: 24 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,29 @@
1
1
# Changelog
2
2
3
+
## Unreleased
4
+
5
+
- The **SEO** entry in the control panel sidebar no longer doubles up: core's own entry for the addon, nested under Tools → Addons, is removed since this addon already links to the same settings screen.
6
+
- Per-site overrides are a collapsible replicator rather than a stacked grid, so a site's seven fields fold away into one row.
7
+
- Migrates from `statamic/seo-pro`: its per-entry `seo` array and its site defaults are translated to this addon's handles and settings, and are read at render time until you migrate. Values pointing at another field (`@seo:content/title`) and values containing Antlers are dropped rather than rendered literally.
8
+
-`php please vulpo:seo:import-redirects file.csv` imports redirects in bulk, with an optional header row, column aliases and a dry run.
9
+
- Every string ships in `lang/en.json`, publishable with the `vulpo-seo-translations` tag, so the addon can be translated.
10
+
-`/sitemap.xml`, `/robots.txt` and `/llms.txt` send `Cache-Control` with `stale-while-revalidate`, built from their `cache_minutes` config.
11
+
- Canonical URLs keep the pagination parameter so page 2 points at itself, and `seo.canonical.trailing_slash` can force a trailing slash on or off.
12
+
- Fixed: entries that only redirect somewhere else (Statamic's `redirect` field, including `redirect: 404`) were listed in the sitemap and llms.txt. Because `absoluteUrl()` returns the destination for such an entry, the sitemap advertised the redirect target as one of the site's own pages.
13
+
- Fixed: a value on a legacy handle was masked by the new field's untouched toggle, which augments to `false` rather than to nothing.
14
+
15
+
## 1.4.0
16
+
17
+
- Redirects and the 404 log are scoped to a site. A rule can apply to one site or to all of them, automatic redirects belong to the entry's site, and the same path on two sites no longer overwrites itself. Existing rules keep working: no site means every site.
18
+
- Redirects match query strings, so legacy URLs like `/index.php?id=42` can be redirected. A rule that matched on the query string no longer has it appended to the destination again.
19
+
- The sitemap emits `xhtml:link` hreflang alternates on a multisite install, and turns into a sitemap index once the URLs pass the per-file limit instead of silently dropping the overflow.
20
+
- Open Graph article properties (`article:published_time`, `article:modified_time`, `article:author`) and an optional `twitter:creator`.
21
+
- Structured data gains Product and Event types, plus a raw JSON-LD field for anything the addon does not model. Invalid JSON is ignored rather than breaking the page.
22
+
- The 404 log is searchable and paginated.
23
+
-`view vulpo seo` and `edit vulpo seo` are separate permissions, so a role can be read-only.
24
+
-`php please vulpo:seo:export-to-files` mirrors the import command.
25
+
- Fixed: `routes/web.php` still read the old `vulpo-seo.*` config keys, so the sitemap, robots and llms.txt routes ignored the published config and used their defaults.
26
+
3
27
## 1.3.0
4
28
5
29
- Works with statamic/eloquent-driver. Redirects, the 404 log, the AI crawler log and the URL index move to database tables on a site that keeps its content in the database, and stay in flat files on one that does not. Detection follows the eloquent driver's own configuration and can be overridden with `seo.storage.driver`.
The suite runs against flat-file storage by default. To run it against the database driver:
14
+
15
+
```bash
16
+
VULPO_SEO_STORAGE_DRIVER=eloquent vendor/bin/pest
17
+
```
18
+
19
+
CI runs both, so a change to storage needs to pass both.
20
+
21
+
## Pull requests
22
+
23
+
- One change per pull request.
24
+
- Add a test. Bugs get a test that fails before the fix; features get a test for the behaviour, not the implementation.
25
+
- Run `vendor/bin/pint` before pushing.
26
+
- Note anything user-visible in `CHANGELOG.md` under "Unreleased".
27
+
28
+
## Reporting a bug
29
+
30
+
Include the Statamic and PHP version, whether the site uses `statamic/eloquent-driver`, and the smallest reproduction you can manage. A failing test is the fastest possible bug report.
31
+
32
+
## Adding a schema type or a crawler
33
+
34
+
New `@type` handling belongs in `src/Seo/Schema.php` with fields in `resources/blueprints/`, and both need a case in `tests/SchemaTest.php`. New AI crawlers are a line in the `ai_crawlers.agents` config; the user agent pattern is matched case-insensitively.
Copy file name to clipboardExpand all lines: README.md
+42-7Lines changed: 42 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,13 +10,13 @@ It replaces the stack many Statamic sites run today — `alt-design/alt-seo`, `a
10
10
11
11
**Meta tags** — title, description, canonical, robots, Open Graph, Twitter cards, hreflang and search console verification from one tag. Per-page fields override site-wide defaults, and each site can override the defaults again.
12
12
13
-
**Sitemap** — `/sitemap.xml`, built from published, routable entries (and optionally taxonomy terms), with per-page "leave out of the sitemap" and collection exclusions. Cached, and flushed when content changes.
13
+
**Sitemap** — `/sitemap.xml`, built from published, routable entries (and optionally taxonomy terms), with per-page "leave out of the sitemap" and collection exclusions. Emits `xhtml:link` hreflang alternates on a multisite install, and becomes a sitemap index (`/sitemap-1.xml`, …) once the URLs no longer fit one file. Cached, and flushed when content changes.
14
14
15
-
**Redirects** — a CP screen with exact, wildcard and regex rules (301, 302, 410). Redirects are checked only when a URL would otherwise 404, so normal page loads pay nothing. When a page's URL changes — renamed slug, moved in the structure, changed date — a redirect is created automatically, plus a wildcard rule for its children.
15
+
**Redirects** — a CP screen with exact, wildcard and regex rules (301, 302, 410), each scoped to one site or to all of them. Query strings are supported, so legacy `/index.php?id=42` URLs can be matched. Redirects are checked only when a URL would otherwise 404, so normal page loads pay nothing. When a page's URL changes — renamed slug, moved in the structure, changed date — a redirect is created automatically for that site, plus a wildcard rule for its children.
16
16
17
-
**404 log** — every miss is logged with hit counts and referrer, and can be turned into a redirect in one click.
17
+
**404 log** — every miss is logged per site with hit counts and referrer, searchable and paginated, and can be turned into a redirect in one click.
18
18
19
-
**Structured data (JSON-LD)** — Organization or LocalBusiness, WebSite, BreadcrumbList, and a per-page FAQ, Article, Serviceor Person node. Coordinates are geocoded from the address for free through OpenStreetMap and cached.
19
+
**Structured data (JSON-LD)** — Organization or LocalBusiness, WebSite, BreadcrumbList, and a per-page FAQ, Article, Service, Person, Product or Event node, plus a raw JSON-LD field for anything else. Coordinates are geocoded from the address for free through OpenStreetMap and cached.
20
20
21
21
**llms.txt** — `/llms.txt` describing the site and its pages for AI assistants, with an editable summary.
22
22
@@ -79,7 +79,7 @@ In Blade:
79
79
{!! Statamic::tag('vulpo_seo') !!}
80
80
```
81
81
82
-
## Migrating from alt-seo, alt-sitemap, alt-redirects or vulpo/geo
82
+
## Migrating from SEO Pro, alt-seo, alt-sitemap, alt-redirects or vulpo/geo
83
83
84
84
```bash
85
85
php please vulpo:seo:migrate --dry-run # see what would change
@@ -88,9 +88,20 @@ php please vulpo:seo:migrate
88
88
89
89
The command renames legacy field handles on every entry and term (`alt_seo_meta_title` → `seo_title`, `geo_faqs` → `seo_schema_faqs`, …), copies the old global settings into the addon settings, and imports any redirects it finds.
90
90
91
-
Until you run it, legacy handles are still read at render time, so nothing breaks the moment you swap addons. Set `legacy_fallbacks` to `false`in the config once you have migrated.
91
+
SEO Pro keeps everything in one `seo` array per entry, so it is translated rather than renamed: `title`, `description`, `canonical_url`, `image`, `robots_indexing`/`robots_following`, `sitemap`, `priority`, `change_frequency` and `json_ld_schema` all find a home, and its site defaults are read from `resources/addons/seo-pro.yaml`. Two things do not travel, because they would render literally in a meta tag: values pointing at another field (`@seo:content/title`) and values containing Antlers. The `seo` array itself is left in place, so SEO Pro keeps working if you have not removed it yet.
92
92
93
-
Replace `{{ alt_seo:meta }}` and `{{ structured_data }}` in your layout with `{{ vulpo_seo }}`, then remove the old addons from `composer.json`.
93
+
Until you run it, legacy handles — including SEO Pro's `seo` array — are still read at render time, so nothing breaks the moment you swap addons. Set `legacy_fallbacks` to `false` in the config once you have migrated.
94
+
95
+
Replace `{{ alt_seo:meta }}`, `{{ seo_pro:meta }}` and `{{ structured_data }}` in your layout with `{{ vulpo_seo }}`, then remove the old addons from `composer.json`.
Columns are `from,to,status,site`; a header row is optional and `source`/`destination`/`code` are accepted as aliases. A `*` in the source path makes it a wildcard rule. Rows missing a source or destination are skipped and counted rather than failing the import.
94
105
95
106
## Configuration
96
107
@@ -116,6 +127,7 @@ Published blueprints in `resources/blueprints/vendor/vulpo-seo/` win over the ad
php please statamic:eloquent:import-addon-settings # Statamic's own, for the settings
155
167
```
156
168
169
+
Going the other way, `php please vulpo:seo:export-to-files` writes the database rows back out as YAML.
170
+
157
171
`import-to-database` leaves the flat files alone, so switching back is a config
158
172
change. Re-running it updates rather than duplicating.
159
173
@@ -183,8 +197,29 @@ the same block.
183
197
- Automatic redirects react to entry saves. Statamic rewrites child URLs without saving each child, which is why a parent change also adds a `parent/*` wildcard rule.
184
198
- Control panel screens use Statamic's own UI components. Every export of the CP's `@ui` package is registered globally as a `ui-<kebab-name>` Vue component, and the CP compiles a Blade view's output as an in-DOM template, so `<ui-card-panel>`, `<ui-table>` and friends work straight from Blade and the screens match the CP. Two things not to try instead: a `<style>` block in a CP view (the Vue app drops it) and Tailwind variants the CP bundle never compiled (`sm:grid-cols-2` and the like are absent, plain utilities are fine).
185
199
- The config file is `config/seo.php`, not `config/vulpo-seo.php`. Statamic derives an addon's slug from the package name, and a custom `extra.statamic.slug` breaks core's settings lookup: settings are written to `resources/addons/{slug}.yaml` but read from `resources/addons/{package-name}.yaml`.
200
+
- Canonical URLs drop the query string, except the pagination parameter (`?page=2` points at itself, so page 2 is not read as a duplicate of page 1). `seo.canonical.trailing_slash` forces a trailing slash on or off; null leaves URLs alone.
201
+
- Entries that only redirect — Statamic's `redirect` field, including `redirect: 404` — are left out of the sitemap and llms.txt. Their `absoluteUrl()` returns the destination, so listing them would advertise another page's URL as one of yours.
202
+
-`/sitemap.xml`, `/robots.txt` and `/llms.txt` send `Cache-Control` built from their `cache_minutes` config, with `stale-while-revalidate` so a CDN never makes a crawler wait for a rebuild. Setting `cache_minutes` to 0 sends `no-store`.
203
+
- The redirects screen is a grid holding every rule at once. It is comfortable into the hundreds; past that, edit `content/vulpo-seo/redirects.yaml` (or the table) directly and use the CSV import for bulk work.
186
204
- Geocoding uses OpenStreetMap Nominatim, whose usage policy requires a descriptive User-Agent; set `VULPO_SEO_GEOCODER_USER_AGENT` for production.
187
205
206
+
## Translations
207
+
208
+
Every string the addon renders goes through `__()`, and English lives in `lang/en.json`. To translate it, publish that file and drop your own locale next to it:
A role with only the view permission sees the data and no write controls.
222
+
188
223
## Extending
189
224
190
225
The preview field is a normal fieldtype, so you can move it, drop it, or add it to a blueprint of your own by publishing the blueprints and editing them.
Email **security@vulpo.be** rather than opening a public issue. Include the affected version, what an attacker can do, and a reproduction if you have one. You will get an acknowledgement within three working days.
10
+
11
+
Please do not test against sites you do not own.
12
+
13
+
## Scope notes
14
+
15
+
The addon serves public text routes (`/robots.txt`, `/llms.txt`, `/sitemap.xml`) built from control panel settings, and logs 404s and AI crawler visits including the requesting path and referer. Reports about data reachable through those routes, about the control panel screens honouring the `view`/`edit vulpo seo` permissions, or about redirect rules being used to reach somewhere unintended are all in scope.
0 commit comments