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 +20 -0
- package/dist/next.js.map +1 -1
- package/dist/next.mjs +21 -0
- package/dist/next.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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":[]}
|