npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

medusa-seo-suite

v0.0.2

Published

A starter for Medusa plugins.

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-suite

2) 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:migrate

This 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-audit workflow, 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:

  • id

  • created_at (ISO)

  • audit_summary (seo_score, performance_score, store_url)

  • keywords_count

  • Description: Lists persisted runs for history browsing.

GET /admin/seo-dashboard/runs/:id

  • Auth requirement: Admin JWT
  • Path params: id run 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 404 if not found.

Health-check endpoints

  • GET /admin/plugin -> returns 200
  • GET /store/plugin -> returns 200

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.
  • getLatestRun(): Promise<{ id; payload; created_at } | null>
    • Fetches most recent persisted run.
  • listRuns(options?: { take?: number; skip?: number })
    • Lists persisted runs in descending created_at order.

Composed sub-services:

  • pageSpeedService (PageSpeedService)
  • sitemapService (SitemapService)
  • metaService (MetaService)
  • rankingService (RankingService)

PageSpeedService

  • Method: audit(): Promise<PageSpeedResult>
  • Calls PageSpeed Insights API and returns:
    • seo_score
    • performance_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-step
    • compose-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:
    • pagespeed
    • sitemap
    • meta
    • ranking

compose-audit-result-step

  • Maps raw step results into persisted DTO shape:
    • audit: SeoAuditReportDTO
    • keywords: SeoRankingDTO[]

Additional defined step files

These steps are defined and exported but not used by run-seo-audit currently:

  • pagespeed-step
  • sitemap-step
  • meta-step
  • ranking-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:
    1. Runs run-seo-audit workflow.
    2. Persists run via SeoDashboardModuleService.createRun.
    3. Logs success/failure.

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

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

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

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_at
  • updated_at
  • deleted_at

Relationships:

  • No explicit foreign-key relationships to Medusa core entities.
  • payload stores snapshot audit and keyword arrays as JSON.

Use Cases & Examples

  1. Daily SEO health monitoring

    • Configure cron (SEO_CRON_HOUR / SEO_CRON_MINUTE) and review latest snapshot from /admin/seo-dashboard.
  2. Manual pre-release SEO checks

    • Trigger POST /admin/seo-dashboard/run before storefront launches or large content changes.
  3. Historical trend inspection

    • Use /admin/seo-dashboard/runs and /admin/seo-dashboard/runs/:id to compare past runs.
  4. Technical SEO spot checks

    • Use stored metadata/sitemap fields to quickly detect missing title/description/canonical or invalid sitemap.
  5. 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/run or wait for scheduled job.

PageSpeed score is null

  • Cause: PageSpeed API failure/timeout or invalid API key.
  • Fix: Verify pageSpeedApiKey, network access, and target storeUrl.

Sitemap invalid or empty

  • Cause: Unreachable sitemap URL or XML without <url>/<sitemap> entries.
  • Fix: Correct sitemapUrl or 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, gscSiteUrl format, private key formatting (\\n handling), and Search Console property permissions.

Cron runs at wrong time

  • Cause: SEO_CRON_HOUR / SEO_CRON_MINUTE parsing or timezone expectations.
  • Fix: Set explicit env values and confirm server timezone; note 24 is normalized to 0 (midnight).

GET /admin/seo-dashboard/runs/:id returns 404

  • Cause: Unknown run id or deleted row.
  • Fix: Use an id from /admin/seo-dashboard/runs response.