rankdeploy-middleware 1.2.2 → 1.3.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.
package/dist/next.js CHANGED
@@ -107,9 +107,29 @@ async function fetchPrerendered(url, userAgent, siteKey, apiUrl = RANKDEPLOY_API
107
107
 
108
108
  // src/next.ts
109
109
  function createMiddleware(options) {
110
+ const apiUrl = options.apiUrl || RANKDEPLOY_API;
110
111
  return async function middleware(request) {
111
112
  const userAgent = request.headers.get("user-agent") || "";
112
113
  const pathname = request.nextUrl.pathname;
114
+ const hostname = request.nextUrl.hostname || new URL(request.nextUrl.toString()).hostname;
115
+ if (pathname === "/sitemap.xml" || pathname === "/robots.txt") {
116
+ try {
117
+ const file = pathname.slice(1);
118
+ const res = await fetch(`${apiUrl}/seo-file?domain=${hostname}&file=${file}`);
119
+ if (res.ok) {
120
+ const content = await res.text();
121
+ return new Response(content, {
122
+ status: 200,
123
+ headers: {
124
+ "Content-Type": pathname === "/sitemap.xml" ? "application/xml" : "text/plain",
125
+ "Cache-Control": "public, max-age=3600"
126
+ }
127
+ });
128
+ }
129
+ } catch {
130
+ }
131
+ return;
132
+ }
113
133
  if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
114
134
  return;
115
135
  }
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 * Serves pre-rendered HTML with SEO overrides.\n * By default serves to ALL requests (so OG checkers work).\n * Set botsOnly: true to only serve to search engine bots.\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NextMiddlewareRequest {\n headers: { get(name: string): string | null };\n nextUrl: { pathname: string; toString(): string };\n}\n\nexport function createMiddleware(options: RankDeployOptions) {\n return async function middleware(request: NextMiddlewareRequest) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = request.nextUrl.pathname;\n\n // Skip static assets and excluded paths\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return;\n }\n\n // Only intercept bot requests (default behavior)\n // Set botsOnly: false to serve to everyone (not recommended — can cause loops)\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return;\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return;\n };\n}\n\nexport const config = {\n matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n","/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Serves pre-rendered HTML with SEO overrides to all visitors.\n * The pre-rendered HTML is visually identical to the original page\n * but includes injected meta tags (title, description, canonical, og:tags).\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n /** Only serve to bots (default: false — serves to everyone for OG checker compatibility) */\n botsOnly?: boolean;\n /** Additional bot user agents to detect (used when botsOnly=true) */\n extraBots?: string[];\n}\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n // SEO tools & OG checkers\n \"opengraph\", \"metatags\", \"preview\", \"crawler\", \"spider\", \"fetch\",\n \"curl\", \"wget\", \"python-requests\", \"node-fetch\", \"axios\",\n];\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 10000);\n\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: controller.signal,\n },\n );\n\n clearTimeout(timer);\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,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,GAAK;AAExD,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,WAAW;AAAA,MACrB;AAAA,IACF;AAEA,iBAAa,KAAK;AAElB,QAAI,IAAI,WAAW,KAAK;AACtB,aAAO,IAAI,KAAK;AAAA,IAClB;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ADnEO,SAAS,iBAAiB,SAA4B;AAC3D,SAAO,eAAe,WAAW,SAAgC;AAC/D,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,QAAQ,QAAQ;AAGjC,QAAI,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACzE;AAAA,IACF;AAIA,QAAI,QAAQ,aAAa,SAAS,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACtE;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,QAAQ,SAAS,GAAG,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAE1G,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA;AAAA,EACF;AACF;AAEO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,mDAAmD;AAC/D;","names":[]}
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, RANKDEPLOY_API, 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 const apiUrl = options.apiUrl || RANKDEPLOY_API;\n\n return async function middleware(request: NextMiddlewareRequest) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = request.nextUrl.pathname;\n const hostname = request.nextUrl.hostname || new URL(request.nextUrl.toString()).hostname;\n\n // Serve auto-generated sitemap.xml and robots.txt\n if (pathname === \"/sitemap.xml\" || pathname === \"/robots.txt\") {\n try {\n const file = pathname.slice(1); // \"sitemap.xml\" or \"robots.txt\"\n const res = await fetch(`${apiUrl}/seo-file?domain=${hostname}&file=${file}`);\n if (res.ok) {\n const content = await res.text();\n return new Response(content, {\n status: 200,\n headers: {\n \"Content-Type\": pathname === \"/sitemap.xml\" ? \"application/xml\" : \"text/plain\",\n \"Cache-Control\": \"public, max-age=3600\",\n },\n });\n }\n } catch {\n // Fall through to origin\n }\n return;\n }\n\n // Skip static assets and excluded paths\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return;\n }\n\n // Only intercept bot requests (default behavior)\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return;\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return;\n };\n}\n\nexport const config = {\n matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n","/**\n * rankdeploy-middleware — Core SEO middleware engine\n *\n * Serves pre-rendered HTML with SEO overrides to all visitors.\n * The pre-rendered HTML is visually identical to the original page\n * but includes injected meta tags (title, description, canonical, og:tags).\n */\n\nexport const RANKDEPLOY_API = \"https://proxy.unikium.com\";\n\nexport const STATIC_EXTENSIONS = /\\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json|xml|txt|mp4|webm|pdf|zip)$/i;\n\nexport interface RankDeployOptions {\n /** Your site key from Rank Deploy dashboard (required) */\n siteKey: string;\n /** Rank Deploy API URL. Default: https://proxy.unikium.com */\n apiUrl?: string;\n /** Paths to exclude from pre-rendering */\n excludePaths?: string[];\n /** Only serve to bots (default: false — serves to everyone for OG checker compatibility) */\n botsOnly?: boolean;\n /** Additional bot user agents to detect (used when botsOnly=true) */\n extraBots?: string[];\n}\n\nexport const BOT_USER_AGENTS = [\n \"googlebot\", \"bingbot\", \"yandexbot\", \"duckduckbot\", \"baiduspider\",\n \"slurp\", \"sogou\", \"exabot\",\n \"facebot\", \"facebookexternalhit\", \"twitterbot\", \"linkedinbot\",\n \"whatsapp\", \"telegrambot\", \"applebot\", \"discordbot\", \"slackbot\", \"pinterest\",\n \"screaming frog\", \"ahrefs\", \"semrush\", \"moz.com\", \"rogerbot\", \"dotbot\",\n \"gptbot\", \"chatgpt\", \"anthropic\", \"claudebot\", \"perplexitybot\", \"cohere\",\n // SEO tools & OG checkers\n \"opengraph\", \"metatags\", \"preview\", \"crawler\", \"spider\", \"fetch\",\n \"curl\", \"wget\", \"python-requests\", \"node-fetch\", \"axios\",\n];\n\nexport function isBot(userAgent: string, extraBots?: string[]): boolean {\n const ua = userAgent.toLowerCase();\n const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;\n return allBots.some((bot) => ua.includes(bot));\n}\n\nexport function isStaticAsset(pathname: string): boolean {\n return STATIC_EXTENSIONS.test(pathname);\n}\n\nexport function isExcluded(pathname: string, excludePaths?: string[]): boolean {\n if (!excludePaths) return false;\n return excludePaths.some((p) => pathname.startsWith(p));\n}\n\n/**\n * Fetch pre-rendered HTML from Rank Deploy API.\n */\nexport async function fetchPrerendered(\n url: string,\n userAgent: string,\n siteKey: string,\n apiUrl = RANKDEPLOY_API,\n): Promise<string | null> {\n try {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 10000);\n\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: controller.signal,\n },\n );\n\n clearTimeout(timer);\n\n if (res.status === 200) {\n return res.text();\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/next.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ RANKDEPLOY_API,
2
3
  fetchPrerendered,
3
4
  isBot,
4
5
  isExcluded,
@@ -7,9 +8,29 @@ import {
7
8
 
8
9
  // src/next.ts
9
10
  function createMiddleware(options) {
11
+ const apiUrl = options.apiUrl || RANKDEPLOY_API;
10
12
  return async function middleware(request) {
11
13
  const userAgent = request.headers.get("user-agent") || "";
12
14
  const pathname = request.nextUrl.pathname;
15
+ const hostname = request.nextUrl.hostname || new URL(request.nextUrl.toString()).hostname;
16
+ if (pathname === "/sitemap.xml" || pathname === "/robots.txt") {
17
+ try {
18
+ const file = pathname.slice(1);
19
+ const res = await fetch(`${apiUrl}/seo-file?domain=${hostname}&file=${file}`);
20
+ if (res.ok) {
21
+ const content = await res.text();
22
+ return new Response(content, {
23
+ status: 200,
24
+ headers: {
25
+ "Content-Type": pathname === "/sitemap.xml" ? "application/xml" : "text/plain",
26
+ "Cache-Control": "public, max-age=3600"
27
+ }
28
+ });
29
+ }
30
+ } catch {
31
+ }
32
+ return;
33
+ }
13
34
  if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
14
35
  return;
15
36
  }
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 * Serves pre-rendered HTML with SEO overrides.\n * By default serves to ALL requests (so OG checkers work).\n * Set botsOnly: true to only serve to search engine bots.\n */\n\nimport { isBot, isStaticAsset, isExcluded, fetchPrerendered, type RankDeployOptions } from \"./index\";\n\ninterface NextMiddlewareRequest {\n headers: { get(name: string): string | null };\n nextUrl: { pathname: string; toString(): string };\n}\n\nexport function createMiddleware(options: RankDeployOptions) {\n return async function middleware(request: NextMiddlewareRequest) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = request.nextUrl.pathname;\n\n // Skip static assets and excluded paths\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return;\n }\n\n // Only intercept bot requests (default behavior)\n // Set botsOnly: false to serve to everyone (not recommended — can cause loops)\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return;\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return;\n };\n}\n\nexport const config = {\n matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n"],"mappings":";;;;;;;;AAeO,SAAS,iBAAiB,SAA4B;AAC3D,SAAO,eAAe,WAAW,SAAgC;AAC/D,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,QAAQ,QAAQ;AAGjC,QAAI,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACzE;AAAA,IACF;AAIA,QAAI,QAAQ,aAAa,SAAS,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACtE;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,QAAQ,SAAS,GAAG,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAE1G,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA;AAAA,EACF;AACF;AAEO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,mDAAmD;AAC/D;","names":[]}
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, RANKDEPLOY_API, 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 const apiUrl = options.apiUrl || RANKDEPLOY_API;\n\n return async function middleware(request: NextMiddlewareRequest) {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n const pathname = request.nextUrl.pathname;\n const hostname = request.nextUrl.hostname || new URL(request.nextUrl.toString()).hostname;\n\n // Serve auto-generated sitemap.xml and robots.txt\n if (pathname === \"/sitemap.xml\" || pathname === \"/robots.txt\") {\n try {\n const file = pathname.slice(1); // \"sitemap.xml\" or \"robots.txt\"\n const res = await fetch(`${apiUrl}/seo-file?domain=${hostname}&file=${file}`);\n if (res.ok) {\n const content = await res.text();\n return new Response(content, {\n status: 200,\n headers: {\n \"Content-Type\": pathname === \"/sitemap.xml\" ? \"application/xml\" : \"text/plain\",\n \"Cache-Control\": \"public, max-age=3600\",\n },\n });\n }\n } catch {\n // Fall through to origin\n }\n return;\n }\n\n // Skip static assets and excluded paths\n if (isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {\n return;\n }\n\n // Only intercept bot requests (default behavior)\n if (options.botsOnly !== false && !isBot(userAgent, options.extraBots)) {\n return;\n }\n\n const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.siteKey, options.apiUrl);\n\n if (html) {\n return new Response(html, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"X-Prerendered\": \"true\",\n \"X-Powered-By\": \"Rank Deploy\",\n },\n });\n }\n\n return;\n };\n}\n\nexport const config = {\n matcher: [\"/((?!api|_next/static|_next/image|favicon.ico).*)\"],\n};\n"],"mappings":";;;;;;;;;AAeO,SAAS,iBAAiB,SAA4B;AAC3D,QAAM,SAAS,QAAQ,UAAU;AAEjC,SAAO,eAAe,WAAW,SAAgC;AAC/D,UAAM,YAAY,QAAQ,QAAQ,IAAI,YAAY,KAAK;AACvD,UAAM,WAAW,QAAQ,QAAQ;AACjC,UAAM,WAAW,QAAQ,QAAQ,YAAY,IAAI,IAAI,QAAQ,QAAQ,SAAS,CAAC,EAAE;AAGjF,QAAI,aAAa,kBAAkB,aAAa,eAAe;AAC7D,UAAI;AACF,cAAM,OAAO,SAAS,MAAM,CAAC;AAC7B,cAAM,MAAM,MAAM,MAAM,GAAG,MAAM,oBAAoB,QAAQ,SAAS,IAAI,EAAE;AAC5E,YAAI,IAAI,IAAI;AACV,gBAAM,UAAU,MAAM,IAAI,KAAK;AAC/B,iBAAO,IAAI,SAAS,SAAS;AAAA,YAC3B,QAAQ;AAAA,YACR,SAAS;AAAA,cACP,gBAAgB,aAAa,iBAAiB,oBAAoB;AAAA,cAClE,iBAAiB;AAAA,YACnB;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,QAAQ;AAAA,MAER;AACA;AAAA,IACF;AAGA,QAAI,cAAc,QAAQ,KAAK,WAAW,UAAU,QAAQ,YAAY,GAAG;AACzE;AAAA,IACF;AAGA,QAAI,QAAQ,aAAa,SAAS,CAAC,MAAM,WAAW,QAAQ,SAAS,GAAG;AACtE;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,iBAAiB,QAAQ,QAAQ,SAAS,GAAG,WAAW,QAAQ,SAAS,QAAQ,MAAM;AAE1G,QAAI,MAAM;AACR,aAAO,IAAI,SAAS,MAAM;AAAA,QACxB,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,gBAAgB;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AAEA;AAAA,EACF;AACF;AAEO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,mDAAmD;AAC/D;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rankdeploy-middleware",
3
- "version": "1.2.2",
3
+ "version": "1.3.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",