{"_id":"@dailyautomations/seo-platform","_rev":"4-ae939abf5e37948590304a70c2eacf02","name":"@dailyautomations/seo-platform","dist-tags":{"latest":"0.1.3"},"versions":{"0.1.0":{"name":"@dailyautomations/seo-platform","version":"0.1.0","license":"UNLICENSED","_id":"@dailyautomations/seo-platform@0.1.0","maintainers":[{"name":"dailyautomations","email":"michael@dailyautomations.com"}],"bin":{"seo-platform-lint":"dist/bin/seo-platform-lint.js"},"dist":{"shasum":"c3c617be6853e4d4ef59239fdda5b5847f76519f","tarball":"https://registry.npmjs.org/@dailyautomations/seo-platform/-/seo-platform-0.1.0.tgz","fileCount":48,"integrity":"sha512-sDYUp8Fv44HbPLy8GOXWhcohWoLCijir/3Mynt4ByAOGk85YLkL53Vr9q36JgX3Cu8HAgMpKx4D6P60KPBQ4dA==","signatures":[{"sig":"MEYCIQCh9AXZrlXJ6Bzjn6iIlZjPWKqiySnQCX1sNKdoY47a2gIhALhchcaLni20oPQBSZyWWw+1x+M5Zp3rPJ/n3EB23JPo","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":204765},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./edge":{"types":"./dist/edge.d.ts","import":"./dist/edge.js"},"./slugs":{"types":"./dist/slugs/index.d.ts","import":"./dist/slugs/index.js"},"./headers":{"types":"./dist/headers/index.d.ts","import":"./dist/headers/index.js","require":"./dist/headers/index.cjs"},"./sitemap":{"types":"./dist/sitemap/index.d.ts","import":"./dist/sitemap/index.js","require":"./dist/sitemap/index.cjs"},"./canonical":{"types":"./dist/canonical/index.d.ts","import":"./dist/canonical/index.js"},"./redirects":{"types":"./dist/redirects/index.d.ts","import":"./dist/redirects/index.js","require":"./dist/redirects/index.cjs"},"./slug-rename":{"types":"./dist/slug-rename/index.d.ts","import":"./dist/slug-rename/index.js","require":"./dist/slug-rename/index.cjs"},"./snapshot/edge":{"types":"./dist/cache/snapshot-edge.d.ts","import":"./dist/cache/snapshot-edge.js"},"./snapshot/node":{"types":"./dist/cache/snapshot-node.d.ts","import":"./dist/cache/snapshot-node.js","require":"./dist/cache/snapshot-node.cjs"},"./redirects/edge":{"types":"./dist/redirects/middleware.d.ts","import":"./dist/redirects/middleware.js"},"./trailing-slash":{"types":"./dist/trailing-slash/index.d.ts","import":"./dist/trailing-slash/index.js"}},"gitHead":"cc859289fbd92f33afcf2f7f12d9512918073930","private":false,"scripts":{"test":"vitest run","build":"tsup","prebuild":"rm -rf dist","lint:edge":"node scripts/edge-lint.mjs","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build && npm run lint:edge"},"_npmUser":{"name":"dailyautomations","email":"michael@dailyautomations.com"},"_npmVersion":"11.9.0","description":"Shared SEO primitives for Daily Automations sites: sitemap, redirects, canonical, trailing-slash, slugify, security headers. Conforms to the locked /api/v1/seo/* v1 wire contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"zod":"^3.23.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^1.6.0","typescript":"^5.5.0"},"peerDependencies":{"next":">=14.0.0 <17.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/seo-platform_0.1.0_1779285082051_0.9433893699898073","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dailyautomations/seo-platform","version":"0.1.1","license":"UNLICENSED","_id":"@dailyautomations/seo-platform@0.1.1","maintainers":[{"name":"dailyautomations","email":"michael@dailyautomations.com"}],"bin":{"seo-platform-lint":"dist/bin/seo-platform-lint.js"},"dist":{"shasum":"4264685d46a74dc3cd62d089024865473000a101","tarball":"https://registry.npmjs.org/@dailyautomations/seo-platform/-/seo-platform-0.1.1.tgz","fileCount":48,"integrity":"sha512-6pSWdmuaS9nFZ1/bnzr9ds204aWrO6223TlQ8yaviqDe77ZDtwXnb5bR4HwBQ+zIEJc7VysxtbqNMUn6R/DSYw==","signatures":[{"sig":"MEUCIQDEE3LN9GN7xkuUImRzs6rsgiXLASiOBWFvwqCwXUGXggIgUz0uul6MxbN8+Ulw+2Sd2u1f8AK3ZeB6WTxuDqcDpYE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":205142},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./edge":{"types":"./dist/edge.d.ts","import":"./dist/edge.js"},"./slugs":{"types":"./dist/slugs/index.d.ts","import":"./dist/slugs/index.js"},"./headers":{"types":"./dist/headers/index.d.ts","import":"./dist/headers/index.js","require":"./dist/headers/index.cjs"},"./sitemap":{"types":"./dist/sitemap/index.d.ts","import":"./dist/sitemap/index.js","require":"./dist/sitemap/index.cjs"},"./canonical":{"types":"./dist/canonical/index.d.ts","import":"./dist/canonical/index.js"},"./redirects":{"types":"./dist/redirects/index.d.ts","import":"./dist/redirects/index.js","require":"./dist/redirects/index.cjs"},"./slug-rename":{"types":"./dist/slug-rename/index.d.ts","import":"./dist/slug-rename/index.js","require":"./dist/slug-rename/index.cjs"},"./snapshot/edge":{"types":"./dist/cache/snapshot-edge.d.ts","import":"./dist/cache/snapshot-edge.js"},"./snapshot/node":{"types":"./dist/cache/snapshot-node.d.ts","import":"./dist/cache/snapshot-node.js","require":"./dist/cache/snapshot-node.cjs"},"./redirects/edge":{"types":"./dist/redirects/middleware.d.ts","import":"./dist/redirects/middleware.js"},"./trailing-slash":{"types":"./dist/trailing-slash/index.d.ts","import":"./dist/trailing-slash/index.js"}},"gitHead":"cc859289fbd92f33afcf2f7f12d9512918073930","private":false,"scripts":{"test":"vitest run","build":"tsup","prebuild":"rm -rf dist","lint:edge":"node scripts/edge-lint.mjs","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build && npm run lint:edge"},"_npmUser":{"name":"dailyautomations","email":"michael@dailyautomations.com"},"_npmVersion":"11.9.0","description":"Shared SEO primitives for Daily Automations sites: sitemap, redirects, canonical, trailing-slash, slugify, security headers. Conforms to the locked /api/v1/seo/* v1 wire contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"zod":"^3.23.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^1.6.0","typescript":"^5.5.0"},"peerDependencies":{"next":">=14.0.0 <17.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/seo-platform_0.1.1_1779285248969_0.49241126826598713","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@dailyautomations/seo-platform","version":"0.1.2","license":"UNLICENSED","_id":"@dailyautomations/seo-platform@0.1.2","maintainers":[{"name":"dailyautomations","email":"michael@dailyautomations.com"}],"bin":{"seo-platform-lint":"dist/bin/seo-platform-lint.js"},"dist":{"shasum":"342f3babe49a7ecb7d177256f2c8b1b935120195","tarball":"https://registry.npmjs.org/@dailyautomations/seo-platform/-/seo-platform-0.1.2.tgz","fileCount":48,"integrity":"sha512-XQB9+M0BUJHnuV0PCb7AK8M5UlbRZXtsOOYo9pgBdIajnaec7gJSWD1gWBZQH+zaUpp4Flb7cHlOA0Pyznvu9g==","signatures":[{"sig":"MEUCIQDFlMaRvnOeVIGSKKZ3AxC2NXnV4n3zZjUIfy9vVMH+RQIgMF9UmZM4VT0z9z7zRfHUgXfqMbwIp22WeHPfV1vB56M=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":211916},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.17"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./edge":{"types":"./dist/edge.d.ts","import":"./dist/edge.js"},"./slugs":{"types":"./dist/slugs/index.d.ts","import":"./dist/slugs/index.js"},"./headers":{"types":"./dist/headers/index.d.ts","import":"./dist/headers/index.js","require":"./dist/headers/index.cjs"},"./sitemap":{"types":"./dist/sitemap/index.d.ts","import":"./dist/sitemap/index.js","require":"./dist/sitemap/index.cjs"},"./canonical":{"types":"./dist/canonical/index.d.ts","import":"./dist/canonical/index.js"},"./redirects":{"types":"./dist/redirects/index.d.ts","import":"./dist/redirects/index.js","require":"./dist/redirects/index.cjs"},"./slug-rename":{"types":"./dist/slug-rename/index.d.ts","import":"./dist/slug-rename/index.js","require":"./dist/slug-rename/index.cjs"},"./snapshot/edge":{"types":"./dist/cache/snapshot-edge.d.ts","import":"./dist/cache/snapshot-edge.js"},"./snapshot/node":{"types":"./dist/cache/snapshot-node.d.ts","import":"./dist/cache/snapshot-node.js","require":"./dist/cache/snapshot-node.cjs"},"./redirects/edge":{"types":"./dist/redirects/middleware.d.ts","import":"./dist/redirects/middleware.js"},"./trailing-slash":{"types":"./dist/trailing-slash/index.d.ts","import":"./dist/trailing-slash/index.js"}},"gitHead":"125ca191f71d5b1e21ad8fd71830c4c7796a7013","private":false,"scripts":{"test":"vitest run","build":"tsup","prebuild":"rm -rf dist","lint:edge":"node scripts/edge-lint.mjs","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build && npm run lint:edge"},"_npmUser":{"name":"dailyautomations","email":"michael@dailyautomations.com"},"_npmVersion":"11.9.0","description":"Shared SEO primitives for Daily Automations sites: sitemap, redirects, canonical, trailing-slash, slugify, security headers. Conforms to the locked /api/v1/seo/* v1 wire contract.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"zod":"^3.23.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^1.6.0","typescript":"^5.5.0"},"peerDependencies":{"next":">=14.0.0 <17.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/seo-platform_0.1.2_1779337638999_0.5613221862891244","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@dailyautomations/seo-platform","version":"0.1.3","description":"Shared SEO primitives for Daily Automations sites: sitemap, redirects, canonical, trailing-slash, slugify, security headers. Conforms to the locked /api/v1/seo/* v1 wire contract.","type":"module","license":"UNLICENSED","private":false,"publishConfig":{"access":"public"},"engines":{"node":">=18.17"},"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./edge":{"types":"./dist/edge.d.ts","import":"./dist/edge.js"},"./sitemap":{"types":"./dist/sitemap/index.d.ts","import":"./dist/sitemap/index.js","require":"./dist/sitemap/index.cjs"},"./redirects":{"types":"./dist/redirects/index.d.ts","import":"./dist/redirects/index.js","require":"./dist/redirects/index.cjs"},"./redirects/edge":{"types":"./dist/redirects/middleware.d.ts","import":"./dist/redirects/middleware.js"},"./headers":{"types":"./dist/headers/index.d.ts","import":"./dist/headers/index.js","require":"./dist/headers/index.cjs"},"./trailing-slash":{"types":"./dist/trailing-slash/index.d.ts","import":"./dist/trailing-slash/index.js"},"./canonical":{"types":"./dist/canonical/index.d.ts","import":"./dist/canonical/index.js"},"./slugs":{"types":"./dist/slugs/index.d.ts","import":"./dist/slugs/index.js"},"./slug-rename":{"types":"./dist/slug-rename/index.d.ts","import":"./dist/slug-rename/index.js","require":"./dist/slug-rename/index.cjs"},"./snapshot/node":{"types":"./dist/cache/snapshot-node.d.ts","import":"./dist/cache/snapshot-node.js","require":"./dist/cache/snapshot-node.cjs"},"./snapshot/edge":{"types":"./dist/cache/snapshot-edge.d.ts","import":"./dist/cache/snapshot-edge.js"}},"bin":{"seo-platform-lint":"dist/bin/seo-platform-lint.js"},"sideEffects":false,"scripts":{"prebuild":"rm -rf dist","build":"tsup","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","lint:edge":"node scripts/edge-lint.mjs","prepublishOnly":"npm run build && npm run lint:edge"},"dependencies":{"zod":"^3.23.0"},"peerDependencies":{"next":">=14.0.0 <17.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"devDependencies":{"tsup":"^8.0.0","vitest":"^1.6.0","typescript":"^5.5.0"},"gitHead":"125ca191f71d5b1e21ad8fd71830c4c7796a7013","_id":"@dailyautomations/seo-platform@0.1.3","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-xkEdXBXPuiN9plg43MG1dAJ+sXJL9caYJVBLJNgKZ4SzgFcGXg6b6RGvqoLVYls0sS1gk/NNIpGNvQ5yRcGmTw==","shasum":"1d655dfc375dc21ef7765da1c4370fcbd8fe9b31","tarball":"https://registry.npmjs.org/@dailyautomations/seo-platform/-/seo-platform-0.1.3.tgz","fileCount":48,"unpackedSize":212716,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDTet4kq8i1ilWu2o4Jtvm+ZmrnWwH7G6Y1b2ayqwsYcQIhALePK42giEUvkYeaUWrtbI61JwnmVxgRdr1h5cfWif4f"}]},"_npmUser":{"name":"dailyautomations","email":"michael@dailyautomations.com"},"directories":{},"maintainers":[{"name":"dailyautomations","email":"michael@dailyautomations.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/seo-platform_0.1.3_1779339985758_0.7888873672966861"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-20T13:51:21.800Z","modified":"2026-05-21T05:06:26.081Z","0.1.0":"2026-05-20T13:51:22.224Z","0.1.1":"2026-05-20T13:54:09.113Z","0.1.2":"2026-05-21T04:27:19.139Z","0.1.3":"2026-05-21T05:06:25.983Z"},"license":"UNLICENSED","description":"Shared SEO primitives for Daily Automations sites: sitemap, redirects, canonical, trailing-slash, slugify, security headers. Conforms to the locked /api/v1/seo/* v1 wire contract.","maintainers":[{"name":"dailyautomations","email":"michael@dailyautomations.com"}],"readme":"# @dailyautomations/seo-platform\n\nShared SEO primitives for the dailyautomations portfolio (daily-seo, daily-coverage, fireside, dailylisten, and others). Eliminates per-site drift on canonical URLs, redirects, sitemaps, security headers, and slugs by providing a single, type-safe SDK backed by the daily-seo SEO API.\n\n---\n\n## Install\n\n```bash\nnpm i @dailyautomations/seo-platform\n```\n\n### Environment variables\n\n| Variable | Fallback | Required |\n|---|---|---|\n| `SEO_PLATFORM_API_URL` | `DAILY_SEO_API_URL` | Yes |\n| `SEO_PLATFORM_API_KEY` | `DAILY_SEO_API_KEY` | Yes |\n\nBoth env vars are read by `loadConfigFromEnv`. If neither is present, a `SeoPlatformError` with code `CONFIG_MISSING` is thrown at startup. Auth is sent as the `X-API-Key` request header on every API call.\n\n---\n\n## Quick start\n\n### Sitemap (Next.js App Router)\n\n```ts\n// app/sitemap.xml/route.ts\nimport { loadConfigFromEnv, fetchSitemapEntries, renderSitemap } from '@dailyautomations/seo-platform/sitemap';\n\nexport async function GET() {\n  const config = loadConfigFromEnv({ siteId: process.env.SITE_ID!, domain: 'example.com' });\n  const entries = await fetchSitemapEntries(config);\n  return new Response(renderSitemap(entries, config), {\n    headers: { 'Content-Type': 'application/xml' },\n  });\n}\n```\n\n### Redirect middleware (Next.js)\n\nImport from `/edge` in middleware files. The edge subpath has zero `node:` imports and is safe for V8-isolate runtimes.\n\n```ts\n// middleware.ts\nimport { withRedirects, withTrailingSlash, loadConfigFromEnv } from '@dailyautomations/seo-platform/edge';\nimport type { NextRequest } from 'next/server';\n\nconst config = loadConfigFromEnv({ siteId: process.env.SITE_ID!, domain: 'example.com', trailingSlash: 'Always' });\n\nexport async function middleware(req: NextRequest) {\n  return withRedirects(config, req, (r) => withTrailingSlash(config, r));\n}\n```\n\n### Security headers (next.config.js)\n\n```js\n// next.config.js\nconst { withSeoPlatformHeaders } = require('@dailyautomations/seo-platform/headers');\n\nmodule.exports = withSeoPlatformHeaders(\n  { siteId: process.env.SITE_ID, domain: 'example.com' },\n  { /* your existing next config */ }\n);\n```\n\n### Slug rename\n\n`renameSlug` POSTs to `/api/v1/seo/slug-rename`, is idempotent (a second call with the same from/to pair is a no-op), and records a 301 entry in the redirect index automatically.\n\n```ts\nimport { renameSlug, loadConfigFromEnv } from '@dailyautomations/seo-platform';\n\nconst config = loadConfigFromEnv({ siteId: '...', domain: 'example.com' });\nawait renameSlug(config, { from: '/old-path', to: '/new-path' });\n```\n\n---\n\n## Edge vs Node subpaths\n\n| Subpath | Edge-safe | Node-only | Notes |\n|---|---|---|---|\n| `.` | No | Yes | Full SDK, includes Node fetch + fs utilities |\n| `./edge` | Yes | Yes | Pure subset, zero `node:` imports |\n| `./sitemap` | No | Yes | `fetchSitemapEntries` uses Node fetch; `serializeSitemap`/`buildSitemapUrls` are edge-safe individually |\n| `./redirects` | No | Yes | Includes `getRedirectIndex` (Node HTTP) |\n| `./redirects/edge` | Yes | Yes | `lookupRedirect` + `withRedirects` only |\n| `./headers` | No | Yes | `withSeoPlatformHeaders` wraps Next.js config |\n| `./trailing-slash` | Yes | Yes | Pure function, no I/O |\n| `./canonical` | Yes | Yes | Pure function, no I/O |\n| `./slugs` | Yes | Yes | `slugify`, `normalize` are pure |\n| `./slug-rename` | No | Yes | Posts to API via Node fetch |\n| `./snapshot/node` | No | Yes | TTL-cached redirect index for Node servers |\n| `./snapshot/edge` | Yes | Yes | `hydrateFromSnapshot` for edge hydration |\n\n---\n\n## CLI: `seo-platform-lint`\n\nUse as a pre-deploy CI gate. Exits 1 if any canonical overlap or redirect errors are detected.\n\n```bash\nnpx seo-platform-lint \\\n  --site-id <uuid> \\\n  --domain example.com \\\n  [--api-url https://daily-seo.fly.dev] \\\n  [--api-key <key>] \\\n  [--trailing-slash Always|Never|Preserve]\n```\n\nReads `SEO_PLATFORM_API_URL` / `SEO_PLATFORM_API_KEY` from env (fallback `DAILY_SEO_*`) when flags are omitted.\n\nExample CI step (GitHub Actions):\n\n```yaml\n- name: SEO lint\n  run: npx seo-platform-lint --site-id ${{ vars.SITE_ID }} --domain example.com\n  env:\n    SEO_PLATFORM_API_KEY: ${{ secrets.DAILY_SEO_API_KEY }}\n    SEO_PLATFORM_API_URL: ${{ vars.DAILY_SEO_API_URL }}\n```\n\n---\n\n## Determinism guarantee\n\n`slugify` and the sitemap serializer are golden-locked: their output is byte-identical to the Rust sibling crate `seo-platform-rust`. Any change to `slugify` output is a **SemVer MAJOR** bump. Do not alter slug normalization logic in a patch or minor release.\n\n---\n\n## SEO rules enforced\n\n- **Canonical drift:** `canonicalFor` normalizes trailing-slash state to match the `trailingSlash` config. A canonical at `/path` served at `/path/` causes Google to treat the page as non-canonical.\n- **Slug renames need a redirect:** `renameSlug` records the 301 before returning. Never rename a slug without calling this function or manually inserting the redirect entry in the same deploy.\n- **Sitemap domain must match the GSC property:** `buildSitemapUrls` uses `config.domain` exclusively. Never hard-code a `*.fly.dev` domain in sitemap entries.\n- **Auth must exempt public crawl targets:** `/sitemap.xml` and `/robots.txt` must be excluded from any auth middleware. `withRedirects` does not apply redirects to these paths.\n","readmeFilename":"README.md"}