rankdeploy-middleware 1.0.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/README.md +70 -0
- package/dist/chunk-FPOJYG2E.mjs +76 -0
- package/dist/chunk-FPOJYG2E.mjs.map +1 -0
- package/dist/express.d.mts +34 -0
- package/dist/express.d.ts +34 -0
- package/dist/express.js +126 -0
- package/dist/express.js.map +1 -0
- package/dist/express.mjs +40 -0
- package/dist/express.mjs.map +1 -0
- package/dist/index.d.mts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +106 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +19 -0
- package/dist/index.mjs.map +1 -0
- package/dist/netlify.d.mts +16 -0
- package/dist/netlify.d.ts +16 -0
- package/dist/netlify.js +127 -0
- package/dist/netlify.js.map +1 -0
- package/dist/netlify.mjs +41 -0
- package/dist/netlify.mjs.map +1 -0
- package/dist/next.d.mts +25 -0
- package/dist/next.d.ts +25 -0
- package/dist/next.js +127 -0
- package/dist/next.js.map +1 -0
- package/dist/next.mjs +39 -0
- package/dist/next.mjs.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @rankdeploy/middleware
|
|
2
|
+
|
|
3
|
+
SEO middleware that serves pre-rendered HTML to search engine bots. Human visitors are not affected.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rankdeploy/middleware
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Next.js / Vercel
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// middleware.ts (at project root)
|
|
15
|
+
export { middleware, config } from '@rankdeploy/middleware/next'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
That's it. One line.
|
|
19
|
+
|
|
20
|
+
## Netlify
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// netlify/edge-functions/rankdeploy.ts
|
|
24
|
+
export { default } from '@rankdeploy/middleware/netlify'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Add to `netlify.toml`:
|
|
28
|
+
```toml
|
|
29
|
+
[[edge_functions]]
|
|
30
|
+
function = "rankdeploy"
|
|
31
|
+
path = "/*"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Express.js
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { rankdeploy } from '@rankdeploy/middleware/express'
|
|
38
|
+
|
|
39
|
+
app.use(rankdeploy())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Custom Options
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { createMiddleware } from '@rankdeploy/middleware/next'
|
|
46
|
+
|
|
47
|
+
export const middleware = createMiddleware({
|
|
48
|
+
// Custom API URL (default: https://proxy.unikium.com)
|
|
49
|
+
apiUrl: 'https://proxy.unikium.com',
|
|
50
|
+
// Extra bot user agents to detect
|
|
51
|
+
extraBots: ['my-custom-bot'],
|
|
52
|
+
// Paths to exclude from pre-rendering
|
|
53
|
+
excludePaths: ['/api', '/admin', '/dashboard'],
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## How it works
|
|
58
|
+
|
|
59
|
+
1. A request comes in
|
|
60
|
+
2. The middleware checks the User-Agent header
|
|
61
|
+
3. If it's a search engine bot (Google, Bing, etc.):
|
|
62
|
+
- Fetches pre-rendered HTML from Rank Deploy API
|
|
63
|
+
- Returns optimized HTML with injected SEO tags
|
|
64
|
+
4. If it's a human visitor:
|
|
65
|
+
- Request passes through unchanged
|
|
66
|
+
- Zero performance impact
|
|
67
|
+
|
|
68
|
+
## Supported bots
|
|
69
|
+
|
|
70
|
+
Google, Bing, Yandex, DuckDuckGo, Baidu, Facebook, Twitter/X, LinkedIn, WhatsApp, Telegram, Slack, Discord, Pinterest, Ahrefs, SEMrush, Moz, GPTBot, Claude, Perplexity, and more.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var RANKDEPLOY_API = "https://proxy.unikium.com";
|
|
3
|
+
var BOT_USER_AGENTS = [
|
|
4
|
+
"googlebot",
|
|
5
|
+
"bingbot",
|
|
6
|
+
"yandexbot",
|
|
7
|
+
"duckduckbot",
|
|
8
|
+
"baiduspider",
|
|
9
|
+
"slurp",
|
|
10
|
+
"sogou",
|
|
11
|
+
"exabot",
|
|
12
|
+
"facebot",
|
|
13
|
+
"facebookexternalhit",
|
|
14
|
+
"twitterbot",
|
|
15
|
+
"linkedinbot",
|
|
16
|
+
"whatsapp",
|
|
17
|
+
"telegrambot",
|
|
18
|
+
"applebot",
|
|
19
|
+
"discordbot",
|
|
20
|
+
"slackbot",
|
|
21
|
+
"pinterest",
|
|
22
|
+
"screaming frog",
|
|
23
|
+
"ahrefs",
|
|
24
|
+
"semrush",
|
|
25
|
+
"moz.com",
|
|
26
|
+
"rogerbot",
|
|
27
|
+
"dotbot",
|
|
28
|
+
"gptbot",
|
|
29
|
+
"chatgpt",
|
|
30
|
+
"anthropic",
|
|
31
|
+
"claudebot",
|
|
32
|
+
"perplexitybot",
|
|
33
|
+
"cohere"
|
|
34
|
+
];
|
|
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
|
+
function isBot(userAgent, extraBots) {
|
|
37
|
+
const ua = userAgent.toLowerCase();
|
|
38
|
+
const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
|
|
39
|
+
return allBots.some((bot) => ua.includes(bot));
|
|
40
|
+
}
|
|
41
|
+
function isStaticAsset(pathname) {
|
|
42
|
+
return STATIC_EXTENSIONS.test(pathname);
|
|
43
|
+
}
|
|
44
|
+
function isExcluded(pathname, excludePaths) {
|
|
45
|
+
if (!excludePaths) return false;
|
|
46
|
+
return excludePaths.some((p) => pathname.startsWith(p));
|
|
47
|
+
}
|
|
48
|
+
async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(
|
|
51
|
+
`${apiUrl}/render?url=${encodeURIComponent(url)}`,
|
|
52
|
+
{
|
|
53
|
+
headers: { "User-Agent": userAgent },
|
|
54
|
+
signal: AbortSignal.timeout(1e4)
|
|
55
|
+
// 10s timeout
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
if (res.status === 200) {
|
|
59
|
+
return res.text();
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
RANKDEPLOY_API,
|
|
69
|
+
BOT_USER_AGENTS,
|
|
70
|
+
STATIC_EXTENSIONS,
|
|
71
|
+
isBot,
|
|
72
|
+
isStaticAsset,
|
|
73
|
+
isExcluded,
|
|
74
|
+
fetchPrerendered
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=chunk-FPOJYG2E.mjs.map
|
|
@@ -0,0 +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":";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":[]}
|
|
@@ -0,0 +1,34 @@
|
|
|
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 };
|
|
@@ -0,0 +1,34 @@
|
|
|
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/express.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/express.ts
|
|
21
|
+
var express_exports = {};
|
|
22
|
+
__export(express_exports, {
|
|
23
|
+
default: () => express_default,
|
|
24
|
+
rankdeploy: () => rankdeploy
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(express_exports);
|
|
27
|
+
|
|
28
|
+
// src/index.ts
|
|
29
|
+
var RANKDEPLOY_API = "https://proxy.unikium.com";
|
|
30
|
+
var BOT_USER_AGENTS = [
|
|
31
|
+
"googlebot",
|
|
32
|
+
"bingbot",
|
|
33
|
+
"yandexbot",
|
|
34
|
+
"duckduckbot",
|
|
35
|
+
"baiduspider",
|
|
36
|
+
"slurp",
|
|
37
|
+
"sogou",
|
|
38
|
+
"exabot",
|
|
39
|
+
"facebot",
|
|
40
|
+
"facebookexternalhit",
|
|
41
|
+
"twitterbot",
|
|
42
|
+
"linkedinbot",
|
|
43
|
+
"whatsapp",
|
|
44
|
+
"telegrambot",
|
|
45
|
+
"applebot",
|
|
46
|
+
"discordbot",
|
|
47
|
+
"slackbot",
|
|
48
|
+
"pinterest",
|
|
49
|
+
"screaming frog",
|
|
50
|
+
"ahrefs",
|
|
51
|
+
"semrush",
|
|
52
|
+
"moz.com",
|
|
53
|
+
"rogerbot",
|
|
54
|
+
"dotbot",
|
|
55
|
+
"gptbot",
|
|
56
|
+
"chatgpt",
|
|
57
|
+
"anthropic",
|
|
58
|
+
"claudebot",
|
|
59
|
+
"perplexitybot",
|
|
60
|
+
"cohere"
|
|
61
|
+
];
|
|
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
|
+
function isBot(userAgent, extraBots) {
|
|
64
|
+
const ua = userAgent.toLowerCase();
|
|
65
|
+
const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
|
|
66
|
+
return allBots.some((bot) => ua.includes(bot));
|
|
67
|
+
}
|
|
68
|
+
function isStaticAsset(pathname) {
|
|
69
|
+
return STATIC_EXTENSIONS.test(pathname);
|
|
70
|
+
}
|
|
71
|
+
function isExcluded(pathname, excludePaths) {
|
|
72
|
+
if (!excludePaths) return false;
|
|
73
|
+
return excludePaths.some((p) => pathname.startsWith(p));
|
|
74
|
+
}
|
|
75
|
+
async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
|
|
76
|
+
try {
|
|
77
|
+
const res = await fetch(
|
|
78
|
+
`${apiUrl}/render?url=${encodeURIComponent(url)}`,
|
|
79
|
+
{
|
|
80
|
+
headers: { "User-Agent": userAgent },
|
|
81
|
+
signal: AbortSignal.timeout(1e4)
|
|
82
|
+
// 10s timeout
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
if (res.status === 200) {
|
|
86
|
+
return res.text();
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/express.ts
|
|
95
|
+
function rankdeploy(options = {}) {
|
|
96
|
+
return async function middleware(req, res, next) {
|
|
97
|
+
const userAgent = req.headers["user-agent"] || req.get?.("user-agent") || "";
|
|
98
|
+
const pathname = req.url.split("?")[0];
|
|
99
|
+
if (!isBot(userAgent, options.extraBots)) {
|
|
100
|
+
return next();
|
|
101
|
+
}
|
|
102
|
+
if (isStaticAsset(pathname)) {
|
|
103
|
+
return next();
|
|
104
|
+
}
|
|
105
|
+
if (isExcluded(pathname, options.excludePaths)) {
|
|
106
|
+
return next();
|
|
107
|
+
}
|
|
108
|
+
const protocol = req.protocol || "https";
|
|
109
|
+
const host = req.get?.("host") || req.headers.host || "";
|
|
110
|
+
const fullUrl = `${protocol}://${host}${req.url}`;
|
|
111
|
+
const html = await fetchPrerendered(fullUrl, userAgent, options.apiUrl);
|
|
112
|
+
if (html) {
|
|
113
|
+
res.set("Content-Type", "text/html; charset=utf-8");
|
|
114
|
+
res.set("X-Prerendered", "true");
|
|
115
|
+
res.set("X-Powered-By", "Rank Deploy");
|
|
116
|
+
return res.status(200).send(html);
|
|
117
|
+
}
|
|
118
|
+
next();
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
var express_default = rankdeploy;
|
|
122
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
123
|
+
0 && (module.exports = {
|
|
124
|
+
rankdeploy
|
|
125
|
+
});
|
|
126
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +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":[]}
|
package/dist/express.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fetchPrerendered,
|
|
3
|
+
isBot,
|
|
4
|
+
isExcluded,
|
|
5
|
+
isStaticAsset
|
|
6
|
+
} from "./chunk-FPOJYG2E.mjs";
|
|
7
|
+
|
|
8
|
+
// src/express.ts
|
|
9
|
+
function rankdeploy(options = {}) {
|
|
10
|
+
return async function middleware(req, res, next) {
|
|
11
|
+
const userAgent = req.headers["user-agent"] || req.get?.("user-agent") || "";
|
|
12
|
+
const pathname = req.url.split("?")[0];
|
|
13
|
+
if (!isBot(userAgent, options.extraBots)) {
|
|
14
|
+
return next();
|
|
15
|
+
}
|
|
16
|
+
if (isStaticAsset(pathname)) {
|
|
17
|
+
return next();
|
|
18
|
+
}
|
|
19
|
+
if (isExcluded(pathname, options.excludePaths)) {
|
|
20
|
+
return next();
|
|
21
|
+
}
|
|
22
|
+
const protocol = req.protocol || "https";
|
|
23
|
+
const host = req.get?.("host") || req.headers.host || "";
|
|
24
|
+
const fullUrl = `${protocol}://${host}${req.url}`;
|
|
25
|
+
const html = await fetchPrerendered(fullUrl, userAgent, options.apiUrl);
|
|
26
|
+
if (html) {
|
|
27
|
+
res.set("Content-Type", "text/html; charset=utf-8");
|
|
28
|
+
res.set("X-Prerendered", "true");
|
|
29
|
+
res.set("X-Powered-By", "Rank Deploy");
|
|
30
|
+
return res.status(200).send(html);
|
|
31
|
+
}
|
|
32
|
+
next();
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
var express_default = rankdeploy;
|
|
36
|
+
export {
|
|
37
|
+
express_default as default,
|
|
38
|
+
rankdeploy
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=express.mjs.map
|
|
@@ -0,0 +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":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
BOT_USER_AGENTS: () => BOT_USER_AGENTS,
|
|
24
|
+
RANKDEPLOY_API: () => RANKDEPLOY_API,
|
|
25
|
+
STATIC_EXTENSIONS: () => STATIC_EXTENSIONS,
|
|
26
|
+
fetchPrerendered: () => fetchPrerendered,
|
|
27
|
+
isBot: () => isBot,
|
|
28
|
+
isExcluded: () => isExcluded,
|
|
29
|
+
isStaticAsset: () => isStaticAsset
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(src_exports);
|
|
32
|
+
var RANKDEPLOY_API = "https://proxy.unikium.com";
|
|
33
|
+
var BOT_USER_AGENTS = [
|
|
34
|
+
"googlebot",
|
|
35
|
+
"bingbot",
|
|
36
|
+
"yandexbot",
|
|
37
|
+
"duckduckbot",
|
|
38
|
+
"baiduspider",
|
|
39
|
+
"slurp",
|
|
40
|
+
"sogou",
|
|
41
|
+
"exabot",
|
|
42
|
+
"facebot",
|
|
43
|
+
"facebookexternalhit",
|
|
44
|
+
"twitterbot",
|
|
45
|
+
"linkedinbot",
|
|
46
|
+
"whatsapp",
|
|
47
|
+
"telegrambot",
|
|
48
|
+
"applebot",
|
|
49
|
+
"discordbot",
|
|
50
|
+
"slackbot",
|
|
51
|
+
"pinterest",
|
|
52
|
+
"screaming frog",
|
|
53
|
+
"ahrefs",
|
|
54
|
+
"semrush",
|
|
55
|
+
"moz.com",
|
|
56
|
+
"rogerbot",
|
|
57
|
+
"dotbot",
|
|
58
|
+
"gptbot",
|
|
59
|
+
"chatgpt",
|
|
60
|
+
"anthropic",
|
|
61
|
+
"claudebot",
|
|
62
|
+
"perplexitybot",
|
|
63
|
+
"cohere"
|
|
64
|
+
];
|
|
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
|
+
function isBot(userAgent, extraBots) {
|
|
67
|
+
const ua = userAgent.toLowerCase();
|
|
68
|
+
const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
|
|
69
|
+
return allBots.some((bot) => ua.includes(bot));
|
|
70
|
+
}
|
|
71
|
+
function isStaticAsset(pathname) {
|
|
72
|
+
return STATIC_EXTENSIONS.test(pathname);
|
|
73
|
+
}
|
|
74
|
+
function isExcluded(pathname, excludePaths) {
|
|
75
|
+
if (!excludePaths) return false;
|
|
76
|
+
return excludePaths.some((p) => pathname.startsWith(p));
|
|
77
|
+
}
|
|
78
|
+
async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
|
|
79
|
+
try {
|
|
80
|
+
const res = await fetch(
|
|
81
|
+
`${apiUrl}/render?url=${encodeURIComponent(url)}`,
|
|
82
|
+
{
|
|
83
|
+
headers: { "User-Agent": userAgent },
|
|
84
|
+
signal: AbortSignal.timeout(1e4)
|
|
85
|
+
// 10s timeout
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
if (res.status === 200) {
|
|
89
|
+
return res.text();
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
97
|
+
0 && (module.exports = {
|
|
98
|
+
BOT_USER_AGENTS,
|
|
99
|
+
RANKDEPLOY_API,
|
|
100
|
+
STATIC_EXTENSIONS,
|
|
101
|
+
fetchPrerendered,
|
|
102
|
+
isBot,
|
|
103
|
+
isExcluded,
|
|
104
|
+
isStaticAsset
|
|
105
|
+
});
|
|
106
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BOT_USER_AGENTS,
|
|
3
|
+
RANKDEPLOY_API,
|
|
4
|
+
STATIC_EXTENSIONS,
|
|
5
|
+
fetchPrerendered,
|
|
6
|
+
isBot,
|
|
7
|
+
isExcluded,
|
|
8
|
+
isStaticAsset
|
|
9
|
+
} from "./chunk-FPOJYG2E.mjs";
|
|
10
|
+
export {
|
|
11
|
+
BOT_USER_AGENTS,
|
|
12
|
+
RANKDEPLOY_API,
|
|
13
|
+
STATIC_EXTENSIONS,
|
|
14
|
+
fetchPrerendered,
|
|
15
|
+
isBot,
|
|
16
|
+
isExcluded,
|
|
17
|
+
isStaticAsset
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
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 };
|
|
@@ -0,0 +1,16 @@
|
|
|
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/netlify.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/netlify.ts
|
|
21
|
+
var netlify_exports = {};
|
|
22
|
+
__export(netlify_exports, {
|
|
23
|
+
createHandler: () => createHandler,
|
|
24
|
+
default: () => netlify_default
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(netlify_exports);
|
|
27
|
+
|
|
28
|
+
// src/index.ts
|
|
29
|
+
var RANKDEPLOY_API = "https://proxy.unikium.com";
|
|
30
|
+
var BOT_USER_AGENTS = [
|
|
31
|
+
"googlebot",
|
|
32
|
+
"bingbot",
|
|
33
|
+
"yandexbot",
|
|
34
|
+
"duckduckbot",
|
|
35
|
+
"baiduspider",
|
|
36
|
+
"slurp",
|
|
37
|
+
"sogou",
|
|
38
|
+
"exabot",
|
|
39
|
+
"facebot",
|
|
40
|
+
"facebookexternalhit",
|
|
41
|
+
"twitterbot",
|
|
42
|
+
"linkedinbot",
|
|
43
|
+
"whatsapp",
|
|
44
|
+
"telegrambot",
|
|
45
|
+
"applebot",
|
|
46
|
+
"discordbot",
|
|
47
|
+
"slackbot",
|
|
48
|
+
"pinterest",
|
|
49
|
+
"screaming frog",
|
|
50
|
+
"ahrefs",
|
|
51
|
+
"semrush",
|
|
52
|
+
"moz.com",
|
|
53
|
+
"rogerbot",
|
|
54
|
+
"dotbot",
|
|
55
|
+
"gptbot",
|
|
56
|
+
"chatgpt",
|
|
57
|
+
"anthropic",
|
|
58
|
+
"claudebot",
|
|
59
|
+
"perplexitybot",
|
|
60
|
+
"cohere"
|
|
61
|
+
];
|
|
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
|
+
function isBot(userAgent, extraBots) {
|
|
64
|
+
const ua = userAgent.toLowerCase();
|
|
65
|
+
const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
|
|
66
|
+
return allBots.some((bot) => ua.includes(bot));
|
|
67
|
+
}
|
|
68
|
+
function isStaticAsset(pathname) {
|
|
69
|
+
return STATIC_EXTENSIONS.test(pathname);
|
|
70
|
+
}
|
|
71
|
+
function isExcluded(pathname, excludePaths) {
|
|
72
|
+
if (!excludePaths) return false;
|
|
73
|
+
return excludePaths.some((p) => pathname.startsWith(p));
|
|
74
|
+
}
|
|
75
|
+
async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
|
|
76
|
+
try {
|
|
77
|
+
const res = await fetch(
|
|
78
|
+
`${apiUrl}/render?url=${encodeURIComponent(url)}`,
|
|
79
|
+
{
|
|
80
|
+
headers: { "User-Agent": userAgent },
|
|
81
|
+
signal: AbortSignal.timeout(1e4)
|
|
82
|
+
// 10s timeout
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
if (res.status === 200) {
|
|
86
|
+
return res.text();
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/netlify.ts
|
|
95
|
+
function createHandler(options = {}) {
|
|
96
|
+
return async function handler(request, context) {
|
|
97
|
+
const userAgent = request.headers.get("user-agent") || "";
|
|
98
|
+
const pathname = new URL(request.url).pathname;
|
|
99
|
+
if (!isBot(userAgent, options.extraBots)) {
|
|
100
|
+
return context.next();
|
|
101
|
+
}
|
|
102
|
+
if (isStaticAsset(pathname)) {
|
|
103
|
+
return context.next();
|
|
104
|
+
}
|
|
105
|
+
if (isExcluded(pathname, options.excludePaths)) {
|
|
106
|
+
return context.next();
|
|
107
|
+
}
|
|
108
|
+
const html = await fetchPrerendered(request.url, userAgent, options.apiUrl);
|
|
109
|
+
if (html) {
|
|
110
|
+
return new Response(html, {
|
|
111
|
+
status: 200,
|
|
112
|
+
headers: {
|
|
113
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
114
|
+
"X-Prerendered": "true",
|
|
115
|
+
"X-Powered-By": "Rank Deploy"
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return context.next();
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
var netlify_default = createHandler();
|
|
123
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
124
|
+
0 && (module.exports = {
|
|
125
|
+
createHandler
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=netlify.js.map
|
|
@@ -0,0 +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":[]}
|
package/dist/netlify.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fetchPrerendered,
|
|
3
|
+
isBot,
|
|
4
|
+
isExcluded,
|
|
5
|
+
isStaticAsset
|
|
6
|
+
} from "./chunk-FPOJYG2E.mjs";
|
|
7
|
+
|
|
8
|
+
// src/netlify.ts
|
|
9
|
+
function createHandler(options = {}) {
|
|
10
|
+
return async function handler(request, context) {
|
|
11
|
+
const userAgent = request.headers.get("user-agent") || "";
|
|
12
|
+
const pathname = new URL(request.url).pathname;
|
|
13
|
+
if (!isBot(userAgent, options.extraBots)) {
|
|
14
|
+
return context.next();
|
|
15
|
+
}
|
|
16
|
+
if (isStaticAsset(pathname)) {
|
|
17
|
+
return context.next();
|
|
18
|
+
}
|
|
19
|
+
if (isExcluded(pathname, options.excludePaths)) {
|
|
20
|
+
return context.next();
|
|
21
|
+
}
|
|
22
|
+
const html = await fetchPrerendered(request.url, userAgent, options.apiUrl);
|
|
23
|
+
if (html) {
|
|
24
|
+
return new Response(html, {
|
|
25
|
+
status: 200,
|
|
26
|
+
headers: {
|
|
27
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
28
|
+
"X-Prerendered": "true",
|
|
29
|
+
"X-Powered-By": "Rank Deploy"
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return context.next();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
var netlify_default = createHandler();
|
|
37
|
+
export {
|
|
38
|
+
createHandler,
|
|
39
|
+
netlify_default as default
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=netlify.mjs.map
|
|
@@ -0,0 +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":[]}
|
package/dist/next.d.mts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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 };
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/next.ts
|
|
21
|
+
var next_exports = {};
|
|
22
|
+
__export(next_exports, {
|
|
23
|
+
config: () => config,
|
|
24
|
+
createMiddleware: () => createMiddleware,
|
|
25
|
+
middleware: () => middleware
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(next_exports);
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var RANKDEPLOY_API = "https://proxy.unikium.com";
|
|
31
|
+
var BOT_USER_AGENTS = [
|
|
32
|
+
"googlebot",
|
|
33
|
+
"bingbot",
|
|
34
|
+
"yandexbot",
|
|
35
|
+
"duckduckbot",
|
|
36
|
+
"baiduspider",
|
|
37
|
+
"slurp",
|
|
38
|
+
"sogou",
|
|
39
|
+
"exabot",
|
|
40
|
+
"facebot",
|
|
41
|
+
"facebookexternalhit",
|
|
42
|
+
"twitterbot",
|
|
43
|
+
"linkedinbot",
|
|
44
|
+
"whatsapp",
|
|
45
|
+
"telegrambot",
|
|
46
|
+
"applebot",
|
|
47
|
+
"discordbot",
|
|
48
|
+
"slackbot",
|
|
49
|
+
"pinterest",
|
|
50
|
+
"screaming frog",
|
|
51
|
+
"ahrefs",
|
|
52
|
+
"semrush",
|
|
53
|
+
"moz.com",
|
|
54
|
+
"rogerbot",
|
|
55
|
+
"dotbot",
|
|
56
|
+
"gptbot",
|
|
57
|
+
"chatgpt",
|
|
58
|
+
"anthropic",
|
|
59
|
+
"claudebot",
|
|
60
|
+
"perplexitybot",
|
|
61
|
+
"cohere"
|
|
62
|
+
];
|
|
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
|
+
function isBot(userAgent, extraBots) {
|
|
65
|
+
const ua = userAgent.toLowerCase();
|
|
66
|
+
const allBots = extraBots ? [...BOT_USER_AGENTS, ...extraBots] : BOT_USER_AGENTS;
|
|
67
|
+
return allBots.some((bot) => ua.includes(bot));
|
|
68
|
+
}
|
|
69
|
+
function isStaticAsset(pathname) {
|
|
70
|
+
return STATIC_EXTENSIONS.test(pathname);
|
|
71
|
+
}
|
|
72
|
+
function isExcluded(pathname, excludePaths) {
|
|
73
|
+
if (!excludePaths) return false;
|
|
74
|
+
return excludePaths.some((p) => pathname.startsWith(p));
|
|
75
|
+
}
|
|
76
|
+
async function fetchPrerendered(url, userAgent, apiUrl = RANKDEPLOY_API) {
|
|
77
|
+
try {
|
|
78
|
+
const res = await fetch(
|
|
79
|
+
`${apiUrl}/render?url=${encodeURIComponent(url)}`,
|
|
80
|
+
{
|
|
81
|
+
headers: { "User-Agent": userAgent },
|
|
82
|
+
signal: AbortSignal.timeout(1e4)
|
|
83
|
+
// 10s timeout
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
if (res.status === 200) {
|
|
87
|
+
return res.text();
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/next.ts
|
|
96
|
+
function createMiddleware(options = {}) {
|
|
97
|
+
return async function middleware2(request) {
|
|
98
|
+
const userAgent = request.headers.get("user-agent") || "";
|
|
99
|
+
const pathname = request.nextUrl.pathname;
|
|
100
|
+
if (!isBot(userAgent, options.extraBots) || isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.apiUrl);
|
|
104
|
+
if (html) {
|
|
105
|
+
return new Response(html, {
|
|
106
|
+
status: 200,
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
109
|
+
"X-Prerendered": "true",
|
|
110
|
+
"X-Powered-By": "Rank Deploy"
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
var middleware = createMiddleware();
|
|
118
|
+
var config = {
|
|
119
|
+
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"]
|
|
120
|
+
};
|
|
121
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
122
|
+
0 && (module.exports = {
|
|
123
|
+
config,
|
|
124
|
+
createMiddleware,
|
|
125
|
+
middleware
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=next.js.map
|
package/dist/next.js.map
ADDED
|
@@ -0,0 +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"]}
|
package/dist/next.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fetchPrerendered,
|
|
3
|
+
isBot,
|
|
4
|
+
isExcluded,
|
|
5
|
+
isStaticAsset
|
|
6
|
+
} from "./chunk-FPOJYG2E.mjs";
|
|
7
|
+
|
|
8
|
+
// src/next.ts
|
|
9
|
+
function createMiddleware(options = {}) {
|
|
10
|
+
return async function middleware2(request) {
|
|
11
|
+
const userAgent = request.headers.get("user-agent") || "";
|
|
12
|
+
const pathname = request.nextUrl.pathname;
|
|
13
|
+
if (!isBot(userAgent, options.extraBots) || isStaticAsset(pathname) || isExcluded(pathname, options.excludePaths)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const html = await fetchPrerendered(request.nextUrl.toString(), userAgent, options.apiUrl);
|
|
17
|
+
if (html) {
|
|
18
|
+
return new Response(html, {
|
|
19
|
+
status: 200,
|
|
20
|
+
headers: {
|
|
21
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
22
|
+
"X-Prerendered": "true",
|
|
23
|
+
"X-Powered-By": "Rank Deploy"
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
var middleware = createMiddleware();
|
|
31
|
+
var config = {
|
|
32
|
+
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"]
|
|
33
|
+
};
|
|
34
|
+
export {
|
|
35
|
+
config,
|
|
36
|
+
createMiddleware,
|
|
37
|
+
middleware
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=next.mjs.map
|
|
@@ -0,0 +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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rankdeploy-middleware",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SEO middleware for Rank Deploy — serves pre-rendered HTML to search engine bots",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./next": {
|
|
15
|
+
"import": "./dist/next.mjs",
|
|
16
|
+
"require": "./dist/next.js",
|
|
17
|
+
"types": "./dist/next.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./netlify": {
|
|
20
|
+
"import": "./dist/netlify.mjs",
|
|
21
|
+
"require": "./dist/netlify.js",
|
|
22
|
+
"types": "./dist/netlify.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./express": {
|
|
25
|
+
"import": "./dist/express.mjs",
|
|
26
|
+
"require": "./dist/express.js",
|
|
27
|
+
"types": "./dist/express.d.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": ["dist", "README.md"],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"prepublishOnly": "npm run build"
|
|
34
|
+
},
|
|
35
|
+
"keywords": ["seo", "prerender", "middleware", "nextjs", "netlify", "express", "rankdeploy", "googlebot"],
|
|
36
|
+
"author": "Rank Deploy",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.8.0"
|
|
41
|
+
}
|
|
42
|
+
}
|