@plumix/plugin-seo
v0.1.2
Published
SEO plugin for Plumix — head meta, Open Graph, structured data, robots.txt and the sitemap
Maintainers
Readme
@plumix/plugin-seo
This Plumix plugin writes what a site tells a search engine — the head meta a public page needs (a description, a robots directive, the Open Graph set, the Twitter card, and the resolved social image), its structured data, plus /robots.txt, the sitemap, and a per-entry and per-term box where an editor overrides any of it. Core emits a canonical URL and nothing else, so without this plugin a page carries none of them and the site serves neither endpoint.
Install
pnpm add @plumix/plugin-seoThen add it to your plumix.config.ts:
import { plumix } from "plumix";
import { seo } from "@plumix/plugin-seo";
export default plumix({
// …your runtime, database, and auth
plugins: [seo()],
});What you get
- A description and a robots directive — the entry's excerpt falling back to the site tagline, and a directive decided by the assertion chain below.
- The Open Graph set —
og:title,og:type,og:url,og:site_name,og:description,og:locale, plusarticle:published_time,article:modified_timeandarticle:authoron a single entry. - The Twitter card —
summary_large_imagewhen a social image resolved,summarywhen none did. - The
og:imagechain — the entry's explicit.ogImage()choice, then the SEO box's own social image URL, then whatever aseo:og_imagesubscriber supplies, then the entry's.featured()photo, then the site-wide default. The order is fixed, so a generated card never outranks a deliberate choice. - A JSON-LD graph on every indexable page — website, publisher, page, article, breadcrumbs, image and author, cross-referenced by URL fragment so the pieces point at each other instead of repeating themselves. Identifiers derive from the canonical URL, so two renders of one URL produce the same graph. Three filters take it apart:
seo:schema:needsdrops a piece,seo:schema:piecereshapes one,seo:schema:graphreplaces the lot. Serialization is this plugin's, escapes included, so a hostile title cannot close the script element. - Breadcrumbs — a
BreadcrumbListin the graph and a<Breadcrumbs data={data} />component a theme renders, both built from one trail, so the page and the search result cannot disagree. - A per-entry and per-term SEO box on every publicly-visible entry type and taxonomy — a search title, a search description, a canonical override, a social image,
noindex/nofollowflags, and (on entries) the schema.org type of the article piece, stored underseo_-prefixed meta keys and saved with the entity's own Save. Exclude a type withseo({ metaBox: { exclude: ["landing_page"] } }). - One indexability predicate behind the robots directive, with the sitemap asking the same questions of whole tables — so a page marked
noindexcannot still appear in the sitemap. It is an ordered set of named assertions that short-circuits on the first that fires:site_private,entry_override,type_default,taxonomy_default,search_results,paginated,not_found, thendefault. It reports which one answered rather than a bare boolean. - Per-type title patterns — a line of
%%variables%%(title,sitename,sep,term,author,date,searchphrase,count) set per entry type, with a site-wide default for everything else. An entry's own search title outranks both, an empty variable is dropped along with any separator left holding nothing together, and an unknown name is dropped rather than shipped into a search result. - Per-type and per-taxonomy indexing defaults, so a whole class of content leaves both the index and the sitemap at once; plus toggles for the three arms that are off by default — search results, paginated archives and pages that were not found.
- A live search-result preview at the top of the box on an entry, with length indicators on the search title and description, updating as the author types — and, when the page is held out of search, the reason in plain words.
/robots.txt— allow-all while indexing is on, disallow-all when it is off, editable from the admin, always carrying aSitemap:line, and adjustable through theseo:robots-txtfilter. Turning Block AI crawlers on adds one group disallowing the crawlers that feed model training and assistant answers, leaving ordinary search crawlers alone.- The sitemap —
/sitemap.xmlindexing one/sitemap-<scope>-<page>.xmlper public entry type, taxonomy and registered archive, paged at 1,000 URLs, published entries only, adjustable through theseo:sitemap:urlsfilter. Responses carry cache headers and per-scope purge tags, so publishing an entry retires that scope alone. An entry's.featured()and.ogImage()pictures ride its<url>as image entries — images only, absolutized, at most ten per URL, resolved in one batched pass per page — and every document names/sitemap.xslso it reads as a table in a browser that still runs XSLT, and as XML to a crawler. /llms.txt— the llmstxt.org convention: the site name, its tagline and a link to the sitemap, adjustable through theseo:llms-txtfilter. A site held out of the index, or one blocking AI crawlers, is served the heading without the map.- IndexNow notification — set a key and publishing or updating an entry submits its URL to the shared endpoint, so a change is picked up in minutes rather than at the next crawl. One submission per entry per request, deferred past the response and swallowing every failure, so an unreachable endpoint is a missed notification and not a failed publish. Off until a key is set.
- A settings page — three groups, each gated by
settings:manage: the site-wide indexing toggle (which drives the robots directive on every page,robots.txt, the sitemap and thellms.txtmap), the default social image, whether the site represents an organization or a person, the AI-crawler toggle, the IndexNow key and the per-type answers; search-engine verification tokens; and therobots.txteditor.
Every tag is gap-filled: it is appended only when nothing has already set that key. The contribution runs last on the render:document chain whatever order the plugins array is in, so a theme's own head tags keep winning — and so do another plugin's.
@plumix/plugin-og contributes one link of the chain above and needs this plugin installed to reach a page's head.
Support
Have a question? Start a discussion. Found a bug? Open an issue.
Contributing
PRs and ideas welcome. The Contributing guide gets you set up — new contributors especially welcome.
License
MIT © Plumix Contributors
