rankdeploy-middleware 1.0.0 → 1.2.0

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;
@@ -45,14 +57,13 @@ function isExcluded(pathname, excludePaths) {
45
57
  if (!excludePaths) return false;
46
58
  return excludePaths.some((p) => pathname.startsWith(p));
47
59
  }
48
- async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
60
+ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API) {
49
61
  try {
50
62
  const res = await fetch(
51
- `${apiUrl}/render?url=${encodeURIComponent(url)}`,
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
- // 10s timeout
56
67
  }
57
68
  );
58
69
  if (res.status === 200) {
@@ -66,11 +77,11 @@ async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
66
77
 
67
78
  export {
68
79
  RANKDEPLOY_API,
69
- BOT_USER_AGENTS,
70
80
  STATIC_EXTENSIONS,
81
+ BOT_USER_AGENTS,
71
82
  isBot,
72
83
  isStaticAsset,
73
84
  isExcluded,
74
85
  fetchPrerendered
75
86
  };
76
- //# sourceMappingURL=chunk-FPOJYG2E.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;
@@ -72,14 +84,13 @@ function isExcluded(pathname, excludePaths) {
72
84
  if (!excludePaths) return false;
73
85
  return excludePaths.some((p) => pathname.startsWith(p));
74
86
  }
75
- async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
87
+ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API) {
76
88
  try {
77
89
  const res = await fetch(
78
- `${apiUrl}/render?url=${encodeURIComponent(url)}`,
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
- // 10s timeout
83
94
  }
84
95
  );
85
96
  if (res.status === 200) {
@@ -92,23 +103,20 @@ async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
92
103
  }
93
104
 
94
105
  // src/express.ts
95
- function rankdeploy(options = {}) {
106
+ function rankdeploy(options) {
96
107
  return async function middleware(req, res, next) {
97
108
  const userAgent = req.headers["user-agent"] || req.get?.("user-agent") || "";
98
109
  const pathname = req.url.split("?")[0];
99
- if (!isBot(userAgent, options.extraBots)) {
100
- return next();
101
- }
102
- if (isStaticAsset(pathname)) {
110
+ if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
103
111
  return next();
104
112
  }
105
- if (isExcluded(pathname, options.excludePaths)) {
113
+ if (options.botsOnly && !isBot(userAgent, options.extraBots)) {
106
114
  return next();
107
115
  }
108
116
  const protocol = req.protocol || "https";
109
117
  const host = req.get?.("host") || req.headers.host || "";
110
118
  const fullUrl = `${protocol}://${host}${req.url}`;
111
- const html = await fetchPrerendered(fullUrl, userAgent, options.apiUrl);
119
+ const html = await fetchPrerendered(fullUrl, userAgent, options.siteKey, options.apiUrl);
112
120
  if (html) {
113
121
  res.set("Content-Type", "text/html; charset=utf-8");
114
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 { middleware } from '@rankdeploy/middleware/next' // Next.js\n * import { default } from '@rankdeploy/middleware/netlify' // Netlify\n * import { rankdeploy } from '@rankdeploy/middleware/express' // Express\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 /** 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\n/**\n * Check if a user agent string belongs to a bot.\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\n/**\n * Check if a path is a static asset.\n */\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\n/**\n * Check if a path should be excluded.\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 * Returns the HTML string if available, null otherwise.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000), // 10s timeout\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n\n // 204 = passthrough (no cache), anything else = error\n return null;\n } catch {\n // Network error, timeout — fail silently\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACYO,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;AAc1B,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;AAKO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAKO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAMA,eAAsB,iBACpB,KACA,WACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC;AAAA,MAC/C;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AAGA,WAAO;AAAA,EACT,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;;;ADpDO,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 && !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,YAAY,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AAC5D,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-FPOJYG2E.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 && !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 && !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,YAAY,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AAC5D,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;
@@ -75,14 +87,13 @@ function isExcluded(pathname, excludePaths) {
75
87
  if (!excludePaths) return false;
76
88
  return excludePaths.some((p) => pathname.startsWith(p));
77
89
  }
78
- async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
90
+ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API) {
79
91
  try {
80
92
  const res = await fetch(
81
- `${apiUrl}/render?url=${encodeURIComponent(url)}`,
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
- // 10s timeout
86
97
  }
87
98
  );
88
99
  if (res.status === 200) {
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 { middleware } from '@rankdeploy/middleware/next' // Next.js\n * import { default } from '@rankdeploy/middleware/netlify' // Netlify\n * import { rankdeploy } from '@rankdeploy/middleware/express' // Express\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 /** 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\n/**\n * Check if a user agent string belongs to a bot.\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\n/**\n * Check if a path is a static asset.\n */\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\n/**\n * Check if a path should be excluded.\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 * Returns the HTML string if available, null otherwise.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000), // 10s timeout\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n\n // 204 = passthrough (no cache), anything else = error\n return null;\n } catch {\n // Network error, timeout — fail silently\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYO,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;AAc1B,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;AAKO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAKO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAMA,eAAsB,iBACpB,KACA,WACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC;AAAA,MAC/C;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AAGA,WAAO;AAAA,EACT,QAAQ;AAEN,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-FPOJYG2E.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;
@@ -72,14 +83,13 @@ function isExcluded(pathname, excludePaths) {
72
83
  if (!excludePaths) return false;
73
84
  return excludePaths.some((p) => pathname.startsWith(p));
74
85
  }
75
- async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
86
+ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API) {
76
87
  try {
77
88
  const res = await fetch(
78
- `${apiUrl}/render?url=${encodeURIComponent(url)}`,
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
- // 10s timeout
83
93
  }
84
94
  );
85
95
  if (res.status === 200) {
@@ -92,20 +102,17 @@ async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
92
102
  }
93
103
 
94
104
  // src/netlify.ts
95
- function createHandler(options = {}) {
105
+ function createHandler(options) {
96
106
  return async function handler(request, context) {
97
107
  const userAgent = request.headers.get("user-agent") || "";
98
108
  const pathname = new URL(request.url).pathname;
99
- if (!isBot(userAgent, options.extraBots)) {
100
- return context.next();
101
- }
102
- if (isStaticAsset(pathname)) {
109
+ if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
103
110
  return context.next();
104
111
  }
105
- if (isExcluded(pathname, options.excludePaths)) {
112
+ if (options.botsOnly && !isBot(userAgent, options.extraBots)) {
106
113
  return context.next();
107
114
  }
108
- const html = await fetchPrerendered(request.url, userAgent, options.apiUrl);
115
+ const html = await fetchPrerendered(request.url, userAgent, options.siteKey, options.apiUrl);
109
116
  if (html) {
110
117
  return new Response(html, {
111
118
  status: 200,
@@ -119,7 +126,6 @@ function createHandler(options = {}) {
119
126
  return context.next();
120
127
  };
121
128
  }
122
- var netlify_default = createHandler();
123
129
  // Annotate the CommonJS export names for ESM import in node:
124
130
  0 && (module.exports = {
125
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 { middleware } from '@rankdeploy/middleware/next' // Next.js\n * import { default } from '@rankdeploy/middleware/netlify' // Netlify\n * import { rankdeploy } from '@rankdeploy/middleware/express' // Express\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 /** 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\n/**\n * Check if a user agent string belongs to a bot.\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\n/**\n * Check if a path is a static asset.\n */\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\n/**\n * Check if a path should be excluded.\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 * Returns the HTML string if available, null otherwise.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000), // 10s timeout\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n\n // 204 = passthrough (no cache), anything else = error\n return null;\n } catch {\n // Network error, timeout — fail silently\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACYO,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;AAc1B,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;AAKO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAKO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAMA,eAAsB,iBACpB,KACA,WACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC;AAAA,MAC/C;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AAGA,WAAO;AAAA,EACT,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;;;ADjEO,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 && !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,YAAY,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AAC5D,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-FPOJYG2E.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 && !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 && !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,YAAY,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AAC5D,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
@@ -21,13 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var next_exports = {};
22
22
  __export(next_exports, {
23
23
  config: () => config,
24
- createMiddleware: () => createMiddleware,
25
- middleware: () => middleware
24
+ createMiddleware: () => createMiddleware
26
25
  });
27
26
  module.exports = __toCommonJS(next_exports);
28
27
 
29
28
  // src/index.ts
30
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;
31
31
  var BOT_USER_AGENTS = [
32
32
  "googlebot",
33
33
  "bingbot",
@@ -58,9 +58,20 @@ var BOT_USER_AGENTS = [
58
58
  "anthropic",
59
59
  "claudebot",
60
60
  "perplexitybot",
61
- "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"
62
74
  ];
63
- var STATIC_EXTENSIONS = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;
64
75
  function isBot(userAgent, extraBots) {
65
76
  const ua = userAgent.toLowerCase();
66
77
  const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
@@ -73,14 +84,13 @@ function isExcluded(pathname, excludePaths) {
73
84
  if (!excludePaths) return false;
74
85
  return excludePaths.some((p) => pathname.startsWith(p));
75
86
  }
76
- async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
87
+ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API) {
77
88
  try {
78
89
  const res = await fetch(
79
- `${apiUrl}/render?url=${encodeURIComponent(url)}`,
90
+ `${apiUrl}/render?url=${encodeURIComponent(url)}&key=${encodeURIComponent(siteKey)}`,
80
91
  {
81
- headers: { "User-Agent": userAgent },
92
+ headers: { "User-Agent": userAgent || "Googlebot/2.1" },
82
93
  signal: AbortSignal.timeout(1e4)
83
- // 10s timeout
84
94
  }
85
95
  );
86
96
  if (res.status === 200) {
@@ -93,14 +103,17 @@ async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
93
103
  }
94
104
 
95
105
  // src/next.ts
96
- function createMiddleware(options = {}) {
97
- return async function middleware2(request) {
106
+ function createMiddleware(options) {
107
+ return async function middleware(request) {
98
108
  const userAgent = request.headers.get("user-agent") || "";
99
109
  const pathname = request.nextUrl.pathname;
100
- 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 && !isBot(userAgent, options.extraBots)) {
101
114
  return;
102
115
  }
103
- const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.apiUrl);
116
+ const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);
104
117
  if (html) {
105
118
  return new Response(html, {
106
119
  status: 200,
@@ -114,14 +127,12 @@ function createMiddleware(options = {}) {
114
127
  return;
115
128
  };
116
129
  }
117
- var middleware = createMiddleware();
118
130
  var config = {
119
131
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"]
120
132
  };
121
133
  // Annotate the CommonJS export names for ESM import in node:
122
134
  0 && (module.exports = {
123
135
  config,
124
- createMiddleware,
125
- middleware
136
+ createMiddleware
126
137
  });
127
138
  //# sourceMappingURL=next.js.map
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 in middleware.ts:\n * export { middleware, config } from '@rankdeploy/middleware/next'\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\n/**\n * Create a Next.js middleware function with custom options.\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 non-bot, static assets, excluded paths\n if (!isBot(userAgent, options.extraBots) || isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return; // undefined = next() in Next.js middleware\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), 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 // No pre-rendered version — let Next.js handle normally\n return;\n };\n}\n\n/**\n * Default middleware — ready to use.\n */\nexport const middleware = createMiddleware();\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 { middleware } from '@rankdeploy/middleware/next' // Next.js\n * import { default } from '@rankdeploy/middleware/netlify' // Netlify\n * import { rankdeploy } from '@rankdeploy/middleware/express' // Express\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 /** 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\n/**\n * Check if a user agent string belongs to a bot.\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\n/**\n * Check if a path is a static asset.\n */\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\n/**\n * Check if a path should be excluded.\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 * Returns the HTML string if available, null otherwise.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000), // 10s timeout\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n\n // 204 = passthrough (no cache), anything else = error\n return null;\n } catch {\n // Network error, timeout — fail silently\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACYO,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;AAc1B,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;AAKO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAKO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAMA,eAAsB,iBACpB,KACA,WACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC;AAAA,MAC/C;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AAGA,WAAO;AAAA,EACT,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;;;ADrEO,SAAS,iBAAiB,UAA6B,CAAC,GAAG;AAChE,SAAO,eAAeA,YAAW,SAAgC;AAC/D,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,QAAQ,QAAQ;AAGjC,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,MAAM;AAEzF,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;AAGA;AAAA,EACF;AACF;AAKO,IAAM,aAAa,iBAAiB;AAEpC,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,mDAAmD;AAC/D;","names":["middleware"]}
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 // If botsOnly mode, only intercept bot requests\n if (options.botsOnly && !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;AAGA,QAAI,QAAQ,YAAY,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AAC5D;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,17 +3,20 @@ import {
3
3
  isBot,
4
4
  isExcluded,
5
5
  isStaticAsset
6
- } from "./chunk-FPOJYG2E.mjs";
6
+ } from "./chunk-C43DAMVJ.mjs";
7
7
 
8
8
  // src/next.ts
9
- function createMiddleware(options = {}) {
10
- return async function middleware2(request) {
9
+ function createMiddleware(options) {
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
14
  return;
15
15
  }
16
- const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.apiUrl);
16
+ if (options.botsOnly && !isBot(userAgent, options.extraBots)) {
17
+ return;
18
+ }
19
+ const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);
17
20
  if (html) {
18
21
  return new Response(html, {
19
22
  status: 200,
@@ -27,13 +30,11 @@ function createMiddleware(options = {}) {
27
30
  return;
28
31
  };
29
32
  }
30
- var middleware = createMiddleware();
31
33
  var config = {
32
34
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"]
33
35
  };
34
36
  export {
35
37
  config,
36
- createMiddleware,
37
- middleware
38
+ createMiddleware
38
39
  };
39
40
  //# sourceMappingURL=next.mjs.map
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 in middleware.ts:\n * export { middleware, config } from '@rankdeploy/middleware/next'\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\n/**\n * Create a Next.js middleware function with custom options.\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 non-bot, static assets, excluded paths\n if (!isBot(userAgent, options.extraBots) || isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return; // undefined = next() in Next.js middleware\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), 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 // No pre-rendered version — let Next.js handle normally\n return;\n };\n}\n\n/**\n * Default middleware — ready to use.\n */\nexport const middleware = createMiddleware();\n\nexport const config = {\n matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n"],"mappings":";;;;;;;;AAiBO,SAAS,iBAAiB,UAA6B,CAAC,GAAG;AAChE,SAAO,eAAeA,YAAW,SAAgC;AAC/D,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,QAAQ,QAAQ;AAGjC,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,MAAM;AAEzF,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;AAGA;AAAA,EACF;AACF;AAKO,IAAM,aAAa,iBAAiB;AAEpC,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,mDAAmD;AAC/D;","names":["middleware"]}
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 // If botsOnly mode, only intercept bot requests\n if (options.botsOnly && !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;AAGA,QAAI,QAAQ,YAAY,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AAC5D;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.0.0",
3
+ "version": "1.2.0",
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",
@@ -27,12 +27,24 @@
27
27
  "types": "./dist/express.d.ts"
28
28
  }
29
29
  },
30
- "files": ["dist", "README.md"],
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
31
34
  "scripts": {
32
35
  "build": "tsup",
33
36
  "prepublishOnly": "npm run build"
34
37
  },
35
- "keywords": ["seo", "prerender", "middleware", "nextjs", "netlify", "express", "rankdeploy", "googlebot"],
38
+ "keywords": [
39
+ "seo",
40
+ "prerender",
41
+ "middleware",
42
+ "nextjs",
43
+ "netlify",
44
+ "express",
45
+ "rankdeploy",
46
+ "googlebot"
47
+ ],
36
48
  "author": "Rank Deploy",
37
49
  "license": "MIT",
38
50
  "devDependencies": {
@@ -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 { middleware } from '@rankdeploy/middleware/next' // Next.js\n * import { default } from '@rankdeploy/middleware/netlify' // Netlify\n * import { rankdeploy } from '@rankdeploy/middleware/express' // Express\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 /** 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\n/**\n * Check if a user agent string belongs to a bot.\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\n/**\n * Check if a path is a static asset.\n */\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\n/**\n * Check if a path should be excluded.\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 * Returns the HTML string if available, null otherwise.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const res = await fetch(\n `${apiUrl}/render?url=${encodeURIComponent(url)}`,\n {\n headers: { \"User-Agent\": userAgent },\n signal: AbortSignal.timeout(10_000), // 10s timeout\n },\n );\n\n if (res.status === 200) {\n return res.text();\n }\n\n // 204 = passthrough (no cache), anything else = error\n return null;\n } catch {\n // Network error, timeout — fail silently\n return null;\n }\n}\n"],"mappings":";AAYO,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;AAc1B,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;AAKO,SAAS,cAAc,UAA2B;AACvD,SAAO,kBAAkB,KAAK,QAAQ;AACxC;AAKO,SAAS,WAAW,UAAkB,cAAkC;AAC7E,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,KAAK,CAAC,MAAM,SAAS,WAAW,CAAC,CAAC;AACxD;AAMA,eAAsB,iBACpB,KACA,WACA,SAAS,gBACe;AACxB,MAAI;AACF,UAAM,MAAM,MAAM;AAAA,MAChB,GAAG,MAAM,eAAe,mBAAmB,GAAG,CAAC;AAAA,MAC/C;AAAA,QACE,SAAS,EAAE,cAAc,UAAU;AAAA,QACnC,QAAQ,YAAY,QAAQ,GAAM;AAAA;AAAA,MACpC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AAGA,WAAO;AAAA,EACT,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -1,34 +0,0 @@
1
- import { RankDeployOptions } from './index.mjs';
2
-
3
- /**
4
- * @rankdeploy/middleware/express — Express.js middleware
5
- *
6
- * Usage:
7
- * import { rankdeploy } from '@rankdeploy/middleware/express'
8
- * app.use(rankdeploy())
9
- *
10
- * With options:
11
- * app.use(rankdeploy({ excludePaths: ['/api', '/admin'] }))
12
- */
13
-
14
- interface ExpressRequest {
15
- headers: Record<string, string | string[] | undefined>;
16
- url: string;
17
- protocol: string;
18
- get(name: string): string | undefined;
19
- }
20
- interface ExpressResponse {
21
- set(name: string, value: string): void;
22
- status(code: number): ExpressResponse;
23
- send(body: string): void;
24
- }
25
- type NextFunction = () => void;
26
- /**
27
- * Create Express middleware for Rank Deploy.
28
- *
29
- * Usage:
30
- * app.use(rankdeploy())
31
- */
32
- declare function rankdeploy(options?: RankDeployOptions): (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => Promise<void>;
33
-
34
- export { rankdeploy as default, rankdeploy };
package/dist/express.d.ts DELETED
@@ -1,34 +0,0 @@
1
- import { RankDeployOptions } from './index.js';
2
-
3
- /**
4
- * @rankdeploy/middleware/express — Express.js middleware
5
- *
6
- * Usage:
7
- * import { rankdeploy } from '@rankdeploy/middleware/express'
8
- * app.use(rankdeploy())
9
- *
10
- * With options:
11
- * app.use(rankdeploy({ excludePaths: ['/api', '/admin'] }))
12
- */
13
-
14
- interface ExpressRequest {
15
- headers: Record<string, string | string[] | undefined>;
16
- url: string;
17
- protocol: string;
18
- get(name: string): string | undefined;
19
- }
20
- interface ExpressResponse {
21
- set(name: string, value: string): void;
22
- status(code: number): ExpressResponse;
23
- send(body: string): void;
24
- }
25
- type NextFunction = () => void;
26
- /**
27
- * Create Express middleware for Rank Deploy.
28
- *
29
- * Usage:
30
- * app.use(rankdeploy())
31
- */
32
- declare function rankdeploy(options?: RankDeployOptions): (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => Promise<void>;
33
-
34
- export { rankdeploy as default, rankdeploy };
package/dist/index.d.mts DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * @rankdeploy/middleware — Core SEO middleware engine
3
- *
4
- * Detects search engine bots and serves pre-rendered HTML from Rank Deploy API.
5
- * Human visitors are not affected.
6
- *
7
- * Usage:
8
- * import { middleware } from '@rankdeploy/middleware/next' // Next.js
9
- * import { default } from '@rankdeploy/middleware/netlify' // Netlify
10
- * import { rankdeploy } from '@rankdeploy/middleware/express' // Express
11
- */
12
- declare const RANKDEPLOY_API = "https://proxy.unikium.com";
13
- declare const BOT_USER_AGENTS: string[];
14
- declare const STATIC_EXTENSIONS: RegExp;
15
- interface RankDeployOptions {
16
- /** Rank Deploy API URL. Default: https://proxy.unikium.com */
17
- apiUrl?: string;
18
- /** Additional bot user agents to detect */
19
- extraBots?: string[];
20
- /** Paths to exclude from pre-rendering */
21
- excludePaths?: string[];
22
- }
23
- /**
24
- * Check if a user agent string belongs to a bot.
25
- */
26
- declare function isBot(userAgent: string, extraBots?: string[]): boolean;
27
- /**
28
- * Check if a path is a static asset.
29
- */
30
- declare function isStaticAsset(pathname: string): boolean;
31
- /**
32
- * Check if a path should be excluded.
33
- */
34
- declare function isExcluded(pathname: string, excludePaths?: string[]): boolean;
35
- /**
36
- * Fetch pre-rendered HTML from Rank Deploy API.
37
- * Returns the HTML string if available, null otherwise.
38
- */
39
- declare function fetchPrerendered(url: string, userAgent: string, apiUrl?: string): Promise<string | null>;
40
-
41
- export { BOT_USER_AGENTS, RANKDEPLOY_API, type RankDeployOptions, STATIC_EXTENSIONS, fetchPrerendered, isBot, isExcluded, isStaticAsset };
package/dist/index.d.ts DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * @rankdeploy/middleware — Core SEO middleware engine
3
- *
4
- * Detects search engine bots and serves pre-rendered HTML from Rank Deploy API.
5
- * Human visitors are not affected.
6
- *
7
- * Usage:
8
- * import { middleware } from '@rankdeploy/middleware/next' // Next.js
9
- * import { default } from '@rankdeploy/middleware/netlify' // Netlify
10
- * import { rankdeploy } from '@rankdeploy/middleware/express' // Express
11
- */
12
- declare const RANKDEPLOY_API = "https://proxy.unikium.com";
13
- declare const BOT_USER_AGENTS: string[];
14
- declare const STATIC_EXTENSIONS: RegExp;
15
- interface RankDeployOptions {
16
- /** Rank Deploy API URL. Default: https://proxy.unikium.com */
17
- apiUrl?: string;
18
- /** Additional bot user agents to detect */
19
- extraBots?: string[];
20
- /** Paths to exclude from pre-rendering */
21
- excludePaths?: string[];
22
- }
23
- /**
24
- * Check if a user agent string belongs to a bot.
25
- */
26
- declare function isBot(userAgent: string, extraBots?: string[]): boolean;
27
- /**
28
- * Check if a path is a static asset.
29
- */
30
- declare function isStaticAsset(pathname: string): boolean;
31
- /**
32
- * Check if a path should be excluded.
33
- */
34
- declare function isExcluded(pathname: string, excludePaths?: string[]): boolean;
35
- /**
36
- * Fetch pre-rendered HTML from Rank Deploy API.
37
- * Returns the HTML string if available, null otherwise.
38
- */
39
- declare function fetchPrerendered(url: string, userAgent: string, apiUrl?: string): Promise<string | null>;
40
-
41
- export { BOT_USER_AGENTS, RANKDEPLOY_API, type RankDeployOptions, STATIC_EXTENSIONS, fetchPrerendered, isBot, isExcluded, isStaticAsset };
@@ -1,16 +0,0 @@
1
- import * as undici_types from 'undici-types';
2
- import { RankDeployOptions } from './index.mjs';
3
-
4
- interface NetlifyContext {
5
- next(): Promise<Response>;
6
- }
7
- /**
8
- * Create a Netlify Edge Function handler with custom options.
9
- */
10
- declare function createHandler(options?: RankDeployOptions): (request: Request, context: NetlifyContext) => Promise<undici_types.Response>;
11
- /**
12
- * Default handler — ready to use as-is.
13
- */
14
- declare const _default: (request: Request, context: NetlifyContext) => Promise<undici_types.Response>;
15
-
16
- export { createHandler, _default as default };
package/dist/netlify.d.ts DELETED
@@ -1,16 +0,0 @@
1
- import * as undici_types from 'undici-types';
2
- import { RankDeployOptions } from './index.js';
3
-
4
- interface NetlifyContext {
5
- next(): Promise<Response>;
6
- }
7
- /**
8
- * Create a Netlify Edge Function handler with custom options.
9
- */
10
- declare function createHandler(options?: RankDeployOptions): (request: Request, context: NetlifyContext) => Promise<undici_types.Response>;
11
- /**
12
- * Default handler — ready to use as-is.
13
- */
14
- declare const _default: (request: Request, context: NetlifyContext) => Promise<undici_types.Response>;
15
-
16
- export { createHandler, _default as default };
package/dist/next.d.mts DELETED
@@ -1,25 +0,0 @@
1
- import * as undici_types from 'undici-types';
2
- import { RankDeployOptions } from './index.mjs';
3
-
4
- interface NextMiddlewareRequest {
5
- headers: {
6
- get(name: string): string | null;
7
- };
8
- nextUrl: {
9
- pathname: string;
10
- toString(): string;
11
- };
12
- }
13
- /**
14
- * Create a Next.js middleware function with custom options.
15
- */
16
- declare function createMiddleware(options?: RankDeployOptions): (request: NextMiddlewareRequest) => Promise<undici_types.Response | undefined>;
17
- /**
18
- * Default middleware — ready to use.
19
- */
20
- declare const middleware: (request: NextMiddlewareRequest) => Promise<undici_types.Response | undefined>;
21
- declare const config: {
22
- matcher: string[];
23
- };
24
-
25
- export { config, createMiddleware, middleware };
package/dist/next.d.ts DELETED
@@ -1,25 +0,0 @@
1
- import * as undici_types from 'undici-types';
2
- import { RankDeployOptions } from './index.js';
3
-
4
- interface NextMiddlewareRequest {
5
- headers: {
6
- get(name: string): string | null;
7
- };
8
- nextUrl: {
9
- pathname: string;
10
- toString(): string;
11
- };
12
- }
13
- /**
14
- * Create a Next.js middleware function with custom options.
15
- */
16
- declare function createMiddleware(options?: RankDeployOptions): (request: NextMiddlewareRequest) => Promise<undici_types.Response | undefined>;
17
- /**
18
- * Default middleware — ready to use.
19
- */
20
- declare const middleware: (request: NextMiddlewareRequest) => Promise<undici_types.Response | undefined>;
21
- declare const config: {
22
- matcher: string[];
23
- };
24
-
25
- export { config, createMiddleware, middleware };