medusa-seo-suite
v0.0.2
Published
A starter for Medusa plugins.
Maintainers
Readme
medusa-seo-suite
Medusa v2 plugin for SEO auditing (PageSpeed, sitemap, metadata, and Search Console keyword data) with persisted run history and admin dashboard UI.
Plugin Overview
medusa-seo-suite provides an SEO audit system for a Medusa storefront:
- Runs combined SEO audits (PageSpeed SEO/performance, sitemap validity, homepage meta tags, Search Console keyword rankings).
- Persists each audit run as a snapshot (
seo_audit_run) so dashboard data can be loaded instantly from stored history. - Exposes admin APIs to fetch latest audit, trigger a new run, list runs, and inspect a specific run.
- Includes admin routes for a dashboard and audit history views.
- Supports scheduled daily audits via a cron job.
It is built for Medusa v2 (uses @medusajs/framework, Medusa modules, workflows SDK, and admin-sdk route extensions).
Installation & Setup
1) Install package
npm install medusa-seo-suite
# or
yarn add medusa-seo-suite2) Register in medusa-config.ts
import { defineConfig } from "@medusajs/framework/utils"
export default defineConfig({
modules: {
seo_dashboard: {
resolve: "medusa-seo-suite",
options: {
gscClientEmail: "[email protected]",
gscPrivateKey: "-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n",
gscSiteUrl: "sc-domain:example.com",
storeUrl: "https://store.example.com",
pageSpeedApiKey: "your_pagespeed_api_key",
sitemapUrl: "https://store.example.com/sitemap.xml",
},
},
},
plugins: [
{
resolve: "medusa-seo-suite",
},
],
})3) Run migrations
npx medusa db:migrateThis plugin migration creates the seo_audit_run table used for persisted snapshots.
Configuration (config.ts / plugin options)
Configuration is defined in src/config.ts.
Plugin options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| gscClientEmail | string | Yes | — | Google Search Console service account email. |
| gscPrivateKey | string | Yes | — | Google Search Console service account private key. |
| gscSiteUrl | string | Yes | — | Search Console property URL (sc-domain:example.com or URL-prefix property). |
| storeUrl | string | Yes | — | Storefront URL audited for PageSpeed and metadata checks. |
| pageSpeedApiKey | string | Yes | — | API key for Google PageSpeed Insights endpoint. |
| sitemapUrl | string | No | ${storeUrl}/sitemap.xml | Sitemap URL to validate. |
Cron config (resolved from env)
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| seoCronConfig.hour | number | No | 0 | Hour in 0..24 (24 treated as midnight). |
| seoCronConfig.minute | number | No | 0 | Minute in 0..59. |
Complete example config block
{
modules: {
seo_dashboard: {
resolve: "medusa-seo-suite",
options: {
gscClientEmail: "[email protected]",
gscPrivateKey: "-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n",
gscSiteUrl: "https://store.example.com/",
storeUrl: "https://store.example.com",
pageSpeedApiKey: "AIza...",
sitemapUrl: "https://store.example.com/sitemap.xml",
},
},
},
}Environment Variables
The plugin reads two environment variables in src/config.ts.
| Variable | Required | Default | Purpose | Example |
|---|---|---|---|---|
| SEO_CRON_HOUR | No | 0 | Daily audit hour (0..24, where 24 becomes 0). | SEO_CRON_HOUR=2 |
| SEO_CRON_MINUTE | No | 0 | Daily audit minute (0..59). | SEO_CRON_MINUTE=30 |
REST APIs / Routes
All functional endpoints in this plugin are admin-scoped.
Admin routes
GET /admin/seo-dashboard
- Auth requirement: Admin JWT (admin namespace)
- Query params: none
- Response schema:
| Field | Type |
|---|---|
| latest_audit | SeoAuditReportDTO \| null |
| keywords | SeoRankingDTO[] |
| last_run_at | Date \| null |
| next_cron_run_at | string \| null (ISO) |
- Description: Returns latest persisted run snapshot and next scheduled cron run time.
POST /admin/seo-dashboard/run
- Auth requirement: Admin JWT
- Body: none
- Query params: none
- Response schema:
| Field | Type |
|---|---|
| latest_audit | SeoAuditReportDTO \| null |
| keywords | SeoRankingDTO[] |
| last_run_at | Date \| null |
| next_cron_run_at | string \| null (ISO) |
- Description: Executes
run-seo-auditworkflow, persists a new run, and returns the fresh dashboard payload.
GET /admin/seo-dashboard/runs
- Auth requirement: Admin JWT
- Query params:
| Param | Type | Required | Default |
|---|---|---|---|
| limit | string parseInt | No | 50 (capped to 100) |
| offset | string parseInt | No | 0 |
- Response schema:
| Field | Type |
|---|---|
| runs | SeoAuditRunListItem[] |
SeoAuditRunListItem includes:
idcreated_at(ISO)audit_summary(seo_score,performance_score,store_url)keywords_countDescription: Lists persisted runs for history browsing.
GET /admin/seo-dashboard/runs/:id
- Auth requirement: Admin JWT
- Path params:
idrun id - Query params: none
- Response schema:
| Field | Type |
|---|---|
| id | string |
| created_at | string (ISO) |
| latest_audit | SeoAuditReportDTO \| null |
| keywords | SeoRankingDTO[] |
| last_run_at | Date \| null |
| next_cron_run_at | string (ISO) |
- Description: Returns one run snapshot by id; returns
404if not found.
Health-check endpoints
GET /admin/plugin-> returns200GET /store/plugin-> returns200
Important API examples
# Get dashboard snapshot
curl -X GET "http://localhost:9000/admin/seo-dashboard" \
-H "Authorization: Bearer <ADMIN_JWT>"# Run SEO audit now
curl -X POST "http://localhost:9000/admin/seo-dashboard/run" \
-H "Authorization: Bearer <ADMIN_JWT>"# List audit history
curl -X GET "http://localhost:9000/admin/seo-dashboard/runs?limit=20&offset=0" \
-H "Authorization: Bearer <ADMIN_JWT>"# Get one audit run
curl -X GET "http://localhost:9000/admin/seo-dashboard/runs/seaudr_123" \
-H "Authorization: Bearer <ADMIN_JWT>"Services
SeoDashboardModuleService
Manages service composition and run persistence.
Key methods:
createRun(payload: ComposeAuditResultOutput): Promise<{ id: string }>- Persists composed audit+keyword payload in
seo_audit_run.
- Persists composed audit+keyword payload in
getLatestRun(): Promise<{ id; payload; created_at } | null>- Fetches most recent persisted run.
listRuns(options?: { take?: number; skip?: number })- Lists persisted runs in descending
created_atorder.
- Lists persisted runs in descending
Composed sub-services:
pageSpeedService(PageSpeedService)sitemapService(SitemapService)metaService(MetaService)rankingService(RankingService)
PageSpeedService
- Method:
audit(): Promise<PageSpeedResult> - Calls PageSpeed Insights API and returns:
seo_scoreperformance_score- optional
error
SitemapService
- Method:
validate(): Promise<SitemapResult> - Fetches sitemap URL and checks for
<url>or<sitemap>entries.
MetaService
- Method:
check(): Promise<MetaResult> - Fetches storefront HTML and extracts:
<title><meta name="description">- canonical
<link rel="canonical">
RankingService
- Method:
fetchTopKeywords(limit = 10): Promise<RankingResult> - Uses Google Search Console API (
googleapis) to query top query/page rows with clicks, impressions, CTR, and position.
Workflows & Steps (Medusa v2)
Workflow: run-seo-audit
- File:
src/workflows/run-seo-audit-workflow.ts - Inputs: none (
{}) - Steps:
fetch-seo-data-stepcompose-audit-result-step
- Output:
ComposeAuditResultOutput({ audit, keywords })
Steps
fetch-seo-data-step
- Runs parallel calls to PageSpeed, sitemap validation, meta check, and rankings.
- Adds step-level timeout fallbacks:
- PageSpeed: 35s
- Sitemap: 10s
- Meta: 10s
- Ranking: 20s
- Output:
pagespeedsitemapmetaranking
compose-audit-result-step
- Maps raw step results into persisted DTO shape:
audit: SeoAuditReportDTOkeywords: SeoRankingDTO[]
Additional defined step files
These steps are defined and exported but not used by run-seo-audit currently:
pagespeed-stepsitemap-stepmeta-stepranking-step
⚠️ Note: If you intend to execute those standalone steps, wire them into a workflow or remove dead paths to avoid maintenance drift.
Subscribers / Event Hooks
No subscribers/event hooks are implemented in this plugin (src/subscribers contains README only).
Jobs
seo-audit-job
- File:
src/jobs/seo-audit-job.ts - Schedule: derived from
getSeoAuditCronSchedule(seoCronConfig)->minute hour * * * - Behavior:
- Runs
run-seo-auditworkflow. - Persists run via
SeoDashboardModuleService.createRun. - Logs success/failure.
- Runs
Admin UI / Widgets
This plugin ships admin route extensions (not widget zones).
Admin Route: seo-dashboard
- File:
src/admin/routes/seo-dashboard/page.tsx - Route config label/icon:
SEO Dashboard+ChartBar - Renders:
- score cards (SEO, performance, sitemap, keywords)
- metadata/technical checks
- keyword ranking table
- next scheduled update display
- Interactions:
- “Run audit now” calls
POST /admin/seo-dashboard/run - navigation to history pages
- “Run audit now” calls
Admin Route: seo-audit-history
- File:
src/admin/routes/seo-audit-history/page.tsx - Renders:
- table of recent runs (
/admin/seo-dashboard/runs) - per-run “View” navigation
- table of recent runs (
Admin Route: seo-audit-history/:id
- File:
src/admin/routes/seo-audit-history/[id]/page.tsx - Renders:
- full snapshot for selected run (
/admin/seo-dashboard/runs/:id) - metadata, score cards, keyword details
- full snapshot for selected run (
Shared Admin Components
src/admin/components/seo/primitives.tsx:DashBadge- tone mapping helpers (
scoreTone,metaBadgeTone) - helper status derivations (
ogTwitterStatus,sitemapDisplayUrl)
Models & Entities
seo_audit_run
Defined in src/modules/seo-dashboard/models/seo-audit-run.ts.
| Field | Type | Nullable |
|---|---|---|
| id | id | No |
| payload | json | No |
Migration includes standard timestamp fields:
created_atupdated_atdeleted_at
Relationships:
- No explicit foreign-key relationships to Medusa core entities.
payloadstores snapshot audit and keyword arrays as JSON.
Use Cases & Examples
Daily SEO health monitoring
- Configure cron (
SEO_CRON_HOUR/SEO_CRON_MINUTE) and review latest snapshot from/admin/seo-dashboard.
- Configure cron (
Manual pre-release SEO checks
- Trigger
POST /admin/seo-dashboard/runbefore storefront launches or large content changes.
- Trigger
Historical trend inspection
- Use
/admin/seo-dashboard/runsand/admin/seo-dashboard/runs/:idto compare past runs.
- Use
Technical SEO spot checks
- Use stored metadata/sitemap fields to quickly detect missing title/description/canonical or invalid sitemap.
Search Console keyword visibility snapshots
- Persist top keyword rows per run and inspect changes through admin history pages.
Troubleshooting
[medusa-seo-suite] <option> is required at startup
- Cause: Missing required plugin options (
gscClientEmail,gscPrivateKey,gscSiteUrl,storeUrl,pageSpeedApiKey). - Fix: Provide all required options in
medusa-config.ts.
Dashboard returns empty audit (latest_audit: null)
- Cause: No run has been persisted yet.
- Fix: Trigger
POST /admin/seo-dashboard/runor wait for scheduled job.
PageSpeed score is null
- Cause: PageSpeed API failure/timeout or invalid API key.
- Fix: Verify
pageSpeedApiKey, network access, and targetstoreUrl.
Sitemap invalid or empty
- Cause: Unreachable sitemap URL or XML without
<url>/<sitemap>entries. - Fix: Correct
sitemapUrlor ensure sitemap endpoint is publicly accessible and populated.
Ranking keywords empty
- Cause: GSC auth/site property mismatch or API errors.
- Fix: Verify service account access,
gscSiteUrlformat, private key formatting (\\nhandling), and Search Console property permissions.
Cron runs at wrong time
- Cause:
SEO_CRON_HOUR/SEO_CRON_MINUTEparsing or timezone expectations. - Fix: Set explicit env values and confirm server timezone; note
24is normalized to0(midnight).
GET /admin/seo-dashboard/runs/:id returns 404
- Cause: Unknown run id or deleted row.
- Fix: Use an id from
/admin/seo-dashboard/runsresponse.
