All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[1.8.1] - 2026-10-22
Fixed
- Redirect lookups no longer take a global cache lock. Every
uncached path sent through
RedirectService::findMatch()(and so theHandleRedirectsmiddleware) blocked for up to 5s on one shared lock to add itself to a tracked-paths array. With a database cache store, crawlers hitting unique URLs queued PHP workers behind that lock, and aLockTimeoutExceptionturned the page view into a 500. The array also grew without limit, andclearCache()forgot each tracked key one by one. Match cache keys now include a unique generation token, andclearCache()replaces it in a single write; old entries expire through their TTL. A token is never reused, so overlapping clears or an evicted token can't bring back stale lookups. The oldseo:redirects:cached_pathskey is forgotten on the next clear (#111). - Sitemap cache clears can no longer bring back stale sitemaps.
SitemapService::clearCache()read the generation counter and wrote it back plus one, so overlapping clears (SitemapObserverclears on every tracked save, force delete, and restore) could write an older generation and serve stalesitemap.xml,sitemap-*-N.xml, andllms.txtuntil the TTL expired. A missing counter also fell back to1, reusing an old namespace if the store evicted the key. The generation is now a unique token thatclearCache()replaces, and a missing token starts a fresh one. The protectedSitemapService::getCacheGeneration()now returnsstringinstead ofint(#113).
[1.8.0] - 2026-10-20
Added
ap.seo.resolveMorphModelfilter for morph types that aren't class names. A model whosegetMorphClass()returns something likeApp\Models\Record:products(one model shared by several tables) stored aseoable_type/sitemapable_typethat Eloquent tried to instantiate as a class, soSeoMeta::seoableandSitemapEntry::sitemapablethrewClass "…" not found. Both relations now ask the filter for a configured model instance first, for lazy loading and forwith( 'seoable' )/with( 'sitemapable' ), through the newResolvesMorphModelstrait andResolvableMorphTorelation.ArtisanPackUI\SEO\Support\MorphModelResolverwraps the filter. Types the filter doesn't resolve behave as before.
Fixed
HreflangServicecache clearing works for morph-map aliases and resolver-backed types. It used to skip anyseoable_typethat wasn't a class name. It now checksMorphModelResolver::canResolve().- Meta and analysis cache keys include the model's table. Keys were
meta:<class>:<id>, so records of one model class stored in several tables shared a cache entry and could serve each other's SEO data. They are nowmeta:<class>:<table>:<id>(and the same foranalysis:). Existing entries simply miss once and are rebuilt.
[1.7.1] - 2026-10-19
Fixed
SeoMetalookups now recognize relations bound withModel::resolveRelationUsing()(#104). Every lookup was gated onmethod_exists( $model, 'seoMeta' ), which misses dynamic relations served byModel::__call(). Models that couldn't take theHasSeotrait were treated as having no SEO meta. Content analysis reported "Meta description is missing." on every record, skipped the persistedfocus_keyword, and never cached results. A newArtisanPackUI\SEO\Support\SeoMetaResolverchecks both declared methods andModel::isRelation(). All call sites now use it, includingAnalysisService,SeoService,HreflangService,VisualEditorIntegration,AnalysisApiController,SeoObserver, theMetaTags/OpenGraph/TwitterCardcomponents and theseoMeta()helper.SeoService::updateSeoMeta()refreshes the loadedseoMetarelation (#105). A model whoseseoMetarelation had already been loaded asnullkept that cachednullafterupdateSeoMeta()created a new row, so later lookups on the same instance missed the new metadata. The saved record is now set back onto the relation.
CI
- Release workflow gains a
sync-docsjob that queues the documentation and changelog imports on docs.artisanpackui.dev after each release (#103). It skips with a notice when theDOCS_API_TOKENsecret is not set; the package slug can be overridden with aDOCS_PACKAGE_SLUGrepository variable.
[1.7.0] - 2026-09-20
Added
- Extension seam for
AnalysisService::extractContent()(#100). SEO analyzers can now see template-provided markup (e.g. an H1 rendered by a page-title bar, hero component, orsingle-*.blade.php) instead of scoring only the model's content field. Two hooks land together: theap.seo.analysisContentfilter fires with the resolved content and the model so hosts can append template markup, and a newArtisanPackUI\SEO\Contracts\SeoAnalyzableContentinterface lets models return the exact HTML analyzers should reason over viagetSeoAnalysisHtml(). Fixes theHeadingStructureAnalyzerfalse "No H1 heading found" flag on pages whose H1 lives outside the content body. seo_analysis_cache.content_hashcolumn + fingerprint-aware cache lookup (#100). The analysis cache now fingerprints the resolved-and-filtered HTML on write and compares it on read, so template-provided markup orap.seo.analysisContentfilter output changes invalidate the cached result even when the model's content field is untouched. Existing rows without a hash are treated as a miss so they refresh on next run.
[1.6.0] - 2026-09-17
Changed
- Default OG card layout is now bottom-left anchored (#97). The
title stack sits against the padded bottom of the canvas (above the
subtitle when one is present, on the padded bottom edge when
title-only), left-aligned at the template padding so it sits
directly under the top-left logo. Matches the MightyShare-style
layout the scrim was designed for and gives the title real breathing
room over a background photo. Anchor points will be lifted onto
OgImageTemplatein a follow-up so operators can toggle bottom / center / top layouts from config.
Added
GdOgImageRenderernow composites a semi-transparent scrim between the background image and the text so title / subtitle text has usable contrast on top of real photos (#97). Only applied whenbackground_image_pathis set — plain-colored backgrounds are unaffected.- New
OgImageTemplatefields —backgroundScrimColor(hex),backgroundScrimOpacity(percentage 0-100, default 60), andbackgroundScrimGradient(bool, default true) — plumbed throughfromConfig(), cache signature, andconfig('seo.og_image.template')asbackground_scrim_color/background_scrim_opacity/background_scrim_gradient. Set opacity to 0 to disable the scrim. - With
background_scrim_gradient = true(the default), the scrim fades from ~25% of the configured opacity at the top down to full opacity at the bottom so the darkening concentrates where the text sits and the top of the background image stays visible.
[1.5.0] - 2026-09-15
Added
MetaTitleSuggestionAgentandMetaDescriptionAgentnow acceptn(default5, clamped to[1, 10]) and return that many deduplicated variants per call. Callers that want a single result read$variants[0]— the array shape is uniform across both agents (#94).- Both agents accept an optional
h1and drop any variant that restates the H1 verbatim, so the meta line adds context instead of echoing the visible headline (#94). - Golden-file test pins the rendered prompts for both agents
(
tests/Feature/Ai/MetaAgentPromptGoldenTest.php), so prompt regressions surface as a diff instead of silent quality drift.
Changed
MetaDescriptionAgentoutput is now{ variants: [...] }(BREAKING). The old single-object shape (meta_description/character_count/rationaleat the top level) is gone; take$variants[0]in downstream code. Seedocs/upgrade-1.5.0.mdfor the migration.- Meta prompts rewritten with a real quality pass — front-load the specific value, forbid brand-first titles unless the page is about the brand, ban filler openers ("Discover…", "Learn…", "Ultimate guide…", "Welcome to…"), require active voice, and include few-shot examples pulled from representative SERP snippets (#94).
MetaDescriptionAgent::MIN_LENGTHraised from120to150so the validator matches the prompt contract; variants outside the full 150–160 window are rejected rather than truncated.MetaDescriptionSuggestorLivewire component + view render the variants list, matchingMetaTitleSuggestor.
Fixed
- Both agents now raise
FeatureErrorwhen zero variants survive validation, so callers get an explicit failure instead of a silent empty list. - Variants that overshoot the length cap are rejected outright instead of being truncated — a truncated title/description loses the front-load discipline the prompt optimizes for.
[1.4.0] - 2026-09-05
Added
llms.txtAI-discovery manifest generator driven by the same indexableSitemapEntrysource as the XML sitemap, with a newap.seo.llmsTxtEntriesfilter hook and an opt-inGET /llms.txtroute toggle (#68).- IndexNow submitter + sitemap-ping path for Bing/Yandex/Seznam/
Naver with a configurable key provider contract and an opt-in
GET /{key}.txtkey-verification route (#69). - Config-driven AI-crawler robots controls grouping related bots
(OpenAI, Anthropic, Google-Extended, Perplexity, Common Crawl,
ByteDance, Meta) with a working
default_allowkill switch (#70). - RSS 2.0 and Atom 1.0 feed generator with sanitized XML output,
an opt-in
/feed.xml+/feed.atomroute toggle backed by aFeedProviderContractbinding, aseo.feedsconfig block, and theap.seo.feedEntriesfilter hook (#72, #88). - Organization
sameAs+logoon the schema graph, withsameAsURL validation and a width/height-carryingImageObjectfor the publisher logo (#73). - OG image generator service (MightyShare-style social cards) rendered via GD, deterministically cached, alpha-preserving, and under a cache lock to coalesce concurrent misses (#74).
apSeoAddSchema()helper +SchemaCollectorservice (#77) andSchemaServicerender-pipeline centralization (#78).- Stable
@idon every schema graph node (Organization, LocalBusiness, WebSite, WebPage, Article);Article.publishercross-references the Organization by@id.
Changed
artisanpack-ui/aiconstraint bumped to^1.2. The SEO AI Livewire components now compose theChecksFeatureToggleandInteractsWithAiFeaturetraits (#76 / #92). Seedocs/upgrade-1.4.0.mdfor the downstream extension BC note.livewire/livewirerequire-dev + suggest constraint bumped to^3.8.3to pick up the CVE-2026-81887 patch.
Fixed
seo.robots.ai_crawlers.default_allow=falseis now a real kill switch — previously a silent no-op that emitted the samerobots.txtasdefault_allow=true.- RSS/Atom feeds strip XML 1.0 forbidden control characters and
drop entries whose
linkuses a non-http(s) scheme (log-and-drop so a single bad row can't kill the feed). - Atom
<id>readsseo.feeds.feed_idwhen set; when unset, falls back to the feed URL AND logs aLog::noticepointing at the config key. LlmsTxtGeneratorcollapses newlines in titles and escapes[/]in descriptions so a Markdown list item can no longer shatter mid-line.- IndexNow surfaces per-URL rejections carried in an otherwise-200
body (
code=UnverifiedHost,warnings[]) assuccess=falsewith awarningfield populated; logged response bodies capped viaStr::limit(..., 512). FocusKeywordAnalyzerusesmb_strtolower('UTF-8')across all seven placement checks so multibyte keywords no longer under-match.AiReadinessAnalyzer::countWords()usespreg_split('/\\s+/u')so Japanese and Cyrillic first paragraphs stop reporting zero words.- GD OG image renderer releases GD resources via
try/finallyaroundimagedestroyon the canvas + loaded PNG handles, preserves PNG alpha (no more black halos on transparent logos), and switches width measurement tomb_strlen; the bitmap fallback now logs a warning when asked to render non-ASCII text. GdOgImageRenderer::hexToRgb()falls back to white and logs a warning on malformed color input rather than silently producing black-on-black cards.LocalBusinessSchemadrops the flatopeningHourswhen a structuredopeningHoursSpecificationis emitted, and normalizesvalidFrom/validThroughthrough a strictY-m-dorDateTimeInterfacenormalizer.OrganizationSchema.sameAsfilters entries throughFILTER_VALIDATE_URL.
Security
squizlabs/php_codesnifferupdated to3.13.6(CVE-2026-67434, OS command injection).livewire/livewireupdated to3.8.7(CVE-2026-81887, DOM-based XSS).
Removed
- Inline
versionfield fromcomposer.json— git tags drive versioning; the field had drifted from tags in past releases.
CI
- PHPCS excludes Blade stubs; CI no longer swallows PHPCS errors
(
continue-on-error: trueremoved). - Test matrix varies Laravel version (11.x, 12.x, 13.x) alongside PHP.
[Unreleased-Beta History]
Entries below were accumulated pre-1.4.0 and are rolled into the 1.4.0 release above.
Added
AiReadinessAnalyzer(content category): New analyzer that scores content on the signals AI answer engines look for. Runs four checks: at least one question-style subheading (ends with?or opens withwhat/why/how/…), a definition-style intro sentence (matches "X is/are/refers to/means Y" near the start), an FAQ section (detected via a heading or a JSON-LDFAQPageschema block), and a summary-friendly opening paragraph within ~60 words. Registered automatically inSEOServiceProviderand gated byconfig('seo.analysis.analyzers.ai_readiness')(#71).- First-paragraph placement check on
FocusKeywordAnalyzer: The keyword analyzer now also verifies that the focus phrase appears in the opening paragraph (falls back to raw text when no<p>tag exists), on top of the existing title/description/URL/H1/subheadings/alt-text checks. Still no-ops when no focus phrase is set (#71). LocalBusinessSchemadated/holidayOpeningHoursSpecificationentries:buildOpeningHours()now accepts optionalvalidFrom/validThroughon each entry so holiday overrides and other date-scoped hours are marked up alongside recurringdayOfWeekentries. A truthyclosedflag emitsopensandclosesas"00:00"(per Google's guidance for all-day closures) and takes precedence over any explicitopens/closesvalues on the same entry. Consumed by the KeystoneModules/LocalSeoschema translator (#75).
Fixed
- Sitemap XML cache is now invalidated on content changes:
SitemapService::generate()/generateIndex()cached the rendered XML with a TTL (seo.sitemap.cache_ttl, default 3600s) but nothing ever forgot the cached entries when the underlying content changed —SitemapObserverkept thesitemap_entriestable current on every Page/Post save, yetGET /sitemap.xml(and thesitemap-*-N.xmlchildren) kept serving the pre-change snapshot until the TTL expired.SitemapObservernow callsSitemapService::clearCache()after a tracked model is saved, force deleted, or restored.SitemapService::clearCache()was also rewritten to bump a single generation counter that is baked into every cache key, so trailing pages for now-deleted content are invalidated as well (the previous per-page enumeration used the reduced page total and would leave a stalesitemap-{type}-N.xmlfor any page that no longer existed after a deletion).seo:generate-sitemapclears the cache before regenerating when writing to--outputso cached generation primes fresh entries; the statistics-only invocation (no--output) no longer touches the cache, since it would wipe a warm cache with no priming to follow (#67). - Sitemap index child URLs now include the page number:
SitemapIndexGeneratoromitted the page segment for page 1, so the index pointed atsitemap-{type}.xml, which no route serves (onlysitemap-{type}-{page}.xmlis registered) — every typed child sitemap returned a 404. The index now emitssitemap-{type}-1.xml, provider sitemaps assitemap-{provider}-1.xml, and the paginated main sitemap assitemap-1.xml(instead of a self-referentialsitemap.xml). Image, video, and news sitemap URLs are unchanged (#66). - Sitemap index now surfaces registered providers:
SitemapService::needsIndex()did not consult$this->providers, so a site with a single database type plus one or more registeredSitemapProviderContractproviders served the plain<urlset>main sitemap at/sitemap.xmland never exposed provider URLs to crawlers. The service now passes providers to the generator, andSitemapIndexGenerator::needsIndex()returnstruewhenever any provider is registered (#66).
Removed
- Laravel 10 support: Narrowed
illuminate/supportconstraint to^11.0|^12.0|^13.0andorchestra/testbenchto^9.0|^10.0|^11.0. Models in this package declare casts with the Laravel 11+casts()method, which Laravel 10'sHasAttributestrait does not call — soRedirect,SeoAnalysisCache,SeoMeta, andSitemapEntrywere silently returning raw column values (unparsed JSON, string datetimes, string enums) for anyone actually on Laravel 10. The advertised constraint was also unreachable in practice becauseartisanpack-ui/core: ^1.3already requiresilluminate/support: ^12.0|^13.0(#65).
Documentation
- Extensibility Hooks section in
README.md: Replaced the previous "Filter Hooks" section with a dedicated "Extensibility Hooks" section that documents the ecosystem hook naming convention (ap.prefix + camelCase segments joined by., with ✅/❌ examples), every filter and action hook the SEO package fires (ap.seo.metaTags,ap.seo.sitemapEntries,ap.seo.schemaGraph,ap.seo.schemaRendering,ap.seo.schemaRendered) with signatures and usage examples, and theapSeoAddSchema()helper with an example showing how a block'srender()method contributes schema entries (#57, #61).
[1.3.0] - 2026-07-21
Added
ap.seo.metaTagsfilter hook: Fired inMetaTagService::generate()after tags are assembled and before theMetaTagsDTOis built. Payload:(array $tags, ?Model $subject, Request $request). Callbacks can add, remove, or rewritetitle,description,canonical,robots, andadditionalMetabefore rendering (#62).ap.seo.sitemapEntriesfilter hook: Fired insideSitemapGenerator::generate()andSitemapGenerator::generateFromProvider()so integrators can append, drop, or reorder sitemap entries per type. Payload:(array $entries, string $sitemapType)(#62).
Changed
artisanpack-ui/hooks: ^1.3is now a required dependency (previously optional). ThePackageDetector::hasHooks()guard has been removed and the hook helper functions are always available.- Renamed VE subscriber hook:
VisualEditorIntegration::registerPrePublishChecks()now subscribes toap.visualEditor.prePublishChecks(matching the visual-editor Wave 3 rename) instead of the oldvisual_editor.pre_publish_checksname.
Removed
PackageDetector::hasHooks()— no longer needed now thatartisanpack-ui/hooksis required.
[1.2.0] - 2026-07-06
Added
- AI Feature Suite: Five AI agents built on top of
artisanpack-ui/aifor SEO workflows. Each ships with Livewire, React, and Vue trigger components plus a JSON API endpoint under/api/seo/ai/.MetaTitleSuggestionAgent— 3-5 CTR-optimized title variants ≤60 chars (#50)MetaDescriptionAgent— one 150-160 char meta description with keyword coverage (#51)ContentAnalysisAgent— structured quality score across four dimensions with actionable recommendations (#52)SchemaGenerationAgent— JSON-LD schema type suggestion + starter object, backed by the existing 14 supported schema types (#53)HreflangSuggestionAgent— cross-references hreflang relationships and surfaces gaps/inconsistencies (#54)
- Feature Registry Integration:
SEOServiceProvider::aiFeatures()auto-registers all five features with labels, default models, and descriptions. - AI Trigger Components:
<livewire:seo::ai-*>Livewire components +MetaTitleSuggestor/ContentAnalyzer/etc. React and Vue components with theuseAiAgenthook/composable.
Changed
artisanpack-ui/ai: ^1.0is now a suggested (optional) dependency, not required. The AI Feature Suite auto-activates whenartisanpack-ui/aiis installed and silently disables otherwise, so PHP 8.2 / Laravel 10 / Laravel 11 users keep the rest of the package. AI features themselves require PHP 8.3+ and Laravel 12+, since those areartisanpack-ui/ai's own requirements. AI Livewire components, AI API endpoints (/api/seo/ai/*), and the AI feature registry entries are only registered when the ai package is present.
[1.1.1] - 2026-06-09
Added
- Laravel 13 Support: Widened
illuminate/supportconstraint to^10.0|^11.0|^12.0|^13.0andorchestra/testbenchto include^11.0. Existing Laravel 10/11/12 users are unaffected; Laravel 13 is selectable on PHP 8.3+ via L13's own requirements (#47)
[1.1.0] - 2026-04-08
Added
- REST API Endpoints: Full API for SEO meta, analysis, redirects, and schema management under
/api/seo/(#29) - API Resources: Eloquent API Resources for SEO models and DTOs (
SeoMetaResource,AnalysisResultResource,RedirectResource,SchemaResource,MetaTagsResource,OpenGraphResource,TwitterCardResource,HreflangResource,SeoPreviewResource) (#30) - TypeScript Type Definitions: Publishable TypeScript types for all SEO API responses via
seo-typespublish tag (#31) - React SEO Admin Components: 12 React components (
SeoMetaEditor,BasicMetaTab,OpenGraphTab,TwitterCardTab,SchemaTab,HreflangTab,SitemapTab,MetaPreview,SocialPreview,SeoAnalysisPanel,RedirectManager,SeoDashboard) and 4 hooks (useApi,useSeoMeta,useSeoAnalysis,useRedirects) (#32) - Vue SEO Admin Components: 12 Vue components mirroring the React set, with 4 composables (
useApi,useSeoMeta,useSeoAnalysis,useRedirects) (#33) seo:install-frontendArtisan Command: Publishes React or Vue SEO components and TypeScript type definitions with--stackand--forceoptions (#34)- Schema Type Field Definitions API:
GET /api/seo/schema/typesnow returns rich metadata including descriptions and field definitions for each schema type, enabling dynamic form rendering in React/Vue editors (#35) - Publishable Asset Scaffolding: Publish tags
seo-react,seo-vue, andseo-typesfor frontend component distribution - GitHub Actions CI/CD: Lint job (PHP-CS-Fixer + PHPCS), test matrix (PHP 8.2, 8.3, 8.4), and separate release workflow with Packagist notification
- Comprehensive Documentation: Frontend scaffolding guide, schema type definitions API docs, v1.1.0 upgrade guide, and updated existing docs
Fixed
- AnalysisService zero scores: Built-in analyzers are now properly registered with the
AnalysisServicein the service provider (#40) - SeoDashboard request guard: Added null check for request in
fetchDashboardDatato prevent errors - AnalyzerName typo: Corrected
internal_linktointernal_linksin TypeScript type definitions - Schema numeric coercion: Deferred numeric coercion in schema fields until input is complete to prevent premature type conversion
Changed
- Breaking:
SchemaTypeContractinterface gained two new methods —getDescription(): stringandgetFieldDefinitions(): arraywere added in 1.1.0. External classes that directly implementSchemaTypeContractmust add these methods. Classes that extendAbstractSchema(the recommended approach) are unaffected, asAbstractSchemaprovides default implementations returning an empty string and empty array respectively. To migrate, either implement both methods in your custom class, or switch to extendingAbstractSchemainstead of implementingSchemaTypeContractdirectly. - CI migrated from GitLab to GitHub Actions: Replaced GitLab CI/CD with GitHub Actions workflows
[1.0.0] - 2026-01-23
First Stable Release
This is the first stable release of ArtisanPack UI SEO, a comprehensive SEO management package for Laravel applications built on Livewire 3.
Highlights
- Complete SEO Management: Meta tags, Open Graph, Twitter Cards, and Schema.org markup
- 14 Schema Types: Built-in JSON-LD structured data for rich search results
- URL Redirect System: Exact, regex, and wildcard redirects with hit tracking
- XML Sitemap Generation: Standard, image, video, and news sitemaps
- SEO Content Analysis: 8 built-in analyzers for content quality scoring
- Laravel 10, 11, and 12 Support: Compatible with current Laravel versions (
illuminate/support ^10.0|^11.0|^12.0) - Livewire 3.6+ Compatible (Optional): Livewire components (SeoMetaEditor, RedirectManager, etc.) require Livewire 3.6+; Blade components work without Livewire
- Comprehensive Documentation: Detailed guides and API reference
Added
Core Features
- HasSeo Trait: Polymorphic SEO metadata attachment for any Eloquent model
- Automatic Fallbacks: Smart fallback system using model properties for SEO values
- SeoMeta Model: Store and manage SEO metadata with full field support
- Feature Toggles: Enable/disable individual features via configuration
Meta Tags
- Title Management: Custom titles with site name and separator
- Description Management: Custom descriptions with automatic truncation
- Robots Directives: Noindex, nofollow, and custom robots meta tags
- Canonical URLs: Set canonical URLs to prevent duplicate content issues
- Viewport Meta: Configurable viewport settings
Social Media
- Open Graph Tags: Full Open Graph protocol support for Facebook/LinkedIn
- Twitter Cards: Summary and summary_large_image card types
- Image Support: Automatic image URL generation with fallbacks
- Custom Social Titles: Separate titles/descriptions for social platforms
Schema.org / JSON-LD
- 14 Built-in Schema Types: Article, BlogPosting, Product, Organization, Person, LocalBusiness, Event, Recipe, FAQPage, HowTo, BreadcrumbList, WebSite, WebPage, VideoObject
- SchemaBuilderInterface: Contract for creating custom schema builders
- Automatic Schema Generation: Generate schema from model data
- Nested Schema Support: Support for complex nested schema structures
Multi-Language (Hreflang)
- Hreflang Tags: Generate hreflang link tags for multi-language sites
- HreflangService: Service for managing hreflang configurations
- x-default Support: Support for default language fallback
URL Redirects
- Redirect Model: Store redirect rules with source, destination, and type
- Match Types: Exact match, regex patterns, and wildcard matching
- Status Codes: Support for 301, 302, 307, and 308 redirects
- Hit Tracking: Track redirect usage with hit counts and timestamps
- Loop Prevention: Automatic detection and prevention of redirect loops
- HandleRedirects Middleware: Automatic redirect handling via middleware
XML Sitemaps
- Standard Sitemaps: Generate XML sitemaps with URLs, priorities, and frequencies
- Image Sitemaps: Include image URLs in sitemaps for image search
- Video Sitemaps: Include video metadata in sitemaps
- News Sitemaps: Generate Google News-compatible sitemaps
- Sitemap Index: Automatic sitemap index generation for large sites
- SitemapProviderInterface: Contract for custom sitemap entry providers
- Cache Support: Cache generated sitemaps for performance
Robots.txt
- Dynamic Generation: Generate robots.txt dynamically from configuration
- Global Rules: Set default allow/disallow rules for all bots
- Bot-Specific Rules: Configure rules for specific user agents
- Sitemap Inclusion: Automatically include sitemap URLs in robots.txt
- RobotsService: Service for managing robots.txt configuration
SEO Analysis
- 8 Built-in Analyzers: Title, description, headings, keyword density, readability, images, links, and meta robots
- AnalyzerInterface: Contract for creating custom analyzers
- Quality Scoring: Score content quality with good/warning/error status
- Suggestions: Actionable suggestions for improving SEO
- Caching: Cache analysis results for performance
Caching
- Meta Tag Caching: Cache generated meta tags per model
- Sitemap Caching: Cache generated sitemaps
- Redirect Caching: Cache redirect lookups for performance
- Analysis Caching: Cache SEO analysis results
- CacheService: Centralized cache management service
- Configurable TTL: Set cache duration per feature
Blade Components
- x-seo-meta: All-in-one component rendering meta, OG, Twitter, and schema
- x-seo-meta-tags: Basic meta tags component
- x-seo-open-graph: Open Graph tags component
- x-seo-twitter-card: Twitter Card tags component
- x-seo-schema: Schema.org JSON-LD component
- x-seo-hreflang: Hreflang link tags component
Livewire Components
- SeoMetaEditor: Full SEO editing interface with tabbed sections
- RedirectManager: URL redirect management with CRUD operations
- SeoDashboard: Overview dashboard with SEO statistics
- SeoAnalysisPanel: Content analysis results display
- HreflangEditor: Multi-language URL editor
- MetaPreview: Search result preview component
- SocialPreview: Social share preview component
Services
- SeoService: Main orchestrator for all SEO operations
- MetaTagService: Generate and manage meta tags
- SocialMetaService: Generate Open Graph and Twitter Card tags
- SchemaService: Generate Schema.org JSON-LD markup
- SitemapService: Generate XML sitemaps
- RedirectService: Manage URL redirects
- RobotsService: Generate robots.txt content
- HreflangService: Manage hreflang configurations
- AnalysisService: Run SEO content analysis
- CacheService: Centralized cache management
Events
- SeoMetaCreated: Dispatched when SEO meta is created
- SeoMetaUpdated: Dispatched when SEO meta is updated
- SitemapGenerated: Dispatched after sitemap generation
- RedirectHit: Dispatched when a redirect is triggered
Helper Functions
- seo(): Get the SeoService instance
- seoMeta(): Get SeoMeta for a model
- seoTitle(): Format page title with site name
- seoDescription(): Truncate description to SEO length
- seoIsEnabled(): Check if a feature is enabled
- seoConfig(): Get configuration value
Artisan Commands
- seo:generate-sitemap: Generate XML sitemap files
- seo:submit-sitemap: Submit sitemap to search engines
- seo:clear-cache: Clear all SEO caches
Facades
- Seo: Facade for SeoService
- Redirect: Facade for RedirectService
Contracts/Interfaces
- SeoableInterface: Contract for SEO-enabled models
- AnalyzerInterface: Contract for SEO analyzers
- SchemaBuilderInterface: Contract for schema builders
- SitemapProviderInterface: Contract for sitemap entry providers
Exceptions
- RedirectLoopException: Thrown when redirect creates a loop
- SitemapGenerationException: Thrown when sitemap generation fails
- InvalidSchemaTypeException: Thrown when schema type is invalid
- InvalidConfigurationException: Thrown when configuration is invalid
Extensibility
- Filter Hooks: WordPress-style hooks via artisanpack-ui/hooks
- Custom Schema Types: Register custom schema builders
- Custom Analyzers: Register custom SEO analyzers
- Custom Sitemap Providers: Register custom sitemap entry providers
- Publishable Views: Customize all Blade views
- Publishable Config: Full configuration customization
Database
- seo_meta Migration: Polymorphic SEO metadata storage
- redirects Migration: URL redirect rules storage
- sitemap_entries Migration: Sitemap entry tracking
- seo_analysis_cache Migration: Cached analysis results
Infrastructure
- Code Style: PHP-CS-Fixer and PHPCS with ArtisanPackUI standards
- GitLab CI/CD: Multi-PHP version testing (8.2, 8.3, 8.4)
- Documentation: Comprehensive markdown documentation
- WordPress-Style Documentation: Full PHPDoc blocks on all classes and methods

