{"_id":"@anandhunadesh/next-wp-sitemap-proxy","_rev":"4-ab5b7bae743f3c621a5732aabe30ef1d","name":"@anandhunadesh/next-wp-sitemap-proxy","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@anandhunadesh/next-wp-sitemap-proxy","version":"0.1.0","keywords":["nextjs","wordpress","yoast","sitemap","headless","seo"],"license":"MIT","_id":"@anandhunadesh/next-wp-sitemap-proxy@0.1.0","maintainers":[{"name":"anandhunatesh","email":"anandhu.natesh@gmail.com"}],"dist":{"shasum":"83c5cf3954c267a012a04d6468fc05a7a89bac5c","tarball":"https://registry.npmjs.org/@anandhunadesh/next-wp-sitemap-proxy/-/next-wp-sitemap-proxy-0.1.0.tgz","fileCount":9,"integrity":"sha512-xgNHTCD1kx9SLlslleXYbS08M+0TxUNO6fafRarP68VqQwaFI6ZU2p0pls4GGgbDd2SwJgzaqHFjcORkn5Vs9A==","signatures":[{"sig":"MEQCIBldi76Mx2pQhe32HDidEXGtZRbDtqUYNxjKFZC5pFGnAiAguQwPR9nPPaidpp7EHSxq7uGhJ3cXA1qvi3+d+tmpLw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12729},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./fetch":"./src/fetch.js","./allowlist":"./src/allowlist.js","./transform":"./src/transform.js","./next/app-router":"./src/next/app-router.js","./next/pages-router":"./src/next/pages-router.js"},"scripts":{"test":"node --test test/allowlist.test.js test/transform.test.js test/fetch.test.js","pack:dry":"npm pack --dry-run","prepublishOnly":"npm test"},"_npmUser":{"name":"anandhunatesh","email":"anandhu.natesh@gmail.com"},"_npmVersion":"10.8.2","description":"Proxy and rewrite Yoast WordPress sitemaps for headless Next.js frontends","directories":{},"sideEffects":false,"_nodeVersion":"20.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/next-wp-sitemap-proxy_0.1.0_1784194880963_0.8880623646109722","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@anandhunadesh/next-wp-sitemap-proxy","version":"0.1.1","keywords":["nextjs","wordpress","yoast","sitemap","headless","seo"],"license":"MIT","_id":"@anandhunadesh/next-wp-sitemap-proxy@0.1.1","maintainers":[{"name":"anandhunatesh","email":"anandhu.natesh@gmail.com"}],"dist":{"shasum":"0403bfcb2dc3c9fe96dc196766084764b2105810","tarball":"https://registry.npmjs.org/@anandhunadesh/next-wp-sitemap-proxy/-/next-wp-sitemap-proxy-0.1.1.tgz","fileCount":9,"integrity":"sha512-rzDCzEi1CbWd9KMMfbo0aH3wtVXmiGxNW1q5sr9x3lMcTzXtfgGfTCVH7p2s7egQuLNIpR85WeYjfADg/qTaOw==","signatures":[{"sig":"MEQCIFr7N/O4D4jaOHNgT8cN6z/nLk9c7N7byOb26MOvXv5jAiAovg19qaKTiVWaG36nh/LtJGqmdPjO2gcUZ8dgEHHNZQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15879},"main":"./src/index.js","type":"module","engines":{"node":">=18"},"exports":{".":"./src/index.js","./fetch":"./src/fetch.js","./allowlist":"./src/allowlist.js","./transform":"./src/transform.js","./next/app-router":"./src/next/app-router.js","./next/pages-router":"./src/next/pages-router.js"},"scripts":{"test":"node --test test/allowlist.test.js test/transform.test.js test/fetch.test.js","pack:dry":"npm pack --dry-run","prepublishOnly":"npm test"},"_npmUser":{"name":"anandhunatesh","email":"anandhu.natesh@gmail.com"},"_npmVersion":"10.8.2","description":"Proxy and rewrite Yoast WordPress sitemaps for headless Next.js frontends","directories":{},"sideEffects":false,"_nodeVersion":"20.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/next-wp-sitemap-proxy_0.1.1_1784197173770_0.9250870528338953","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@anandhunadesh/next-wp-sitemap-proxy","version":"0.1.2","keywords":["nextjs","wordpress","yoast","sitemap","headless","seo","typescript"],"license":"MIT","_id":"@anandhunadesh/next-wp-sitemap-proxy@0.1.2","maintainers":[{"name":"anandhunatesh","email":"anandhu.natesh@gmail.com"}],"dist":{"shasum":"e7fb3f589aab052698f420dec4b8f7a0799bd079","tarball":"https://registry.npmjs.org/@anandhunadesh/next-wp-sitemap-proxy/-/next-wp-sitemap-proxy-0.1.2.tgz","fileCount":15,"integrity":"sha512-v5MU02upkRIDsLnt20IAW6WHWiebIQFegrVHV1YJtIrYkRuRVcn9NKHiCtrDa65KKE8ExsNb2IWgLgZNdB0QDA==","signatures":[{"sig":"MEUCIAps4MUz3WBlO8ckSDYM0dhSF6uptXgjnqPxy4elSb7fAiEAifzgjDWMQIIXxEHC9oGJKND6pYQ5g36Xjj1HUqMZH0Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":20777},"main":"./src/index.js","type":"module","types":"./src/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./src/index.d.ts","import":"./src/index.js","default":"./src/index.js"},"./fetch":{"types":"./src/fetch.d.ts","import":"./src/fetch.js","default":"./src/fetch.js"},"./allowlist":{"types":"./src/allowlist.d.ts","import":"./src/allowlist.js","default":"./src/allowlist.js"},"./transform":{"types":"./src/transform.d.ts","import":"./src/transform.js","default":"./src/transform.js"},"./next/app-router":{"types":"./src/next/app-router.d.ts","import":"./src/next/app-router.js","default":"./src/next/app-router.js"},"./next/pages-router":{"types":"./src/next/pages-router.d.ts","import":"./src/next/pages-router.js","default":"./src/next/pages-router.js"}},"scripts":{"test":"node --test test/allowlist.test.js test/transform.test.js test/fetch.test.js","pack:dry":"npm pack --dry-run","prepublishOnly":"npm test"},"_npmUser":{"name":"anandhunatesh","email":"anandhu.natesh@gmail.com"},"_npmVersion":"10.8.2","description":"Proxy and rewrite Yoast WordPress sitemaps for headless Next.js frontends","directories":{},"sideEffects":false,"_nodeVersion":"20.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/next-wp-sitemap-proxy_0.1.2_1784201418255_0.8886132597904961","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@anandhunadesh/next-wp-sitemap-proxy","version":"0.2.1","description":"Proxy and rewrite Yoast WordPress sitemaps for headless Next.js frontends","type":"module","main":"./src/index.js","types":"./src/index.d.ts","exports":{".":{"types":"./src/index.d.ts","import":"./src/index.js","default":"./src/index.js"},"./allowlist":{"types":"./src/allowlist.d.ts","import":"./src/allowlist.js","default":"./src/allowlist.js"},"./transform":{"types":"./src/transform.d.ts","import":"./src/transform.js","default":"./src/transform.js"},"./fetch":{"types":"./src/fetch.d.ts","import":"./src/fetch.js","default":"./src/fetch.js"},"./stylesheet":{"types":"./src/stylesheet.d.ts","import":"./src/stylesheet.js","default":"./src/stylesheet.js"},"./next/pages-router":{"types":"./src/next/pages-router.d.ts","import":"./src/next/pages-router.js","default":"./src/next/pages-router.js"},"./next/app-router":{"types":"./src/next/app-router.d.ts","import":"./src/next/app-router.js","default":"./src/next/app-router.js"}},"sideEffects":false,"publishConfig":{"access":"public"},"scripts":{"test":"node --test test/allowlist.test.js test/transform.test.js test/fetch.test.js","pack:dry":"npm pack --dry-run","prepublishOnly":"npm test"},"keywords":["nextjs","wordpress","yoast","sitemap","headless","seo","typescript","xsl"],"license":"MIT","repository":{"type":"git","url":"git+https://github.com/Macromaniak/next-wp-sitemap-proxy.git"},"bugs":{"url":"https://github.com/Macromaniak/next-wp-sitemap-proxy/issues"},"homepage":"https://github.com/Macromaniak/next-wp-sitemap-proxy#readme","engines":{"node":">=18"},"_id":"@anandhunadesh/next-wp-sitemap-proxy@0.2.1","_nodeVersion":"20.18.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-K3gyXSmEKokt6Py+ca9XSGMgfEccnNzNdHQLr4gjyj2afd4wsjrqLZ2X67owwi6E04aN7U2FfKW0Ve0Nex3XmQ==","shasum":"fd35ea7621b2309c70d6dc8ebb813aaaf40b0c96","tarball":"https://registry.npmjs.org/@anandhunadesh/next-wp-sitemap-proxy/-/next-wp-sitemap-proxy-0.2.1.tgz","fileCount":18,"unpackedSize":30597,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCTCF8lAJQgZF06lHW+ouKhj2wn84rwQ3zyBB8BfcDxWwIhAKNFOLqRalFf+Vh8+43dDL2b7yq5Dt1+K+AJ0Xp+H+2j"}]},"_npmUser":{"name":"anandhunatesh","email":"anandhu.natesh@gmail.com"},"directories":{},"maintainers":[{"name":"anandhunatesh","email":"anandhu.natesh@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/next-wp-sitemap-proxy_0.2.1_1784626862328_0.6116515714028408"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-16T09:41:20.740Z","modified":"2026-07-21T09:41:02.643Z","0.1.0":"2026-07-16T09:41:21.094Z","0.1.1":"2026-07-16T10:19:33.902Z","0.1.2":"2026-07-16T11:30:18.408Z","0.2.1":"2026-07-21T09:41:02.483Z"},"license":"MIT","keywords":["nextjs","wordpress","yoast","sitemap","headless","seo","typescript","xsl"],"description":"Proxy and rewrite Yoast WordPress sitemaps for headless Next.js frontends","maintainers":[{"name":"anandhunatesh","email":"anandhu.natesh@gmail.com"}],"readme":"# @anandhunadesh/next-wp-sitemap-proxy\n\nProxy Yoast SEO sitemaps from WordPress through a headless Next.js frontend.\n\nIn a typical headless setup, Yoast emits sitemap URLs that point at the CMS domain. This package:\n\n1. Serves those sitemaps from your Next.js site (`/sitemap.xml`, `/post-sitemap.xml`, …)\n2. Fetches the XML from WordPress\n3. Rewrites page `<loc>` URLs to your frontend domain\n4. Keeps image / upload URLs on the CMS domain\n5. Only allows known Yoast sitemap filenames (path traversal safe)\n6. Serves a styled browser view via bundled `main-sitemap.xsl` (like Yoast)\n\n## Requirements\n\n- Node.js 18+\n- Next.js (Pages Router or App Router)\n- WordPress with Yoast SEO sitemaps enabled\n\nTypeScript is supported via bundled `.d.ts` files (no extra install). Works in JS and TS projects.\n\n## Install\n\n```bash\nnpm install @anandhunadesh/next-wp-sitemap-proxy\n```\n\n## Environment variables\n\nUse full origins (no trailing path):\n\n| Variable | Example | Purpose |\n| --- | --- | --- |\n| `WP_URL` / `CMS_ORIGIN` | `https://cms.example.com` | WordPress / Yoast source |\n| `NEXT_PUBLIC_SITE_URL` / `FRONTEND_ORIGIN` | `https://www.example.com` | Public Next.js site |\n\nYou can name them whatever you like — pass the values into the handlers as `cmsOrigin` and `frontendOrigin`.\n\n## Setup overview\n\nYou need **two pieces** in every project:\n\n1. A **route** that fetches + returns the transformed XML\n2. **Rewrites** in `next.config` so `/sitemap.xml` hits that route\n\nUse the **same** allowlist config in both places.\n\n---\n\n## Pages Router\n\n### 1. Create the sitemap route\n\n`pages/sitemaps/[...sitemap].js`:\n\n```js\nimport SitemapProxyPage, {\n  createPagesRouterSitemapHandler,\n} from \"@anandhunadesh/next-wp-sitemap-proxy/next/pages-router\";\n\nconst sitemapConfig = {\n  cmsOrigin: process.env.WP_URL,\n  frontendOrigin: process.env.NEXT_PUBLIC_SITE_URL,\n  fetchTimeoutMs: 10000,\n  allowlistOptions: {\n    staticSitemapFiles: [\n      \"sitemap.xml\",\n      \"post-sitemap.xml\",\n      \"post-sitemap2.xml\",\n      \"page-sitemap.xml\",\n      // Add custom post type sitemaps as needed:\n      // \"people-sitemap.xml\",\n      // \"press-sitemap.xml\",\n    ],\n    paginationRules: [\n      { prefix: \"post-sitemap\", minNumber: 3 },\n      { prefix: \"page-sitemap\", minNumber: 2 },\n    ],\n  },\n};\n\nexport const getServerSideProps = createPagesRouterSitemapHandler(sitemapConfig);\n\nexport default SitemapProxyPage;\n```\n\n### 2. Add rewrites\n\n`next.config.mjs` (or `next.config.js`):\n\n```js\nimport { buildSitemapRewrites } from \"@anandhunadesh/next-wp-sitemap-proxy\";\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  async rewrites() {\n    return buildSitemapRewrites({\n      staticSitemapFiles: [\n        \"sitemap.xml\",\n        \"post-sitemap.xml\",\n        \"post-sitemap2.xml\",\n        \"page-sitemap.xml\",\n      ],\n      paginationRules: [\n        { prefix: \"post-sitemap\", minNumber: 3 },\n        { prefix: \"page-sitemap\", minNumber: 2 },\n      ],\n      routePrefix: \"/sitemaps\",\n    });\n  },\n};\n\nexport default nextConfig;\n```\n\nThis maps:\n\n- `/main-sitemap.xsl` → `/sitemaps/main-sitemap.xsl`\n- `/sitemap.xml` → `/sitemaps/sitemap.xml`\n- `/post-sitemap3.xml` → `/sitemaps/post-sitemap3.xml`\n- etc.\n\n---\n\n## App Router\n\n### 1. Create the route handler\n\n`app/sitemaps/[...sitemap]/route.js`:\n\n```js\nimport { createAppRouterSitemapHandler } from \"@anandhunadesh/next-wp-sitemap-proxy/next/app-router\";\n\nexport const GET = createAppRouterSitemapHandler({\n  cmsOrigin: process.env.WP_URL,\n  frontendOrigin: process.env.NEXT_PUBLIC_SITE_URL,\n  fetchTimeoutMs: 10000,\n  allowlistOptions: {\n    staticSitemapFiles: [\n      \"sitemap.xml\",\n      \"post-sitemap.xml\",\n      \"post-sitemap2.xml\",\n      \"page-sitemap.xml\",\n    ],\n    paginationRules: [\n      { prefix: \"post-sitemap\", minNumber: 3 },\n      { prefix: \"page-sitemap\", minNumber: 2 },\n    ],\n  },\n});\n```\n\n### 2. Add the same rewrites\n\nUse `buildSitemapRewrites()` in `next.config` exactly as in the Pages Router section above.\n\n---\n\n## Configuration reference\n\n### Handler options (`createPagesRouterSitemapHandler` / `createAppRouterSitemapHandler`)\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `cmsOrigin` | `string` | required | WordPress origin, e.g. `https://cms.example.com` |\n| `frontendOrigin` | `string` | required | Next.js public origin |\n| `fetchTimeoutMs` | `number` | `10000` | CMS fetch timeout |\n| `allowlistOptions` | `object` | package defaults | Which sitemap files are allowed |\n| `stylesheetHref` | `string \\| false \\| null` | `/main-sitemap.xsl` | XSL href injected into XML. Pass `false`/`null` to disable |\n| `fetchImpl` | `function` | `fetch` | Optional custom fetch (tests / proxies) |\n\n### Allowlist options\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `staticSitemapFiles` | `string[]` | `sitemap.xml`, `post-sitemap.xml`, `post-sitemap2.xml`, `page-sitemap.xml` | Exact filenames to allow |\n| `paginationRules` | `{ prefix, minNumber }[]` | post ≥ 3, page ≥ 2 | Pattern allowlist for paginated Yoast files |\n| `routePrefix` | `string` | `/sitemaps` | Used only by `buildSitemapRewrites` |\n| `stylesheetHref` | `string \\| false \\| null` | `/main-sitemap.xsl` | Also rewritten into the sitemap route. Pass `false`/`null` to skip |\n\n**Tip:** List every Yoast sitemap your site actually emits (including CPT sitemaps like `people-sitemap.xml`). Files not on the allowlist return `404`.\n\n### Browser styling (XSL)\n\nBy default the package:\n\n1. Injects `<?xml-stylesheet type=\"text/xsl\" href=\"/main-sitemap.xsl\"?>` into sitemap XML\n2. Rewrites `/main-sitemap.xsl` → `/sitemaps/main-sitemap.xsl`\n3. Serves the bundled XSL from the same catch-all sitemap route\n\nOpen `/sitemap.xml` in a browser to see the styled table view. Search engines ignore the XSL and read the raw XML.\n\nTo disable styling:\n\n```js\ncreatePagesRouterSitemapHandler({\n  cmsOrigin: process.env.WP_URL,\n  frontendOrigin: process.env.NEXT_PUBLIC_SITE_URL,\n  stylesheetHref: false,\n});\n\nbuildSitemapRewrites({\n  // ...\n  stylesheetHref: false,\n});\n```\n\n---\n\n## Verify it works\n\n1. Confirm Yoast works on CMS: `https://cms.example.com/sitemap.xml`\n2. Open frontend: `https://www.example.com/sitemap.xml`\n3. In a browser you should see a styled HTML table (XSL)\n4. Check that `<loc>` URLs use the **frontend** domain (View Source)\n5. Check that `<image:loc>` (and `/wp-content/uploads/` URLs) still use the **CMS** domain\n6. Confirm `https://www.example.com/main-sitemap.xsl` loads\n\n---\n\n## Behavior details\n\n- Yoast CMS stylesheet PIs are replaced with `/main-sitemap.xsl` on the frontend (or removed if disabled)\n- `<loc>` URLs are rewritten from CMS → frontend (except `/wp-content/uploads/` paths)\n- `<image:loc>` URLs are forced back to CMS if they leaked to the frontend domain\n- Missing / unknown sitemaps → `404`\n- CMS timeout → `504`\n- Other CMS fetch failures → `502`\n\n---\n\n## Low-level API\n\nIf you need custom handlers:\n\n```js\nimport {\n  isAllowedSitemapPath,\n  buildSitemapRewrites,\n  transformYoastSitemapXml,\n  fetchYoastSitemap,\n} from \"@anandhunadesh/next-wp-sitemap-proxy\";\n\nconst result = await fetchYoastSitemap(\"sitemap.xml\", {\n  cmsOrigin: \"https://cms.example.com\",\n  frontendOrigin: \"https://www.example.com\",\n});\n\nif (result.ok) {\n  // result.xml — transformed sitemap\n}\n```\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md","homepage":"https://github.com/Macromaniak/next-wp-sitemap-proxy#readme","repository":{"type":"git","url":"git+https://github.com/Macromaniak/next-wp-sitemap-proxy.git"},"bugs":{"url":"https://github.com/Macromaniak/next-wp-sitemap-proxy/issues"}}