rankdeploy-middleware 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  // src/index.ts
2
2
  var RANKDEPLOY_API = "https://proxy.unikium.com";
3
+ var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
3
4
  var BOT_USER_AGENTS = [
4
5
  "googlebot",
5
6
  "bingbot",
@@ -30,9 +31,20 @@ var BOT_USER_AGENTS = [
30
31
  "anthropic",
31
32
  "claudebot",
32
33
  "perplexitybot",
33
- "cohere"
34
+ "cohere",
35
+ // SEO tools & OG checkers
36
+ "opengraph",
37
+ "metatags",
38
+ "preview",
39
+ "crawler",
40
+ "spider",
41
+ "fetch",
42
+ "curl",
43
+ "wget",
44
+ "python-requests",
45
+ "node-fetch",
46
+ "axios"
34
47
  ];
35
- var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
36
48
  function isBot(userAgent, extraBots) {
37
49
  const ua = userAgent.toLowerCase();
38
50
  const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
@@ -50,7 +62,7 @@ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API
50
62
  const res = await fetch(
51
63
  `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,
52
64
  {
53
- headers: { "User-Agent": userAgent },
65
+ headers: { "User-Agent": userAgent || "Googlebot/2.1" },
54
66
  signal: AbortSignal.timeout(1e4)
55
67
  }
56
68
  );
@@ -65,11 +77,11 @@ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API
65
77
 
66
78
  export {
67
79
  RANKDEPLOY_API,
68
- BOT_USER_AGENTS,
69
80
  STATIC_EXTENSIONS,
81
+ BOT_USER_AGENTS,
70
82
  isBot,
71
83
  isStaticAsset,
72
84
  isExcluded,
73
85
  fetchPrerendered
74
86
  };
75
- //# sourceMappingURL=chunk-UQC64IMV.mjs.map
87
+ //# sourceMappingURL=chunk-C43DAMVJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Serves pre-rendered HTML with SEO overrides to all visitors.\n * The pre-rendered HTML is visually identical to the original page\n * but includes injected meta tags (title, description, canonical, og:tags).\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n /** Only serve to bots (default: false — serves to everyone for OG checker compatibility) */\n botsOnly?: boolean;\n /** Additional bot user agents to detect (used when botsOnly=true) */\n extraBots?: string[];\n}\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n // SEO tools & OG checkers\n \"opengraph\", \"metatags\", \"preview\", \"crawler\", \"spider\", \"fetch\",\n \"curl\", \"wget\", \"python-requests\", \"node-fetch\", \"axios\",\n];\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent || \"Googlebot/2.1\" },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";AAQO,IAAM,iBAAiB;AAEvB,IAAM,oBAAoB;AAe1B,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAAA;AAAA,EAEhE;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EAAU;AAAA,EACzD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAmB;AAAA,EAAc;AACnD;AAEO,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,aAAa,gBAAgB;AAAA,QACtD,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
package/dist/express.js CHANGED
@@ -27,6 +27,7 @@ module.exports = __toCommonJS(express_exports);
27
27
 
28
28
  // src/index.ts
29
29
  var RANKDEPLOY_API = "https://proxy.unikium.com";
30
+ var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
30
31
  var BOT_USER_AGENTS = [
31
32
  "googlebot",
32
33
  "bingbot",
@@ -57,9 +58,20 @@ var BOT_USER_AGENTS = [
57
58
  "anthropic",
58
59
  "claudebot",
59
60
  "perplexitybot",
60
- "cohere"
61
+ "cohere",
62
+ // SEO tools & OG checkers
63
+ "opengraph",
64
+ "metatags",
65
+ "preview",
66
+ "crawler",
67
+ "spider",
68
+ "fetch",
69
+ "curl",
70
+ "wget",
71
+ "python-requests",
72
+ "node-fetch",
73
+ "axios"
61
74
  ];
62
- var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
63
75
  function isBot(userAgent, extraBots) {
64
76
  const ua = userAgent.toLowerCase();
65
77
  const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
@@ -77,7 +89,7 @@ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API
77
89
  const res = await fetch(
78
90
  `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,
79
91
  {
80
- headers: { "User-Agent": userAgent },
92
+ headers: { "User-Agent": userAgent || "Googlebot/2.1" },
81
93
  signal: AbortSignal.timeout(1e4)
82
94
  }
83
95
  );
@@ -91,23 +103,20 @@ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API
91
103
  }
92
104
 
93
105
  // src/express.ts
94
- function rankdeploy(options = {}) {
106
+ function rankdeploy(options) {
95
107
  return async function middleware(req, res, next) {
96
108
  const userAgent = req.headers["user-agent"] || req.get?.("user-agent") || "";
97
109
  const pathname = req.url.split("?")[0];
98
- if (!isBot(userAgent, options.extraBots)) {
99
- return next();
100
- }
101
- if (isStaticAsset(pathname)) {
110
+ if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
102
111
  return next();
103
112
  }
104
- if (isExcluded(pathname, options.excludePaths)) {
113
+ if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {
105
114
  return next();
106
115
  }
107
116
  const protocol = req.protocol || "https";
108
117
  const host = req.get?.("host") || req.headers.host || "";
109
118
  const fullUrl = `${protocol}://${host}${req.url}`;
110
- const html = await fetchPrerendered(fullUrl, userAgent, options.apiUrl);
119
+ const html = await fetchPrerendered(fullUrl, userAgent, options.siteKey, options.apiUrl);
111
120
  if (html) {
112
121
  res.set("Content-Type", "text/html; charset=utf-8");
113
122
  res.set("X-Prerendered", "true");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/express.ts","../src/index.ts"],"sourcesContent":["/**\n * @rankdeploy/middleware/express — Express.js middleware\n *\n * Usage:\n * import { rankdeploy } from '@rankdeploy/middleware/express'\n * app.use(rankdeploy())\n *\n * With options:\n * app.use(rankdeploy({ excludePaths: ['/api', '/admin'] }))\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface ExpressRequest {\n headers: Record<string, string | string[] | undefined>;\n url: string;\n protocol: string;\n get(name: string): string | undefined;\n}\n\ninterface ExpressResponse {\n set(name: string, value: string): void;\n status(code: number): ExpressResponse;\n send(body: string): void;\n}\n\ntype NextFunction = () => void;\n\n/**\n * Create Express middleware for Rank Deploy.\n *\n * Usage:\n * app.use(rankdeploy())\n */\nexport function rankdeploy(options: RankDeployOptions = {}) {\n return async function middleware(\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction,\n ) {\n const userAgent = (req.headers[\"user-agent\"] as string) || req.get?.(\"user-agent\") || \"\";\n const pathname = req.url.split(\"?\")[0];\n\n if (!isBot(userAgent, options.extraBots)) {\n return next();\n }\n\n if (isStaticAsset(pathname)) {\n return next();\n }\n\n if (isExcluded(pathname, options.excludePaths)) {\n return next();\n }\n\n const protocol = req.protocol || \"https\";\n const host = req.get?.(\"host\") || req.headers.host || \"\";\n const fullUrl = `${protocol}://${host}${req.url}`;\n\n const html = await fetchPrerendered(fullUrl, userAgent, options.apiUrl);\n\n if (html) {\n res.set(\"Content-Type\", \"text/html; charset=utf-8\");\n res.set(\"X-Prerendered\", \"true\");\n res.set(\"X-Powered-By\", \"Rank Deploy\");\n return res.status(200).send(html);\n }\n\n next();\n };\n}\n\nexport default rankdeploy;\n","/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Detects search engine bots and serves pre-rendered HTML from Rank Deploy API.\n * Human visitors are not affected.\n *\n * Usage:\n * import { createMiddleware } from 'rankdeploy-middleware/next'\n * export const middleware = createMiddleware({ siteKey: 'your-site-key' })\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n];\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Additional bot user agents to detect */\n extraBots?: string[];\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n}\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWO,IAAM,iBAAiB;AAEvB,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAClE;AAEO,IAAM,oBAAoB;AAa1B,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADzCO,SAAS,WAAW,UAA6B,CAAC,GAAG;AAC1D,SAAO,eAAe,WACpB,KACA,KACA,MACA;AACA,UAAM,YAAa,IAAI,QAAQ,YAAY,KAAgB,IAAI,MAAM,YAAY,KAAK;AACtF,UAAM,WAAW,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC;AAErC,QAAI,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACxC,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,cAAc,QAAQ,GAAG;AAC3B,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,WAAW,UAAU,QAAQ,YAAY,GAAG;AAC9C,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,WAAW,IAAI,YAAY;AACjC,UAAM,OAAO,IAAI,MAAM,MAAM,KAAK,IAAI,QAAQ,QAAQ;AACtD,UAAM,UAAU,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,GAAG;AAE/C,UAAM,OAAO,MAAM,iBAAiB,SAAS,WAAW,QAAQ,MAAM;AAEtE,QAAI,MAAM;AACR,UAAI,IAAI,gBAAgB,0BAA0B;AAClD,UAAI,IAAI,iBAAiB,MAAM;AAC/B,UAAI,IAAI,gBAAgB,aAAa;AACrC,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,IAAI;AAAA,IAClC;AAEA,SAAK;AAAA,EACP;AACF;AAEA,IAAO,kBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/express.ts","../src/index.ts"],"sourcesContent":["/**\n * rankdeploy-middleware/express — Express.js middleware\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface ExpressRequest {\n headers: Record<string, string | string[] | undefined>;\n url: string;\n protocol: string;\n get?(name: string): string | undefined;\n}\n\ninterface ExpressResponse {\n set(name: string, value: string): void;\n status(code: number): ExpressResponse;\n send(body: string): void;\n}\n\ntype NextFunction = () => void;\n\nexport function rankdeploy(options: RankDeployOptions) {\n return async function middleware(req: ExpressRequest, res: ExpressResponse, next: NextFunction) {\n const userAgent = (req.headers[\"user-agent\"] as string) || req.get?.(\"user-agent\") || \"\";\n const pathname = req.url.split(\"?\")[0];\n\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return next();\n }\n\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return next();\n }\n\n const protocol = req.protocol || \"https\";\n const host = req.get?.(\"host\") || req.headers.host || \"\";\n const fullUrl = `${protocol}://${host}${req.url}`;\n\n const html = await fetchPrerendered(fullUrl, userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n res.set(\"Content-Type\", \"text/html; charset=utf-8\");\n res.set(\"X-Prerendered\", \"true\");\n res.set(\"X-Powered-By\", \"Rank Deploy\");\n return res.status(200).send(html);\n }\n\n next();\n };\n}\n\nexport default rankdeploy;\n","/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Serves pre-rendered HTML with SEO overrides to all visitors.\n * The pre-rendered HTML is visually identical to the original page\n * but includes injected meta tags (title, description, canonical, og:tags).\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n /** Only serve to bots (default: false — serves to everyone for OG checker compatibility) */\n botsOnly?: boolean;\n /** Additional bot user agents to detect (used when botsOnly=true) */\n extraBots?: string[];\n}\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n // SEO tools & OG checkers\n \"opengraph\", \"metatags\", \"preview\", \"crawler\", \"spider\", \"fetch\",\n \"curl\", \"wget\", \"python-requests\", \"node-fetch\", \"axios\",\n];\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent || \"Googlebot/2.1\" },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQO,IAAM,iBAAiB;AAEvB,IAAM,oBAAoB;AAe1B,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAAA;AAAA,EAEhE;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EAAU;AAAA,EACzD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAmB;AAAA,EAAc;AACnD;AAEO,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,aAAa,gBAAgB;AAAA,QACtD,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADxDO,SAAS,WAAW,SAA4B;AACrD,SAAO,eAAe,WAAW,KAAqB,KAAsB,MAAoB;AAC9F,UAAM,YAAa,IAAI,QAAQ,YAAY,KAAgB,IAAI,MAAM,YAAY,KAAK;AACtF,UAAM,WAAW,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC;AAErC,QAAI,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACzE,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,QAAQ,aAAa,SAAS,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACtE,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,WAAW,IAAI,YAAY;AACjC,UAAM,OAAO,IAAI,MAAM,MAAM,KAAK,IAAI,QAAQ,QAAQ;AACtD,UAAM,UAAU,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,GAAG;AAE/C,UAAM,OAAO,MAAM,iBAAiB,SAAS,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAEvF,QAAI,MAAM;AACR,UAAI,IAAI,gBAAgB,0BAA0B;AAClD,UAAI,IAAI,iBAAiB,MAAM;AAC/B,UAAI,IAAI,gBAAgB,aAAa;AACrC,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,IAAI;AAAA,IAClC;AAEA,SAAK;AAAA,EACP;AACF;AAEA,IAAO,kBAAQ;","names":[]}
package/dist/express.mjs CHANGED
@@ -3,26 +3,23 @@ import {
3
3
  isBot,
4
4
  isExcluded,
5
5
  isStaticAsset
6
- } from "./chunk-UQC64IMV.mjs";
6
+ } from "./chunk-C43DAMVJ.mjs";
7
7
 
8
8
  // src/express.ts
9
- function rankdeploy(options = {}) {
9
+ function rankdeploy(options) {
10
10
  return async function middleware(req, res, next) {
11
11
  const userAgent = req.headers["user-agent"] || req.get?.("user-agent") || "";
12
12
  const pathname = req.url.split("?")[0];
13
- if (!isBot(userAgent, options.extraBots)) {
13
+ if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
14
14
  return next();
15
15
  }
16
- if (isStaticAsset(pathname)) {
17
- return next();
18
- }
19
- if (isExcluded(pathname, options.excludePaths)) {
16
+ if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {
20
17
  return next();
21
18
  }
22
19
  const protocol = req.protocol || "https";
23
20
  const host = req.get?.("host") || req.headers.host || "";
24
21
  const fullUrl = `${protocol}://${host}${req.url}`;
25
- const html = await fetchPrerendered(fullUrl, userAgent, options.apiUrl);
22
+ const html = await fetchPrerendered(fullUrl, userAgent, options.siteKey, options.apiUrl);
26
23
  if (html) {
27
24
  res.set("Content-Type", "text/html; charset=utf-8");
28
25
  res.set("X-Prerendered", "true");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/express.ts"],"sourcesContent":["/**\n * @rankdeploy/middleware/express — Express.js middleware\n *\n * Usage:\n * import { rankdeploy } from '@rankdeploy/middleware/express'\n * app.use(rankdeploy())\n *\n * With options:\n * app.use(rankdeploy({ excludePaths: ['/api', '/admin'] }))\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface ExpressRequest {\n headers: Record<string, string | string[] | undefined>;\n url: string;\n protocol: string;\n get(name: string): string | undefined;\n}\n\ninterface ExpressResponse {\n set(name: string, value: string): void;\n status(code: number): ExpressResponse;\n send(body: string): void;\n}\n\ntype NextFunction = () => void;\n\n/**\n * Create Express middleware for Rank Deploy.\n *\n * Usage:\n * app.use(rankdeploy())\n */\nexport function rankdeploy(options: RankDeployOptions = {}) {\n return async function middleware(\n req: ExpressRequest,\n res: ExpressResponse,\n next: NextFunction,\n ) {\n const userAgent = (req.headers[\"user-agent\"] as string) || req.get?.(\"user-agent\") || \"\";\n const pathname = req.url.split(\"?\")[0];\n\n if (!isBot(userAgent, options.extraBots)) {\n return next();\n }\n\n if (isStaticAsset(pathname)) {\n return next();\n }\n\n if (isExcluded(pathname, options.excludePaths)) {\n return next();\n }\n\n const protocol = req.protocol || \"https\";\n const host = req.get?.(\"host\") || req.headers.host || \"\";\n const fullUrl = `${protocol}://${host}${req.url}`;\n\n const html = await fetchPrerendered(fullUrl, userAgent, options.apiUrl);\n\n if (html) {\n res.set(\"Content-Type\", \"text/html; charset=utf-8\");\n res.set(\"X-Prerendered\", \"true\");\n res.set(\"X-Powered-By\", \"Rank Deploy\");\n return res.status(200).send(html);\n }\n\n next();\n };\n}\n\nexport default rankdeploy;\n"],"mappings":";;;;;;;;AAkCO,SAAS,WAAW,UAA6B,CAAC,GAAG;AAC1D,SAAO,eAAe,WACpB,KACA,KACA,MACA;AACA,UAAM,YAAa,IAAI,QAAQ,YAAY,KAAgB,IAAI,MAAM,YAAY,KAAK;AACtF,UAAM,WAAW,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC;AAErC,QAAI,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACxC,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,cAAc,QAAQ,GAAG;AAC3B,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,WAAW,UAAU,QAAQ,YAAY,GAAG;AAC9C,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,WAAW,IAAI,YAAY;AACjC,UAAM,OAAO,IAAI,MAAM,MAAM,KAAK,IAAI,QAAQ,QAAQ;AACtD,UAAM,UAAU,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,GAAG;AAE/C,UAAM,OAAO,MAAM,iBAAiB,SAAS,WAAW,QAAQ,MAAM;AAEtE,QAAI,MAAM;AACR,UAAI,IAAI,gBAAgB,0BAA0B;AAClD,UAAI,IAAI,iBAAiB,MAAM;AAC/B,UAAI,IAAI,gBAAgB,aAAa;AACrC,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,IAAI;AAAA,IAClC;AAEA,SAAK;AAAA,EACP;AACF;AAEA,IAAO,kBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/express.ts"],"sourcesContent":["/**\n * rankdeploy-middleware/express — Express.js middleware\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface ExpressRequest {\n headers: Record<string, string | string[] | undefined>;\n url: string;\n protocol: string;\n get?(name: string): string | undefined;\n}\n\ninterface ExpressResponse {\n set(name: string, value: string): void;\n status(code: number): ExpressResponse;\n send(body: string): void;\n}\n\ntype NextFunction = () => void;\n\nexport function rankdeploy(options: RankDeployOptions) {\n return async function middleware(req: ExpressRequest, res: ExpressResponse, next: NextFunction) {\n const userAgent = (req.headers[\"user-agent\"] as string) || req.get?.(\"user-agent\") || \"\";\n const pathname = req.url.split(\"?\")[0];\n\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return next();\n }\n\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return next();\n }\n\n const protocol = req.protocol || \"https\";\n const host = req.get?.(\"host\") || req.headers.host || \"\";\n const fullUrl = `${protocol}://${host}${req.url}`;\n\n const html = await fetchPrerendered(fullUrl, userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n res.set(\"Content-Type\", \"text/html; charset=utf-8\");\n res.set(\"X-Prerendered\", \"true\");\n res.set(\"X-Powered-By\", \"Rank Deploy\");\n return res.status(200).send(html);\n }\n\n next();\n };\n}\n\nexport default rankdeploy;\n"],"mappings":";;;;;;;;AAqBO,SAAS,WAAW,SAA4B;AACrD,SAAO,eAAe,WAAW,KAAqB,KAAsB,MAAoB;AAC9F,UAAM,YAAa,IAAI,QAAQ,YAAY,KAAgB,IAAI,MAAM,YAAY,KAAK;AACtF,UAAM,WAAW,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC;AAErC,QAAI,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACzE,aAAO,KAAK;AAAA,IACd;AAEA,QAAI,QAAQ,aAAa,SAAS,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACtE,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,WAAW,IAAI,YAAY;AACjC,UAAM,OAAO,IAAI,MAAM,MAAM,KAAK,IAAI,QAAQ,QAAQ;AACtD,UAAM,UAAU,GAAG,QAAQ,MAAM,IAAI,GAAG,IAAI,GAAG;AAE/C,UAAM,OAAO,MAAM,iBAAiB,SAAS,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAEvF,QAAI,MAAM;AACR,UAAI,IAAI,gBAAgB,0BAA0B;AAClD,UAAI,IAAI,iBAAiB,MAAM;AAC/B,UAAI,IAAI,gBAAgB,aAAa;AACrC,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,IAAI;AAAA,IAClC;AAEA,SAAK;AAAA,EACP;AACF;AAEA,IAAO,kBAAQ;","names":[]}
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ __export(src_exports, {
30
30
  });
31
31
  module.exports = __toCommonJS(src_exports);
32
32
  var RANKDEPLOY_API = "https://proxy.unikium.com";
33
+ var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
33
34
  var BOT_USER_AGENTS = [
34
35
  "googlebot",
35
36
  "bingbot",
@@ -60,9 +61,20 @@ var BOT_USER_AGENTS = [
60
61
  "anthropic",
61
62
  "claudebot",
62
63
  "perplexitybot",
63
- "cohere"
64
+ "cohere",
65
+ // SEO tools & OG checkers
66
+ "opengraph",
67
+ "metatags",
68
+ "preview",
69
+ "crawler",
70
+ "spider",
71
+ "fetch",
72
+ "curl",
73
+ "wget",
74
+ "python-requests",
75
+ "node-fetch",
76
+ "axios"
64
77
  ];
65
- var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
66
78
  function isBot(userAgent, extraBots) {
67
79
  const ua = userAgent.toLowerCase();
68
80
  const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
@@ -80,7 +92,7 @@ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API
80
92
  const res = await fetch(
81
93
  `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,
82
94
  {
83
- headers: { "User-Agent": userAgent },
95
+ headers: { "User-Agent": userAgent || "Googlebot/2.1" },
84
96
  signal: AbortSignal.timeout(1e4)
85
97
  }
86
98
  );
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Detects search engine bots and serves pre-rendered HTML from Rank Deploy API.\n * Human visitors are not affected.\n *\n * Usage:\n * import { createMiddleware } from 'rankdeploy-middleware/next'\n * export const middleware = createMiddleware({ siteKey: 'your-site-key' })\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n];\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Additional bot user agents to detect */\n extraBots?: string[];\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n}\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWO,IAAM,iBAAiB;AAEvB,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAClE;AAEO,IAAM,oBAAoB;AAa1B,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Serves pre-rendered HTML with SEO overrides to all visitors.\n * The pre-rendered HTML is visually identical to the original page\n * but includes injected meta tags (title, description, canonical, og:tags).\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n /** Only serve to bots (default: false — serves to everyone for OG checker compatibility) */\n botsOnly?: boolean;\n /** Additional bot user agents to detect (used when botsOnly=true) */\n extraBots?: string[];\n}\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n // SEO tools & OG checkers\n \"opengraph\", \"metatags\", \"preview\", \"crawler\", \"spider\", \"fetch\",\n \"curl\", \"wget\", \"python-requests\", \"node-fetch\", \"axios\",\n];\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent || \"Googlebot/2.1\" },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQO,IAAM,iBAAiB;AAEvB,IAAM,oBAAoB;AAe1B,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAAA;AAAA,EAEhE;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EAAU;AAAA,EACzD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAmB;AAAA,EAAc;AACnD;AAEO,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,aAAa,gBAAgB;AAAA,QACtD,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  isBot,
7
7
  isExcluded,
8
8
  isStaticAsset
9
- } from "./chunk-UQC64IMV.mjs";
9
+ } from "./chunk-C43DAMVJ.mjs";
10
10
  export {
11
11
  BOT_USER_AGENTS,
12
12
  RANKDEPLOY_API,
package/dist/netlify.js CHANGED
@@ -20,13 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/netlify.ts
21
21
  var netlify_exports = {};
22
22
  __export(netlify_exports, {
23
- createHandler: () => createHandler,
24
- default: () => netlify_default
23
+ createHandler: () => createHandler
25
24
  });
26
25
  module.exports = __toCommonJS(netlify_exports);
27
26
 
28
27
  // src/index.ts
29
28
  var RANKDEPLOY_API = "https://proxy.unikium.com";
29
+ var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
30
30
  var BOT_USER_AGENTS = [
31
31
  "googlebot",
32
32
  "bingbot",
@@ -57,9 +57,20 @@ var BOT_USER_AGENTS = [
57
57
  "anthropic",
58
58
  "claudebot",
59
59
  "perplexitybot",
60
- "cohere"
60
+ "cohere",
61
+ // SEO tools & OG checkers
62
+ "opengraph",
63
+ "metatags",
64
+ "preview",
65
+ "crawler",
66
+ "spider",
67
+ "fetch",
68
+ "curl",
69
+ "wget",
70
+ "python-requests",
71
+ "node-fetch",
72
+ "axios"
61
73
  ];
62
- var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
63
74
  function isBot(userAgent, extraBots) {
64
75
  const ua = userAgent.toLowerCase();
65
76
  const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
@@ -77,7 +88,7 @@ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API
77
88
  const res = await fetch(
78
89
  `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,
79
90
  {
80
- headers: { "User-Agent": userAgent },
91
+ headers: { "User-Agent": userAgent || "Googlebot/2.1" },
81
92
  signal: AbortSignal.timeout(1e4)
82
93
  }
83
94
  );
@@ -91,20 +102,17 @@ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API
91
102
  }
92
103
 
93
104
  // src/netlify.ts
94
- function createHandler(options = {}) {
105
+ function createHandler(options) {
95
106
  return async function handler(request, context) {
96
107
  const userAgent = request.headers.get("user-agent") || "";
97
108
  const pathname = new URL(request.url).pathname;
98
- if (!isBot(userAgent, options.extraBots)) {
99
- return context.next();
100
- }
101
- if (isStaticAsset(pathname)) {
109
+ if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
102
110
  return context.next();
103
111
  }
104
- if (isExcluded(pathname, options.excludePaths)) {
112
+ if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {
105
113
  return context.next();
106
114
  }
107
- const html = await fetchPrerendered(request.url, userAgent, options.apiUrl);
115
+ const html = await fetchPrerendered(request.url, userAgent, options.siteKey, options.apiUrl);
108
116
  if (html) {
109
117
  return new Response(html, {
110
118
  status: 200,
@@ -118,7 +126,6 @@ function createHandler(options = {}) {
118
126
  return context.next();
119
127
  };
120
128
  }
121
- var netlify_default = createHandler();
122
129
  // Annotate the CommonJS export names for ESM import in node:
123
130
  0 && (module.exports = {
124
131
  createHandler
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/netlify.ts","../src/index.ts"],"sourcesContent":["/**\n * @rankdeploy/middleware/netlify — Netlify Edge Function middleware\n *\n * Usage in netlify/edge-functions/rankdeploy.ts:\n * export { default } from '@rankdeploy/middleware/netlify'\n *\n * Add to netlify.toml:\n * [[edge_functions]]\n * function = \"rankdeploy\"\n * path = \"/*\"\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NetlifyContext {\n next(): Promise<Response>;\n}\n\n/**\n * Create a Netlify Edge Function handler with custom options.\n */\nexport function createHandler(options: RankDeployOptions = {}) {\n return async function handler(request: Request, context: NetlifyContext) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = new URL(request.url).pathname;\n\n if (!isBot(userAgent, options.extraBots)) {\n return context.next();\n }\n\n if (isStaticAsset(pathname)) {\n return context.next();\n }\n\n if (isExcluded(pathname, options.excludePaths)) {\n return context.next();\n }\n\n const html = await fetchPrerendered(request.url, userAgent, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return context.next();\n };\n}\n\n/**\n * Default handler — ready to use as-is.\n */\nexport default createHandler();\n","/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Detects search engine bots and serves pre-rendered HTML from Rank Deploy API.\n * Human visitors are not affected.\n *\n * Usage:\n * import { createMiddleware } from 'rankdeploy-middleware/next'\n * export const middleware = createMiddleware({ siteKey: 'your-site-key' })\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n];\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Additional bot user agents to detect */\n extraBots?: string[];\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n}\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWO,IAAM,iBAAiB;AAEvB,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAClE;AAEO,IAAM,oBAAoB;AAa1B,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADtDO,SAAS,cAAc,UAA6B,CAAC,GAAG;AAC7D,SAAO,eAAe,QAAQ,SAAkB,SAAyB;AACvE,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,IAAI,IAAI,QAAQ,GAAG,EAAE;AAEtC,QAAI,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACxC,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,QAAI,cAAc,QAAQ,GAAG;AAC3B,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,QAAI,WAAW,UAAU,QAAQ,YAAY,GAAG;AAC9C,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,KAAK,WAAW,QAAQ,MAAM;AAE1E,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;AAKA,IAAO,kBAAQ,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/netlify.ts","../src/index.ts"],"sourcesContent":["/**\n * rankdeploy-middleware/netlify — Netlify Edge Function middleware\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NetlifyContext {\n next(): Promise<Response>;\n}\n\nexport function createHandler(options: RankDeployOptions) {\n return async function handler(request: Request, context: NetlifyContext) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = new URL(request.url).pathname;\n\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return context.next();\n }\n\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return context.next();\n }\n\n const html = await fetchPrerendered(request.url, userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return context.next();\n };\n}\n","/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Serves pre-rendered HTML with SEO overrides to all visitors.\n * The pre-rendered HTML is visually identical to the original page\n * but includes injected meta tags (title, description, canonical, og:tags).\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n /** Only serve to bots (default: false — serves to everyone for OG checker compatibility) */\n botsOnly?: boolean;\n /** Additional bot user agents to detect (used when botsOnly=true) */\n extraBots?: string[];\n}\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n // SEO tools & OG checkers\n \"opengraph\", \"metatags\", \"preview\", \"crawler\", \"spider\", \"fetch\",\n \"curl\", \"wget\", \"python-requests\", \"node-fetch\", \"axios\",\n];\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent || \"Googlebot/2.1\" },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQO,IAAM,iBAAiB;AAEvB,IAAM,oBAAoB;AAe1B,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAAA;AAAA,EAEhE;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EAAU;AAAA,EACzD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAmB;AAAA,EAAc;AACnD;AAEO,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,aAAa,gBAAgB;AAAA,QACtD,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADnEO,SAAS,cAAc,SAA4B;AACxD,SAAO,eAAe,QAAQ,SAAkB,SAAyB;AACvE,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,IAAI,IAAI,QAAQ,GAAG,EAAE;AAEtC,QAAI,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACzE,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,QAAI,QAAQ,aAAa,SAAS,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACtE,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,KAAK,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAE3F,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;","names":[]}
package/dist/netlify.mjs CHANGED
@@ -3,23 +3,20 @@ import {
3
3
  isBot,
4
4
  isExcluded,
5
5
  isStaticAsset
6
- } from "./chunk-UQC64IMV.mjs";
6
+ } from "./chunk-C43DAMVJ.mjs";
7
7
 
8
8
  // src/netlify.ts
9
- function createHandler(options = {}) {
9
+ function createHandler(options) {
10
10
  return async function handler(request, context) {
11
11
  const userAgent = request.headers.get("user-agent") || "";
12
12
  const pathname = new URL(request.url).pathname;
13
- if (!isBot(userAgent, options.extraBots)) {
13
+ if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
14
14
  return context.next();
15
15
  }
16
- if (isStaticAsset(pathname)) {
16
+ if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {
17
17
  return context.next();
18
18
  }
19
- if (isExcluded(pathname, options.excludePaths)) {
20
- return context.next();
21
- }
22
- const html = await fetchPrerendered(request.url, userAgent, options.apiUrl);
19
+ const html = await fetchPrerendered(request.url, userAgent, options.siteKey, options.apiUrl);
23
20
  if (html) {
24
21
  return new Response(html, {
25
22
  status: 200,
@@ -33,9 +30,7 @@ function createHandler(options = {}) {
33
30
  return context.next();
34
31
  };
35
32
  }
36
- var netlify_default = createHandler();
37
33
  export {
38
- createHandler,
39
- netlify_default as default
34
+ createHandler
40
35
  };
41
36
  //# sourceMappingURL=netlify.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/netlify.ts"],"sourcesContent":["/**\n * @rankdeploy/middleware/netlify — Netlify Edge Function middleware\n *\n * Usage in netlify/edge-functions/rankdeploy.ts:\n * export { default } from '@rankdeploy/middleware/netlify'\n *\n * Add to netlify.toml:\n * [[edge_functions]]\n * function = \"rankdeploy\"\n * path = \"/*\"\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NetlifyContext {\n next(): Promise<Response>;\n}\n\n/**\n * Create a Netlify Edge Function handler with custom options.\n */\nexport function createHandler(options: RankDeployOptions = {}) {\n return async function handler(request: Request, context: NetlifyContext) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = new URL(request.url).pathname;\n\n if (!isBot(userAgent, options.extraBots)) {\n return context.next();\n }\n\n if (isStaticAsset(pathname)) {\n return context.next();\n }\n\n if (isExcluded(pathname, options.excludePaths)) {\n return context.next();\n }\n\n const html = await fetchPrerendered(request.url, userAgent, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return context.next();\n };\n}\n\n/**\n * Default handler — ready to use as-is.\n */\nexport default createHandler();\n"],"mappings":";;;;;;;;AAqBO,SAAS,cAAc,UAA6B,CAAC,GAAG;AAC7D,SAAO,eAAe,QAAQ,SAAkB,SAAyB;AACvE,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,IAAI,IAAI,QAAQ,GAAG,EAAE;AAEtC,QAAI,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACxC,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,QAAI,cAAc,QAAQ,GAAG;AAC3B,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,QAAI,WAAW,UAAU,QAAQ,YAAY,GAAG;AAC9C,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,KAAK,WAAW,QAAQ,MAAM;AAE1E,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;AAKA,IAAO,kBAAQ,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/netlify.ts"],"sourcesContent":["/**\n * rankdeploy-middleware/netlify — Netlify Edge Function middleware\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NetlifyContext {\n next(): Promise<Response>;\n}\n\nexport function createHandler(options: RankDeployOptions) {\n return async function handler(request: Request, context: NetlifyContext) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = new URL(request.url).pathname;\n\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return context.next();\n }\n\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return context.next();\n }\n\n const html = await fetchPrerendered(request.url, userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return context.next();\n };\n}\n"],"mappings":";;;;;;;;AAUO,SAAS,cAAc,SAA4B;AACxD,SAAO,eAAe,QAAQ,SAAkB,SAAyB;AACvE,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,IAAI,IAAI,QAAQ,GAAG,EAAE;AAEtC,QAAI,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACzE,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,QAAI,QAAQ,aAAa,SAAS,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACtE,aAAO,QAAQ,KAAK;AAAA,IACtB;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,KAAK,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAE3F,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;","names":[]}
package/dist/next.js CHANGED
@@ -27,6 +27,7 @@ module.exports = __toCommonJS(next_exports);
27
27
 
28
28
  // src/index.ts
29
29
  var RANKDEPLOY_API = "https://proxy.unikium.com";
30
+ var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
30
31
  var BOT_USER_AGENTS = [
31
32
  "googlebot",
32
33
  "bingbot",
@@ -57,9 +58,20 @@ var BOT_USER_AGENTS = [
57
58
  "anthropic",
58
59
  "claudebot",
59
60
  "perplexitybot",
60
- "cohere"
61
+ "cohere",
62
+ // SEO tools & OG checkers
63
+ "opengraph",
64
+ "metatags",
65
+ "preview",
66
+ "crawler",
67
+ "spider",
68
+ "fetch",
69
+ "curl",
70
+ "wget",
71
+ "python-requests",
72
+ "node-fetch",
73
+ "axios"
61
74
  ];
62
- var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
63
75
  function isBot(userAgent, extraBots) {
64
76
  const ua = userAgent.toLowerCase();
65
77
  const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
@@ -77,7 +89,7 @@ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API
77
89
  const res = await fetch(
78
90
  `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,
79
91
  {
80
- headers: { "User-Agent": userAgent },
92
+ headers: { "User-Agent": userAgent || "Googlebot/2.1" },
81
93
  signal: AbortSignal.timeout(1e4)
82
94
  }
83
95
  );
@@ -95,7 +107,10 @@ function createMiddleware(options) {
95
107
  return async function middleware(request) {
96
108
  const userAgent = request.headers.get("user-agent") || "";
97
109
  const pathname = request.nextUrl.pathname;
98
- if (!isBot(userAgent, options.extraBots) || isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
110
+ if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
111
+ return;
112
+ }
113
+ if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {
99
114
  return;
100
115
  }
101
116
  const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);
package/dist/next.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/next.ts","../src/index.ts"],"sourcesContent":["/**\n * rankdeploy-middleware/next — Next.js / Vercel middleware\n *\n * Usage:\n * import { createMiddleware } from 'rankdeploy-middleware/next'\n * export const middleware = createMiddleware({ siteKey: 'your-site-key' })\n * export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'] }\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NextMiddlewareRequest {\n headers: { get(name: string): string | null };\n nextUrl: { pathname: string; toString(): string };\n}\n\nexport function createMiddleware(options: RankDeployOptions) {\n return async function middleware(request: NextMiddlewareRequest) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = request.nextUrl.pathname;\n\n if (!isBot(userAgent, options.extraBots) || isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return;\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return;\n };\n}\n\nexport const config = {\n matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n","/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Detects search engine bots and serves pre-rendered HTML from Rank Deploy API.\n * Human visitors are not affected.\n *\n * Usage:\n * import { createMiddleware } from 'rankdeploy-middleware/next'\n * export const middleware = createMiddleware({ siteKey: 'your-site-key' })\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n];\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Additional bot user agents to detect */\n extraBots?: string[];\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n}\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWO,IAAM,iBAAiB;AAEvB,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAClE;AAEO,IAAM,oBAAoB;AAa1B,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AD3DO,SAAS,iBAAiB,SAA4B;AAC3D,SAAO,eAAe,WAAW,SAAgC;AAC/D,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,QAAQ,QAAQ;AAEjC,QAAI,CAAC,MAAM,WAAW,QAAQ,SAAS,KAAK,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACjH;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,QAAQ,SAAS,GAAG,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAE1G,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA;AAAA,EACF;AACF;AAEO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,mDAAmD;AAC/D;","names":[]}
1
+ {"version":3,"sources":["../src/next.ts","../src/index.ts"],"sourcesContent":["/**\n * rankdeploy-middleware/next — Next.js / Vercel middleware\n *\n * Serves pre-rendered HTML with SEO overrides.\n * By default serves to ALL requests (so OG checkers work).\n * Set botsOnly: true to only serve to search engine bots.\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NextMiddlewareRequest {\n headers: { get(name: string): string | null };\n nextUrl: { pathname: string; toString(): string };\n}\n\nexport function createMiddleware(options: RankDeployOptions) {\n return async function middleware(request: NextMiddlewareRequest) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = request.nextUrl.pathname;\n\n // Skip static assets and excluded paths\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return;\n }\n\n // Only intercept bot requests (default behavior)\n // Set botsOnly: false to serve to everyone (not recommended — can cause loops)\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return;\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return;\n };\n}\n\nexport const config = {\n matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n","/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Serves pre-rendered HTML with SEO overrides to all visitors.\n * The pre-rendered HTML is visually identical to the original page\n * but includes injected meta tags (title, description, canonical, og:tags).\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n /** Only serve to bots (default: false — serves to everyone for OG checker compatibility) */\n botsOnly?: boolean;\n /** Additional bot user agents to detect (used when botsOnly=true) */\n extraBots?: string[];\n}\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n // SEO tools & OG checkers\n \"opengraph\", \"metatags\", \"preview\", \"crawler\", \"spider\", \"fetch\",\n \"curl\", \"wget\", \"python-requests\", \"node-fetch\", \"axios\",\n];\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent || \"Googlebot/2.1\" },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQO,IAAM,iBAAiB;AAEvB,IAAM,oBAAoB;AAe1B,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAAA;AAAA,EAEhE;AAAA,EAAa;AAAA,EAAY;AAAA,EAAW;AAAA,EAAW;AAAA,EAAU;AAAA,EACzD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAmB;AAAA,EAAc;AACnD;AAEO,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,aAAa,gBAAgB;AAAA,QACtD,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AD9DO,SAAS,iBAAiB,SAA4B;AAC3D,SAAO,eAAe,WAAW,SAAgC;AAC/D,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,QAAQ,QAAQ;AAGjC,QAAI,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACzE;AAAA,IACF;AAIA,QAAI,QAAQ,aAAa,SAAS,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACtE;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,QAAQ,SAAS,GAAG,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAE1G,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA;AAAA,EACF;AACF;AAEO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,mDAAmD;AAC/D;","names":[]}
package/dist/next.mjs CHANGED
@@ -3,14 +3,17 @@ import {
3
3
  isBot,
4
4
  isExcluded,
5
5
  isStaticAsset
6
- } from "./chunk-UQC64IMV.mjs";
6
+ } from "./chunk-C43DAMVJ.mjs";
7
7
 
8
8
  // src/next.ts
9
9
  function createMiddleware(options) {
10
10
  return async function middleware(request) {
11
11
  const userAgent = request.headers.get("user-agent") || "";
12
12
  const pathname = request.nextUrl.pathname;
13
- if (!isBot(userAgent, options.extraBots) || isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
13
+ if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
14
+ return;
15
+ }
16
+ if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {
14
17
  return;
15
18
  }
16
19
  const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);
package/dist/next.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/next.ts"],"sourcesContent":["/**\n * rankdeploy-middleware/next — Next.js / Vercel middleware\n *\n * Usage:\n * import { createMiddleware } from 'rankdeploy-middleware/next'\n * export const middleware = createMiddleware({ siteKey: 'your-site-key' })\n * export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'] }\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NextMiddlewareRequest {\n headers: { get(name: string): string | null };\n nextUrl: { pathname: string; toString(): string };\n}\n\nexport function createMiddleware(options: RankDeployOptions) {\n return async function middleware(request: NextMiddlewareRequest) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = request.nextUrl.pathname;\n\n if (!isBot(userAgent, options.extraBots) || isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return;\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return;\n };\n}\n\nexport const config = {\n matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n"],"mappings":";;;;;;;;AAgBO,SAAS,iBAAiB,SAA4B;AAC3D,SAAO,eAAe,WAAW,SAAgC;AAC/D,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,QAAQ,QAAQ;AAEjC,QAAI,CAAC,MAAM,WAAW,QAAQ,SAAS,KAAK,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACjH;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,QAAQ,SAAS,GAAG,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAE1G,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA;AAAA,EACF;AACF;AAEO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,mDAAmD;AAC/D;","names":[]}
1
+ {"version":3,"sources":["../src/next.ts"],"sourcesContent":["/**\n * rankdeploy-middleware/next — Next.js / Vercel middleware\n *\n * Serves pre-rendered HTML with SEO overrides.\n * By default serves to ALL requests (so OG checkers work).\n * Set botsOnly: true to only serve to search engine bots.\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NextMiddlewareRequest {\n headers: { get(name: string): string | null };\n nextUrl: { pathname: string; toString(): string };\n}\n\nexport function createMiddleware(options: RankDeployOptions) {\n return async function middleware(request: NextMiddlewareRequest) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = request.nextUrl.pathname;\n\n // Skip static assets and excluded paths\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return;\n }\n\n // Only intercept bot requests (default behavior)\n // Set botsOnly: false to serve to everyone (not recommended — can cause loops)\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return;\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return;\n };\n}\n\nexport const config = {\n matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n"],"mappings":";;;;;;;;AAeO,SAAS,iBAAiB,SAA4B;AAC3D,SAAO,eAAe,WAAW,SAAgC;AAC/D,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,QAAQ,QAAQ;AAGjC,QAAI,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACzE;AAAA,IACF;AAIA,QAAI,QAAQ,aAAa,SAAS,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACtE;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,QAAQ,SAAS,GAAG,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAE1G,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA;AAAA,EACF;AACF;AAEO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,mDAAmD;AAC/D;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rankdeploy-middleware",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "SEO middleware for Rank Deploy — serves pre-rendered HTML to search engine bots",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Detects search engine bots and serves pre-rendered HTML from Rank Deploy API.\n * Human visitors are not affected.\n *\n * Usage:\n * import { createMiddleware } from 'rankdeploy-middleware/next'\n * export const middleware = createMiddleware({ siteKey: 'your-site-key' })\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n];\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Additional bot user agents to detect */\n extraBots?: string[];\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n}\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000),\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":";AAWO,IAAM,iBAAiB;AAEvB,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAa;AAAA,EAAW;AAAA,EAAa;AAAA,EAAe;AAAA,EACpD;AAAA,EAAS;AAAA,EAAS;AAAA,EAClB;AAAA,EAAW;AAAA,EAAuB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAY;AAAA,EAAe;AAAA,EAAY;AAAA,EAAc;AAAA,EAAY;AAAA,EACjE;AAAA,EAAkB;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAC9D;AAAA,EAAU;AAAA,EAAW;AAAA,EAAa;AAAA,EAAa;AAAA,EAAiB;AAClE;AAEO,IAAM,oBAAoB;AAa1B,SAAS,MAAM,WAAmB,WAA+B;AACtE,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,GAAG,SAAS,IAAI;AACjE,SAAO,QAAQ,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAG,CAAC;AAC/C;AAEO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAEO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAKA,eAAsB,iBACpB,KACA,WACA,SACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC,QAAQ,mBAAmB,OAAO,CAAC;AAAA,MAClF;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}