rkk-next 1.0.0 โ 1.1.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 +375 -128
- package/dist/backend/cache.d.ts +49 -0
- package/dist/backend/cache.js +166 -0
- package/dist/backend/middleware.d.ts +43 -0
- package/dist/backend/middleware.js +185 -0
- package/dist/backend/optimization.d.ts +63 -0
- package/dist/backend/optimization.js +186 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/package.json +20 -5
package/README.md
CHANGED
|
@@ -1,218 +1,465 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="./assets/logo.svg" alt="rkk-next logo" width="120" height="120" />
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[](./LICENSE)
|
|
7
|
-
[](https://www.typescriptlang.org/)
|
|
5
|
+
# rkk-next
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
**Production-ready SEO, Performance & Routing SDK for Next.js**
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/rkk-next)
|
|
10
|
+
[](https://www.npmjs.com/package/rkk-next)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
[](https://www.typescriptlang.org/)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
**Enterprise-grade toolkit for building SEO-optimized, lightning-fast Next.js applications**
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
- โ
Centralized meta management (OpenGraph, Twitter Cards)
|
|
18
|
-
- โ
JSON-LD structured data (Schema.org)
|
|
19
|
-
- โ
Canonical URL handling
|
|
20
|
-
- โ
SEO-safe defaults & best practices
|
|
16
|
+
[Get Started](#-quick-start) ยท [Documentation](./docs/DOCS.md) ยท [Examples](./examples/) ยท [Report Bug](https://github.com/ROHIT8759/rkk-next/issues)
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
- โ
Intelligent route prefetching (hover-based)
|
|
24
|
-
- โ
Network-aware prefetching
|
|
25
|
-
- โ
Route change observer with performance metrics
|
|
26
|
-
- โ
Analytics-ready routing events
|
|
18
|
+
</div>
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
- โ
Lazy loading for heavy components
|
|
30
|
-
- โ
Optimized image wrapper (SEO + performance)
|
|
31
|
-
- โ
Cache & CDN header presets
|
|
32
|
-
- โ
Edge-friendly caching strategies
|
|
33
|
-
- โ
Security headers included
|
|
20
|
+
---
|
|
34
21
|
|
|
35
|
-
|
|
36
|
-
- โ
Web Vitals tracking (LCP, FID, CLS, etc.)
|
|
37
|
-
- โ
Route navigation analytics
|
|
38
|
-
- โ
Performance monitoring
|
|
22
|
+
## ๐ฏ Why rkk-next?
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
|
|
24
|
+
Building performant, SEO-optimized Next.js applications requires juggling multiple concerns: meta tags, structured data, route prefetching, image optimization, and caching strategies. **rkk-next** provides production-tested solutions out of the box.
|
|
25
|
+
|
|
26
|
+
**Perfect for:**
|
|
27
|
+
|
|
28
|
+
- ๐ Startups needing rapid development
|
|
29
|
+
- ๐ผ Enterprise applications requiring SEO excellence
|
|
30
|
+
- ๐จ Marketing websites and landing pages
|
|
31
|
+
- ๐ Web3 dashboards and SaaS platforms
|
|
32
|
+
- โก Performance-critical applications
|
|
33
|
+
|
|
34
|
+
## โจ Key Features
|
|
35
|
+
|
|
36
|
+
<table>
|
|
37
|
+
<tr>
|
|
38
|
+
<td width="50%">
|
|
39
|
+
|
|
40
|
+
### ๐ **SEO Excellence**
|
|
41
|
+
|
|
42
|
+
- Comprehensive meta tag management
|
|
43
|
+
- OpenGraph & Twitter Cards
|
|
44
|
+
- JSON-LD structured data (Schema.org)
|
|
45
|
+
- Automatic canonical URLs
|
|
46
|
+
- Server-side rendering optimized
|
|
47
|
+
|
|
48
|
+
</td>
|
|
49
|
+
<td width="50%">
|
|
50
|
+
|
|
51
|
+
### โก **Performance First**
|
|
52
|
+
|
|
53
|
+
- Intelligent route prefetching
|
|
54
|
+
- Network-aware optimizations
|
|
55
|
+
- Lazy loading for heavy components
|
|
56
|
+
- CDN & edge caching strategies
|
|
57
|
+
- Built-in security headers
|
|
58
|
+
|
|
59
|
+
</td>
|
|
60
|
+
</tr>
|
|
61
|
+
<tr>
|
|
62
|
+
<td width="50%">
|
|
63
|
+
|
|
64
|
+
### ๐ **Analytics Ready**
|
|
65
|
+
|
|
66
|
+
- Core Web Vitals tracking
|
|
67
|
+
- Route navigation metrics
|
|
68
|
+
- Performance monitoring
|
|
69
|
+
- Custom event tracking
|
|
70
|
+
- Production-ready insights
|
|
71
|
+
|
|
72
|
+
</td>
|
|
73
|
+
<td width="50%">
|
|
74
|
+
|
|
75
|
+
### โก **Backend Utilities**
|
|
76
|
+
|
|
77
|
+
- Express-like middleware
|
|
78
|
+
- API route optimization
|
|
79
|
+
- Rate limiting & CORS
|
|
80
|
+
- Response caching
|
|
81
|
+
- Request validation
|
|
82
|
+
|
|
83
|
+
</td>
|
|
84
|
+
</tr>
|
|
85
|
+
<tr>
|
|
86
|
+
<td width="50%">
|
|
87
|
+
|
|
88
|
+
### ๐จ **Developer Experience**
|
|
89
|
+
|
|
90
|
+
- Full TypeScript support
|
|
91
|
+
- Zero configuration needed
|
|
92
|
+
- Pages Router & App Router
|
|
93
|
+
- Comprehensive documentation
|
|
94
|
+
- Active maintenance
|
|
95
|
+
|
|
96
|
+
</td>
|
|
97
|
+
</tr>
|
|
98
|
+
</table>
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## ๐ Quick Start
|
|
42
103
|
|
|
104
|
+
### Create New Project (Recommended)
|
|
43
105
|
|
|
44
|
-
|
|
106
|
+
Get started instantly with our CLI tool:
|
|
45
107
|
|
|
108
|
+
```bash
|
|
109
|
+
npx create-next-rkk@latest my-app
|
|
110
|
+
cd my-app
|
|
111
|
+
npm run dev
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Add to Existing Project
|
|
115
|
+
|
|
116
|
+
Install into your existing Next.js application:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm install rkk-next
|
|
120
|
+
# or
|
|
46
121
|
yarn add rkk-next
|
|
122
|
+
# or
|
|
123
|
+
pnpm add rkk-next
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
47
127
|
|
|
48
|
-
|
|
49
|
-
|
|
128
|
+
## ๐ Usage Examples
|
|
129
|
+
|
|
130
|
+
### SEO Meta Management
|
|
131
|
+
|
|
132
|
+
Centralize your SEO configuration with type-safe components:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
50
135
|
import { MetaManager } from "rkk-next";
|
|
51
136
|
|
|
52
|
-
export default function
|
|
137
|
+
export default function HomePage() {
|
|
53
138
|
return (
|
|
54
139
|
<>
|
|
55
140
|
<MetaManager
|
|
56
141
|
title="Home | My App"
|
|
57
|
-
description="
|
|
58
|
-
keywords="Next.js, SEO, Performance"
|
|
59
|
-
image="/og.png"
|
|
142
|
+
description="Production-ready Next.js application with enterprise SEO"
|
|
143
|
+
keywords="Next.js, React, SEO, Performance"
|
|
144
|
+
image="https://myapp.com/og-image.png"
|
|
60
145
|
siteName="My App"
|
|
146
|
+
twitterHandle="myhandle"
|
|
61
147
|
/>
|
|
62
148
|
|
|
63
|
-
<
|
|
149
|
+
<main>{/* Your content */}</main>
|
|
64
150
|
</>
|
|
65
151
|
);
|
|
66
152
|
}
|
|
153
|
+
```
|
|
67
154
|
|
|
68
|
-
|
|
69
|
-
import { JsonLd } from "rkk-next";
|
|
155
|
+
### Structured Data (JSON-LD)
|
|
70
156
|
|
|
71
|
-
|
|
72
|
-
type="WebSite"
|
|
73
|
-
data={{
|
|
74
|
-
name: "My App",
|
|
75
|
-
url: "https://myapp.com",
|
|
76
|
-
}}
|
|
77
|
-
/>
|
|
157
|
+
Improve search engine understanding with structured data:
|
|
78
158
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
import { SmartLink } from "rkk-next";
|
|
159
|
+
```tsx
|
|
160
|
+
import { JsonLd } from "rkk-next";
|
|
82
161
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
162
|
+
export default function ArticlePage() {
|
|
163
|
+
return (
|
|
164
|
+
<>
|
|
165
|
+
<JsonLd
|
|
166
|
+
type="Article"
|
|
167
|
+
data={{
|
|
168
|
+
headline: "Advanced Next.js SEO Techniques",
|
|
169
|
+
image: "https://myapp.com/article.jpg",
|
|
170
|
+
datePublished: "2025-12-18T08:00:00.000Z",
|
|
171
|
+
author: {
|
|
172
|
+
"@type": "Person",
|
|
173
|
+
name: "John Doe",
|
|
174
|
+
},
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
86
177
|
|
|
178
|
+
<article>{/* Article content */}</article>
|
|
179
|
+
</>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
87
183
|
|
|
88
|
-
|
|
89
|
-
โ Network-aware
|
|
90
|
-
โ SEO-safe <a> tag
|
|
184
|
+
### Smart Routing & Prefetching
|
|
91
185
|
|
|
92
|
-
|
|
93
|
-
import { observeRoutes } from "rkk-next";
|
|
186
|
+
Enhance navigation performance with intelligent prefetching:
|
|
94
187
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
188
|
+
```tsx
|
|
189
|
+
import { SmartLink, observeRoutes } from "rkk-next";
|
|
190
|
+
import { useEffect } from "react";
|
|
98
191
|
|
|
99
|
-
|
|
100
|
-
|
|
192
|
+
export default function Navigation() {
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
// Track route changes for analytics
|
|
195
|
+
const unsubscribe = observeRoutes((event) => {
|
|
196
|
+
analytics.track("page_view", {
|
|
197
|
+
url: event.url,
|
|
198
|
+
duration: event.duration,
|
|
199
|
+
});
|
|
200
|
+
});
|
|
101
201
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
alt="Landing page hero image"
|
|
105
|
-
width={1200}
|
|
106
|
-
height={630}
|
|
107
|
-
priority
|
|
108
|
-
/>
|
|
202
|
+
return unsubscribe;
|
|
203
|
+
}, []);
|
|
109
204
|
|
|
205
|
+
return (
|
|
206
|
+
<nav>
|
|
207
|
+
<SmartLink href="/products" prefetchOnHover>
|
|
208
|
+
Products
|
|
209
|
+
</SmartLink>
|
|
210
|
+
</nav>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
```
|
|
110
214
|
|
|
111
|
-
|
|
112
|
-
โ Responsive sizes
|
|
113
|
-
โ LCP optimized
|
|
215
|
+
### Optimized Images
|
|
114
216
|
|
|
115
|
-
|
|
116
|
-
import { lazyImport } from "rkk-next";
|
|
217
|
+
Ensure SEO-compliant and performant images:
|
|
117
218
|
|
|
118
|
-
|
|
219
|
+
```tsx
|
|
220
|
+
import { OptimizedImage } from "rkk-next";
|
|
119
221
|
|
|
120
|
-
export default function
|
|
121
|
-
return
|
|
222
|
+
export default function Hero() {
|
|
223
|
+
return (
|
|
224
|
+
<OptimizedImage
|
|
225
|
+
src="/hero-banner.jpg"
|
|
226
|
+
alt="Professional hero banner showcasing our product"
|
|
227
|
+
width={1920}
|
|
228
|
+
height={1080}
|
|
229
|
+
priority // For above-the-fold images
|
|
230
|
+
quality={85}
|
|
231
|
+
/>
|
|
232
|
+
);
|
|
122
233
|
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Code Splitting & Lazy Loading
|
|
123
237
|
|
|
124
|
-
|
|
125
|
-
import { prefetchRoute, isFastConnection } from "rkk-next";
|
|
238
|
+
Reduce initial bundle size with intelligent lazy loading:
|
|
126
239
|
|
|
127
|
-
|
|
128
|
-
|
|
240
|
+
```tsx
|
|
241
|
+
import { lazyImport, DefaultLoader } from "rkk-next";
|
|
242
|
+
|
|
243
|
+
// Heavy component loaded on-demand
|
|
244
|
+
const AnalyticsDashboard = lazyImport(() => import("./AnalyticsDashboard"), {
|
|
245
|
+
loading: DefaultLoader,
|
|
246
|
+
ssr: false,
|
|
247
|
+
delay: 100,
|
|
129
248
|
});
|
|
130
249
|
|
|
131
|
-
|
|
250
|
+
export default function Dashboard() {
|
|
251
|
+
return (
|
|
252
|
+
<main>
|
|
253
|
+
<h1>Dashboard</h1>
|
|
254
|
+
<AnalyticsDashboard />
|
|
255
|
+
</main>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Performance-Optimized Caching
|
|
132
261
|
|
|
133
|
-
|
|
262
|
+
Configure production-grade caching in `next.config.js`:
|
|
134
263
|
|
|
264
|
+
```javascript
|
|
135
265
|
const {
|
|
136
266
|
LONG_TERM_CACHE,
|
|
137
267
|
EDGE_CACHE,
|
|
138
268
|
NO_CACHE,
|
|
269
|
+
SECURITY_HEADERS,
|
|
139
270
|
applyCache,
|
|
140
|
-
} = require("rkk-next");
|
|
271
|
+
} = require("rkk-next/performance/cacheHeaders");
|
|
141
272
|
|
|
142
273
|
module.exports = {
|
|
143
274
|
async headers() {
|
|
144
275
|
return [
|
|
276
|
+
// Static assets: aggressive caching
|
|
145
277
|
applyCache("/_next/static/:path*", LONG_TERM_CACHE),
|
|
278
|
+
applyCache("/images/:path*", LONG_TERM_CACHE),
|
|
279
|
+
|
|
280
|
+
// API routes: edge caching
|
|
146
281
|
applyCache("/api/public/:path*", EDGE_CACHE),
|
|
282
|
+
|
|
283
|
+
// User-specific pages: no cache
|
|
147
284
|
applyCache("/dashboard/:path*", NO_CACHE),
|
|
285
|
+
|
|
286
|
+
// Security headers for all routes
|
|
287
|
+
{
|
|
288
|
+
source: "/:path*",
|
|
289
|
+
headers: SECURITY_HEADERS,
|
|
290
|
+
},
|
|
148
291
|
];
|
|
149
292
|
},
|
|
150
293
|
};
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Backend API Utilities
|
|
297
|
+
|
|
298
|
+
Build robust Next.js API routes with Express-like middleware:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// pages/api/users/[id].ts
|
|
302
|
+
import { NextApiRequest, NextApiResponse } from "next";
|
|
303
|
+
import {
|
|
304
|
+
composeMiddleware,
|
|
305
|
+
cors,
|
|
306
|
+
rateLimit,
|
|
307
|
+
validateRequest,
|
|
308
|
+
logger,
|
|
309
|
+
errorHandler,
|
|
310
|
+
cacheResponse,
|
|
311
|
+
jsonResponse,
|
|
312
|
+
allowMethods,
|
|
313
|
+
} from "rkk-next";
|
|
314
|
+
|
|
315
|
+
// Compose middleware chain
|
|
316
|
+
const handler = composeMiddleware(
|
|
317
|
+
cors({ origin: "https://yourdomain.com" }),
|
|
318
|
+
rateLimit({ maxRequests: 100, windowMs: 60000 }),
|
|
319
|
+
logger(),
|
|
320
|
+
allowMethods(["GET", "PUT", "DELETE"]),
|
|
321
|
+
cacheResponse({ ttl: 300 }), // Cache for 5 minutes
|
|
322
|
+
validateRequest((req) => {
|
|
323
|
+
if (req.method === "PUT" && !req.body.name) {
|
|
324
|
+
return "Name is required";
|
|
325
|
+
}
|
|
326
|
+
}),
|
|
327
|
+
errorHandler()
|
|
328
|
+
)(async (req: NextApiRequest, res: NextApiResponse) => {
|
|
329
|
+
const { id } = req.query;
|
|
330
|
+
|
|
331
|
+
// Your API logic
|
|
332
|
+
const user = await getUserById(id as string);
|
|
333
|
+
|
|
334
|
+
return jsonResponse(res, {
|
|
335
|
+
success: true,
|
|
336
|
+
data: user,
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
export default handler;
|
|
341
|
+
```
|
|
151
342
|
|
|
152
|
-
|
|
343
|
+
**Server-side caching with automatic TTL:**
|
|
153
344
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
| MetaManager | โ
| โ
(via generateAppMetadata) |
|
|
157
|
-
| JsonLd | โ
| โ
|
|
|
158
|
-
| SmartLink | โ
| โ ๏ธ (use for internal links only) |
|
|
159
|
-
| Routing Observer | โ
| โ ๏ธ (Pages Router recommended) |
|
|
160
|
-
| OptimizedImage | โ
| โ
|
|
|
161
|
-
| Lazy Loading | โ
| โ
|
|
|
162
|
-
| Cache Headers | โ
| โ
|
|
|
163
|
-
| Web Vitals | โ
| โ
|
|
|
345
|
+
```typescript
|
|
346
|
+
import { cache, memoize } from "rkk-next";
|
|
164
347
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
348
|
+
// Cache expensive operations
|
|
349
|
+
const expensiveQuery = memoize(
|
|
350
|
+
async (userId: string) => {
|
|
351
|
+
return await database.query(/* ... */);
|
|
352
|
+
},
|
|
353
|
+
{ ttl: 600 } // 10 minutes
|
|
354
|
+
);
|
|
170
355
|
|
|
171
|
-
|
|
356
|
+
// Manual cache control
|
|
357
|
+
cache.set("user:123", userData, 300);
|
|
358
|
+
const cachedUser = cache.get("user:123");
|
|
359
|
+
```
|
|
172
360
|
|
|
173
|
-
|
|
361
|
+
See [Backend Utilities Documentation](docs/BACKEND.md) for complete API reference.
|
|
174
362
|
|
|
175
|
-
|
|
363
|
+
---
|
|
176
364
|
|
|
177
|
-
|
|
365
|
+
## ๐งฉ Compatibility Matrix
|
|
178
366
|
|
|
179
|
-
|
|
367
|
+
| Feature | Pages Router | App Router | Notes |
|
|
368
|
+
| -------------- | :----------: | :--------: | ------------------------------------- |
|
|
369
|
+
| MetaManager | โ
| โ
| App Router uses `generateAppMetadata` |
|
|
370
|
+
| JsonLd | โ
| โ
| Works with both routers |
|
|
371
|
+
| SmartLink | โ
| โ ๏ธ | Recommended for Pages Router |
|
|
372
|
+
| RouteObserver | โ
| โ ๏ธ | Pages Router only |
|
|
373
|
+
| OptimizedImage | โ
| โ
| Full support both routers |
|
|
374
|
+
| Lazy Loading | โ
| โ
| Dynamic imports supported |
|
|
375
|
+
| Cache Headers | โ
| โ
| Universal support |
|
|
376
|
+
| Web Vitals | โ
| โ
| Analytics integration |
|
|
377
|
+
| Backend Utils | โ
| โ
| API routes middleware |
|
|
180
378
|
|
|
181
|
-
|
|
379
|
+
**System Requirements:**
|
|
182
380
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
381
|
+
- Next.js `>= 12.0.0`
|
|
382
|
+
- React `>= 17.0.0`
|
|
383
|
+
- Node.js `>= 16.0.0`
|
|
384
|
+
- TypeScript `>= 4.5.0` (optional but recommended)
|
|
186
385
|
|
|
187
|
-
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## ๐ Learn More
|
|
389
|
+
|
|
390
|
+
### ๐ Documentation
|
|
188
391
|
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
392
|
+
- [Complete Documentation](./docs/DOCS.md) - Comprehensive API reference
|
|
393
|
+
- [Quick Start Guide](./docs/QUICKSTART.md) - Get running in 5 minutes
|
|
394
|
+
- [Migration Guide](./docs/DOCS.md) - Upgrade from other solutions
|
|
395
|
+
- [Best Practices](./docs/DOCS.md#best-practices) - Production tips
|
|
193
396
|
|
|
194
397
|
## ๐ค Contributing
|
|
195
398
|
|
|
196
|
-
We welcome contributions
|
|
399
|
+
We welcome contributions from the community! Whether it's:
|
|
400
|
+
|
|
401
|
+
- ๐ Bug reports and fixes
|
|
402
|
+
- โจ New features and enhancements
|
|
403
|
+
- ๐ Documentation improvements
|
|
404
|
+
- ๐ก Feature suggestions
|
|
405
|
+
|
|
406
|
+
**Getting Started:**
|
|
197
407
|
|
|
198
408
|
1. Fork the repository
|
|
199
|
-
2. Create
|
|
200
|
-
3.
|
|
201
|
-
4.
|
|
202
|
-
5.
|
|
409
|
+
2. Create a feature branch: `git checkout -b feature/amazing-feature`
|
|
410
|
+
3. Make your changes with clear commit messages
|
|
411
|
+
4. Write or update tests as needed
|
|
412
|
+
5. Submit a pull request
|
|
203
413
|
|
|
204
|
-
|
|
414
|
+
See [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for detailed guidelines.
|
|
205
415
|
|
|
206
|
-
|
|
207
|
-
- [ ] Expand Web Vitals analytics integration
|
|
208
|
-
- [ ] Create demo app showcase
|
|
209
|
-
- [ ] Add Lighthouse CI integration
|
|
210
|
-
- [ ] Video tutorials & guides
|
|
416
|
+
---
|
|
211
417
|
|
|
212
418
|
## ๐ License
|
|
213
419
|
|
|
214
420
|
MIT License ยฉ 2025 [Rohit Kumar Kundu](https://github.com/ROHIT8759)
|
|
215
421
|
|
|
422
|
+
Free for commercial and personal use. See [LICENSE](./LICENSE) for details.
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## ๐ Support & Community
|
|
427
|
+
|
|
428
|
+
### Get Help
|
|
429
|
+
|
|
430
|
+
- ๐ [Documentation](./docs/DOCS.md)
|
|
431
|
+
- ๐ฌ [GitHub Discussions](https://github.com/ROHIT8759/rkk-next/discussions)
|
|
432
|
+
- ๐ [Issue Tracker](https://github.com/ROHIT8759/rkk-next/issues)
|
|
433
|
+
|
|
434
|
+
### Show Your Support
|
|
435
|
+
|
|
436
|
+
If rkk-next helps your project:
|
|
437
|
+
|
|
438
|
+
- โญ Star the repository
|
|
439
|
+
- ๐ฆ Share on social media
|
|
440
|
+
- ๐ Write about your experience
|
|
441
|
+
- ๐ค Contribute back to the project
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## ๐งโ๐ป Author
|
|
446
|
+
|
|
447
|
+
**Rohit Kumar Kundu**
|
|
448
|
+
Full-Stack Developer | Next.js & Web3 Specialist
|
|
449
|
+
|
|
450
|
+
๐ [GitHub](https://github.com/ROHIT8759) ยท [LinkedIn](https://linkedin.com/in/rohit-kumar-kundu) ยท [Portfolio](https://rohitkundu.dev)
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
<div align="center">
|
|
455
|
+
|
|
456
|
+
**Built with โค๏ธ for the Next.js community**
|
|
457
|
+
|
|
458
|
+
[Get Started](./docs/QUICKSTART.md) ยท [Documentation](./docs/DOCS.md) ยท [Examples](./examples/) ยท [Changelog](./CHANGELOG.md)
|
|
459
|
+
|
|
460
|
+
</div>
|
|
461
|
+
MIT License ยฉ 2025 [Rohit Kumar Kundu](https://github.com/ROHIT8759)
|
|
462
|
+
|
|
216
463
|
Free to use, modify, and distribute. See [LICENSE](./LICENSE) for details.
|
|
217
464
|
|
|
218
465
|
## โญ Support the Project
|
|
@@ -245,4 +492,4 @@ If you want, I can now:
|
|
|
245
492
|
|
|
246
493
|
โ Final SDK audit before release
|
|
247
494
|
|
|
248
|
-
Just tell me ๐
|
|
495
|
+
Just tell me ๐
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
export interface CacheOptions {
|
|
3
|
+
/** Time to live in seconds */
|
|
4
|
+
ttl?: number;
|
|
5
|
+
/** Cache key generator function */
|
|
6
|
+
keyGenerator?: (req: NextApiRequest) => string;
|
|
7
|
+
/** Conditional caching based on request */
|
|
8
|
+
shouldCache?: (req: NextApiRequest) => boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* In-memory cache store for API responses
|
|
12
|
+
*/
|
|
13
|
+
declare class MemoryCache {
|
|
14
|
+
private cache;
|
|
15
|
+
set<T = any>(key: string, data: T, ttl: number): void;
|
|
16
|
+
get<T = any>(key: string): T | null;
|
|
17
|
+
has(key: string): boolean;
|
|
18
|
+
delete(key: string): void;
|
|
19
|
+
clear(): void;
|
|
20
|
+
size(): number;
|
|
21
|
+
/**
|
|
22
|
+
* Clean up expired entries
|
|
23
|
+
*/
|
|
24
|
+
cleanup(): void;
|
|
25
|
+
}
|
|
26
|
+
export declare const cache: MemoryCache;
|
|
27
|
+
/**
|
|
28
|
+
* Cache API response middleware
|
|
29
|
+
*/
|
|
30
|
+
export declare function cacheResponse(options?: CacheOptions): (req: NextApiRequest, res: NextApiResponse, next: () => void | Promise<void>) => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Invalidate cache by pattern or specific key
|
|
33
|
+
*/
|
|
34
|
+
export declare function invalidateCache(pattern?: string | RegExp): void;
|
|
35
|
+
/**
|
|
36
|
+
* Get cache statistics
|
|
37
|
+
*/
|
|
38
|
+
export declare function getCacheStats(): {
|
|
39
|
+
size: number;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Memoize function results with TTL
|
|
44
|
+
*/
|
|
45
|
+
export declare function memoize<T extends (...args: any[]) => any>(fn: T, options?: {
|
|
46
|
+
ttl?: number;
|
|
47
|
+
keyGenerator?: (...args: Parameters<T>) => string;
|
|
48
|
+
}): T;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cache = void 0;
|
|
4
|
+
exports.cacheResponse = cacheResponse;
|
|
5
|
+
exports.invalidateCache = invalidateCache;
|
|
6
|
+
exports.getCacheStats = getCacheStats;
|
|
7
|
+
exports.memoize = memoize;
|
|
8
|
+
/**
|
|
9
|
+
* In-memory cache store for API responses
|
|
10
|
+
*/
|
|
11
|
+
class MemoryCache {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.cache = new Map();
|
|
14
|
+
}
|
|
15
|
+
set(key, data, ttl) {
|
|
16
|
+
this.cache.set(key, {
|
|
17
|
+
data,
|
|
18
|
+
timestamp: Date.now(),
|
|
19
|
+
ttl: ttl * 1000, // Convert to milliseconds
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
get(key) {
|
|
23
|
+
const entry = this.cache.get(key);
|
|
24
|
+
if (!entry) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const isExpired = Date.now() - entry.timestamp > entry.ttl;
|
|
28
|
+
if (isExpired) {
|
|
29
|
+
this.cache.delete(key);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return entry.data;
|
|
33
|
+
}
|
|
34
|
+
has(key) {
|
|
35
|
+
return this.get(key) !== null;
|
|
36
|
+
}
|
|
37
|
+
delete(key) {
|
|
38
|
+
this.cache.delete(key);
|
|
39
|
+
}
|
|
40
|
+
clear() {
|
|
41
|
+
this.cache.clear();
|
|
42
|
+
}
|
|
43
|
+
size() {
|
|
44
|
+
return this.cache.size;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Clean up expired entries
|
|
48
|
+
*/
|
|
49
|
+
cleanup() {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
52
|
+
if (now - entry.timestamp > entry.ttl) {
|
|
53
|
+
this.cache.delete(key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.cache = new MemoryCache();
|
|
59
|
+
// Periodic cleanup every 5 minutes
|
|
60
|
+
if (typeof setInterval !== 'undefined') {
|
|
61
|
+
setInterval(() => exports.cache.cleanup(), 5 * 60 * 1000);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Default cache key generator
|
|
65
|
+
*/
|
|
66
|
+
function defaultKeyGenerator(req) {
|
|
67
|
+
const method = req.method || 'GET';
|
|
68
|
+
const url = req.url || '';
|
|
69
|
+
const query = JSON.stringify(req.query || {});
|
|
70
|
+
return `${method}:${url}:${query}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Cache API response middleware
|
|
74
|
+
*/
|
|
75
|
+
function cacheResponse(options = {}) {
|
|
76
|
+
const { ttl = 60, // Default 60 seconds
|
|
77
|
+
keyGenerator = defaultKeyGenerator, shouldCache = (req) => req.method === 'GET', } = options;
|
|
78
|
+
return async (req, res, next) => {
|
|
79
|
+
// Skip caching if condition not met
|
|
80
|
+
if (!shouldCache(req)) {
|
|
81
|
+
await next();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const cacheKey = keyGenerator(req);
|
|
85
|
+
// Check if cached response exists
|
|
86
|
+
const cachedData = exports.cache.get(cacheKey);
|
|
87
|
+
if (cachedData) {
|
|
88
|
+
res.setHeader('X-Cache', 'HIT');
|
|
89
|
+
res.setHeader('Cache-Control', `public, max-age=${ttl}`);
|
|
90
|
+
res.status(200).json(cachedData);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Intercept response to cache it
|
|
94
|
+
const originalJson = res.json;
|
|
95
|
+
const originalSend = res.send;
|
|
96
|
+
let responseData;
|
|
97
|
+
res.json = function (data) {
|
|
98
|
+
responseData = data;
|
|
99
|
+
return originalJson.call(this, data);
|
|
100
|
+
};
|
|
101
|
+
res.send = function (data) {
|
|
102
|
+
responseData = data;
|
|
103
|
+
return originalSend.call(this, data);
|
|
104
|
+
};
|
|
105
|
+
const originalEnd = res.end;
|
|
106
|
+
res.end = function (...args) {
|
|
107
|
+
// Only cache successful responses
|
|
108
|
+
if (res.statusCode >= 200 && res.statusCode < 300 && responseData) {
|
|
109
|
+
exports.cache.set(cacheKey, responseData, ttl);
|
|
110
|
+
res.setHeader('X-Cache', 'MISS');
|
|
111
|
+
res.setHeader('Cache-Control', `public, max-age=${ttl}`);
|
|
112
|
+
}
|
|
113
|
+
return originalEnd.apply(this, args);
|
|
114
|
+
};
|
|
115
|
+
await next();
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Invalidate cache by pattern or specific key
|
|
120
|
+
*/
|
|
121
|
+
function invalidateCache(pattern) {
|
|
122
|
+
if (!pattern) {
|
|
123
|
+
exports.cache.clear();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const regex = typeof pattern === 'string'
|
|
127
|
+
? new RegExp(pattern)
|
|
128
|
+
: pattern;
|
|
129
|
+
// This is a simplified implementation
|
|
130
|
+
// In production, you'd want a more efficient pattern matching
|
|
131
|
+
exports.cache.clear(); // For now, clear all
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get cache statistics
|
|
135
|
+
*/
|
|
136
|
+
function getCacheStats() {
|
|
137
|
+
return {
|
|
138
|
+
size: exports.cache.size(),
|
|
139
|
+
timestamp: new Date().toISOString(),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Memoize function results with TTL
|
|
144
|
+
*/
|
|
145
|
+
function memoize(fn, options = {}) {
|
|
146
|
+
const { ttl = 60, keyGenerator = (...args) => JSON.stringify(args), } = options;
|
|
147
|
+
const memoCache = new Map();
|
|
148
|
+
return ((...args) => {
|
|
149
|
+
const key = keyGenerator(...args);
|
|
150
|
+
const cached = memoCache.get(key);
|
|
151
|
+
if (cached) {
|
|
152
|
+
const isExpired = Date.now() - cached.timestamp > cached.ttl;
|
|
153
|
+
if (!isExpired) {
|
|
154
|
+
return cached.data;
|
|
155
|
+
}
|
|
156
|
+
memoCache.delete(key);
|
|
157
|
+
}
|
|
158
|
+
const result = fn(...args);
|
|
159
|
+
memoCache.set(key, {
|
|
160
|
+
data: result,
|
|
161
|
+
timestamp: Date.now(),
|
|
162
|
+
ttl: ttl * 1000,
|
|
163
|
+
});
|
|
164
|
+
return result;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
export type NextApiHandler = (req: NextApiRequest, res: NextApiResponse) => Promise<void> | void;
|
|
3
|
+
export type Middleware = (req: NextApiRequest, res: NextApiResponse, next: () => void | Promise<void>) => void | Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* Compose multiple middleware functions into a single handler
|
|
6
|
+
*/
|
|
7
|
+
export declare function composeMiddleware(...middlewares: Middleware[]): (handler: NextApiHandler) => NextApiHandler;
|
|
8
|
+
/**
|
|
9
|
+
* CORS middleware for Next.js API routes
|
|
10
|
+
*/
|
|
11
|
+
export declare function cors(options?: {
|
|
12
|
+
origin?: string | string[];
|
|
13
|
+
methods?: string[];
|
|
14
|
+
credentials?: boolean;
|
|
15
|
+
allowedHeaders?: string[];
|
|
16
|
+
}): Middleware;
|
|
17
|
+
/**
|
|
18
|
+
* Rate limiting middleware
|
|
19
|
+
*/
|
|
20
|
+
export declare function rateLimit(options?: {
|
|
21
|
+
windowMs?: number;
|
|
22
|
+
max?: number;
|
|
23
|
+
message?: string;
|
|
24
|
+
}): Middleware;
|
|
25
|
+
/**
|
|
26
|
+
* Request validation middleware
|
|
27
|
+
*/
|
|
28
|
+
export declare function validateRequest(schema: {
|
|
29
|
+
body?: (data: any) => boolean;
|
|
30
|
+
query?: (data: any) => boolean;
|
|
31
|
+
headers?: (data: any) => boolean;
|
|
32
|
+
}): Middleware;
|
|
33
|
+
/**
|
|
34
|
+
* Request logging middleware
|
|
35
|
+
*/
|
|
36
|
+
export declare function logger(options?: {
|
|
37
|
+
verbose?: boolean;
|
|
38
|
+
includeBody?: boolean;
|
|
39
|
+
}): Middleware;
|
|
40
|
+
/**
|
|
41
|
+
* Error handling middleware
|
|
42
|
+
*/
|
|
43
|
+
export declare function errorHandler(): Middleware;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.composeMiddleware = composeMiddleware;
|
|
4
|
+
exports.cors = cors;
|
|
5
|
+
exports.rateLimit = rateLimit;
|
|
6
|
+
exports.validateRequest = validateRequest;
|
|
7
|
+
exports.logger = logger;
|
|
8
|
+
exports.errorHandler = errorHandler;
|
|
9
|
+
/**
|
|
10
|
+
* Compose multiple middleware functions into a single handler
|
|
11
|
+
*/
|
|
12
|
+
function composeMiddleware(...middlewares) {
|
|
13
|
+
return (handler) => {
|
|
14
|
+
return async (req, res) => {
|
|
15
|
+
let index = 0;
|
|
16
|
+
const next = async () => {
|
|
17
|
+
if (index < middlewares.length) {
|
|
18
|
+
const middleware = middlewares[index++];
|
|
19
|
+
await middleware(req, res, next);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
await handler(req, res);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
await next();
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* CORS middleware for Next.js API routes
|
|
31
|
+
*/
|
|
32
|
+
function cors(options = {}) {
|
|
33
|
+
const { origin = '*', methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], credentials = true, allowedHeaders = ['Content-Type', 'Authorization'], } = options;
|
|
34
|
+
return (req, res, next) => {
|
|
35
|
+
const requestOrigin = req.headers.origin || '*';
|
|
36
|
+
if (Array.isArray(origin)) {
|
|
37
|
+
if (origin.includes(requestOrigin)) {
|
|
38
|
+
res.setHeader('Access-Control-Allow-Origin', requestOrigin);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else if (origin === '*' || origin === requestOrigin) {
|
|
42
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
43
|
+
}
|
|
44
|
+
res.setHeader('Access-Control-Allow-Methods', methods.join(', '));
|
|
45
|
+
res.setHeader('Access-Control-Allow-Headers', allowedHeaders.join(', '));
|
|
46
|
+
if (credentials) {
|
|
47
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
48
|
+
}
|
|
49
|
+
// Handle preflight requests
|
|
50
|
+
if (req.method === 'OPTIONS') {
|
|
51
|
+
res.status(200).end();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
next();
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Rate limiting middleware
|
|
59
|
+
*/
|
|
60
|
+
function rateLimit(options = {}) {
|
|
61
|
+
const { windowMs = 60000, // 1 minute
|
|
62
|
+
max = 60, // 60 requests per minute
|
|
63
|
+
message = 'Too many requests, please try again later.', } = options;
|
|
64
|
+
const requests = new Map();
|
|
65
|
+
return (req, res, next) => {
|
|
66
|
+
const identifier = req.headers['x-forwarded-for'] ||
|
|
67
|
+
req.socket.remoteAddress ||
|
|
68
|
+
'unknown';
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const windowStart = now - windowMs;
|
|
71
|
+
// Get existing requests for this identifier
|
|
72
|
+
let userRequests = requests.get(identifier) || [];
|
|
73
|
+
// Filter out requests outside the time window
|
|
74
|
+
userRequests = userRequests.filter(time => time > windowStart);
|
|
75
|
+
if (userRequests.length >= max) {
|
|
76
|
+
res.status(429).json({ error: message });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Add current request
|
|
80
|
+
userRequests.push(now);
|
|
81
|
+
requests.set(identifier, userRequests);
|
|
82
|
+
// Cleanup old entries periodically
|
|
83
|
+
if (Math.random() < 0.01) {
|
|
84
|
+
for (const [key, times] of requests.entries()) {
|
|
85
|
+
const filteredTimes = times.filter(time => time > windowStart);
|
|
86
|
+
if (filteredTimes.length === 0) {
|
|
87
|
+
requests.delete(key);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
requests.set(key, filteredTimes);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
next();
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Request validation middleware
|
|
99
|
+
*/
|
|
100
|
+
function validateRequest(schema) {
|
|
101
|
+
return (req, res, next) => {
|
|
102
|
+
try {
|
|
103
|
+
if (schema.body && !schema.body(req.body)) {
|
|
104
|
+
res.status(400).json({ error: 'Invalid request body' });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (schema.query && !schema.query(req.query)) {
|
|
108
|
+
res.status(400).json({ error: 'Invalid query parameters' });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (schema.headers && !schema.headers(req.headers)) {
|
|
112
|
+
res.status(400).json({ error: 'Invalid headers' });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
next();
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
res.status(400).json({ error: 'Validation error' });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Request logging middleware
|
|
124
|
+
*/
|
|
125
|
+
function logger(options = {}) {
|
|
126
|
+
const { verbose = false, includeBody = false } = options;
|
|
127
|
+
return (req, res, next) => {
|
|
128
|
+
const start = Date.now();
|
|
129
|
+
const originalJson = res.json;
|
|
130
|
+
const originalSend = res.send;
|
|
131
|
+
const originalEnd = res.end;
|
|
132
|
+
let responseBody;
|
|
133
|
+
res.json = function (body) {
|
|
134
|
+
responseBody = body;
|
|
135
|
+
return originalJson.call(this, body);
|
|
136
|
+
};
|
|
137
|
+
res.send = function (body) {
|
|
138
|
+
responseBody = body;
|
|
139
|
+
return originalSend.call(this, body);
|
|
140
|
+
};
|
|
141
|
+
res.end = function (...args) {
|
|
142
|
+
const duration = Date.now() - start;
|
|
143
|
+
const logData = {
|
|
144
|
+
method: req.method,
|
|
145
|
+
url: req.url,
|
|
146
|
+
status: res.statusCode,
|
|
147
|
+
duration: `${duration}ms`,
|
|
148
|
+
};
|
|
149
|
+
if (verbose) {
|
|
150
|
+
logData.headers = req.headers;
|
|
151
|
+
logData.query = req.query;
|
|
152
|
+
}
|
|
153
|
+
if (includeBody && req.body) {
|
|
154
|
+
logData.requestBody = req.body;
|
|
155
|
+
}
|
|
156
|
+
console.log(`[API] ${req.method} ${req.url} - ${res.statusCode} (${duration}ms)`);
|
|
157
|
+
if (verbose) {
|
|
158
|
+
console.log(JSON.stringify(logData, null, 2));
|
|
159
|
+
}
|
|
160
|
+
return originalEnd.apply(this, args);
|
|
161
|
+
};
|
|
162
|
+
next();
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Error handling middleware
|
|
167
|
+
*/
|
|
168
|
+
function errorHandler() {
|
|
169
|
+
return async (req, res, next) => {
|
|
170
|
+
try {
|
|
171
|
+
await next();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.error('[API Error]', error);
|
|
175
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
176
|
+
res.status(500).json({
|
|
177
|
+
error: 'Internal server error',
|
|
178
|
+
...(isDev && {
|
|
179
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
180
|
+
stack: error instanceof Error ? error.stack : undefined
|
|
181
|
+
}),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { Middleware } from './middleware';
|
|
3
|
+
/**
|
|
4
|
+
* Compression middleware for API responses
|
|
5
|
+
*/
|
|
6
|
+
export declare function compress(options?: {
|
|
7
|
+
threshold?: number;
|
|
8
|
+
level?: number;
|
|
9
|
+
}): Middleware;
|
|
10
|
+
/**
|
|
11
|
+
* Response time tracking
|
|
12
|
+
*/
|
|
13
|
+
export declare function responseTime(): Middleware;
|
|
14
|
+
/**
|
|
15
|
+
* Pagination helper for API responses
|
|
16
|
+
*/
|
|
17
|
+
export interface PaginationOptions {
|
|
18
|
+
page?: number;
|
|
19
|
+
limit?: number;
|
|
20
|
+
maxLimit?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface PaginatedResponse<T> {
|
|
23
|
+
data: T[];
|
|
24
|
+
pagination: {
|
|
25
|
+
page: number;
|
|
26
|
+
limit: number;
|
|
27
|
+
total: number;
|
|
28
|
+
totalPages: number;
|
|
29
|
+
hasNext: boolean;
|
|
30
|
+
hasPrev: boolean;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export declare function paginate<T>(data: T[], options?: PaginationOptions): PaginatedResponse<T>;
|
|
34
|
+
/**
|
|
35
|
+
* Parse pagination params from request
|
|
36
|
+
*/
|
|
37
|
+
export declare function getPaginationParams(req: NextApiRequest): PaginationOptions;
|
|
38
|
+
/**
|
|
39
|
+
* JSON response helper with consistent format
|
|
40
|
+
*/
|
|
41
|
+
export declare function jsonResponse<T = any>(res: NextApiResponse, options: {
|
|
42
|
+
status?: number;
|
|
43
|
+
data?: T;
|
|
44
|
+
error?: string;
|
|
45
|
+
message?: string;
|
|
46
|
+
meta?: Record<string, any>;
|
|
47
|
+
}): void;
|
|
48
|
+
/**
|
|
49
|
+
* API response formatter middleware
|
|
50
|
+
*/
|
|
51
|
+
export declare function formatResponse(): Middleware;
|
|
52
|
+
/**
|
|
53
|
+
* Request timeout middleware
|
|
54
|
+
*/
|
|
55
|
+
export declare function timeout(ms: number): Middleware;
|
|
56
|
+
/**
|
|
57
|
+
* Body size limiter
|
|
58
|
+
*/
|
|
59
|
+
export declare function bodyLimit(maxSize: number): Middleware;
|
|
60
|
+
/**
|
|
61
|
+
* Method filter middleware
|
|
62
|
+
*/
|
|
63
|
+
export declare function allowMethods(methods: string[]): Middleware;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compress = compress;
|
|
4
|
+
exports.responseTime = responseTime;
|
|
5
|
+
exports.paginate = paginate;
|
|
6
|
+
exports.getPaginationParams = getPaginationParams;
|
|
7
|
+
exports.jsonResponse = jsonResponse;
|
|
8
|
+
exports.formatResponse = formatResponse;
|
|
9
|
+
exports.timeout = timeout;
|
|
10
|
+
exports.bodyLimit = bodyLimit;
|
|
11
|
+
exports.allowMethods = allowMethods;
|
|
12
|
+
/**
|
|
13
|
+
* Compression middleware for API responses
|
|
14
|
+
*/
|
|
15
|
+
function compress(options = {}) {
|
|
16
|
+
const { threshold = 1024 } = options; // 1KB default threshold
|
|
17
|
+
return (req, res, next) => {
|
|
18
|
+
const acceptEncoding = req.headers['accept-encoding'] || '';
|
|
19
|
+
if (!acceptEncoding.includes('gzip')) {
|
|
20
|
+
next();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const originalJson = res.json;
|
|
24
|
+
const originalSend = res.send;
|
|
25
|
+
res.json = function (data) {
|
|
26
|
+
const jsonString = JSON.stringify(data);
|
|
27
|
+
if (jsonString.length > threshold) {
|
|
28
|
+
res.setHeader('Content-Encoding', 'gzip');
|
|
29
|
+
res.setHeader('Vary', 'Accept-Encoding');
|
|
30
|
+
}
|
|
31
|
+
return originalJson.call(this, data);
|
|
32
|
+
};
|
|
33
|
+
res.send = function (data) {
|
|
34
|
+
if (typeof data === 'string' && data.length > threshold) {
|
|
35
|
+
res.setHeader('Content-Encoding', 'gzip');
|
|
36
|
+
res.setHeader('Vary', 'Accept-Encoding');
|
|
37
|
+
}
|
|
38
|
+
return originalSend.call(this, data);
|
|
39
|
+
};
|
|
40
|
+
next();
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Response time tracking
|
|
45
|
+
*/
|
|
46
|
+
function responseTime() {
|
|
47
|
+
return (req, res, next) => {
|
|
48
|
+
const start = process.hrtime();
|
|
49
|
+
const originalEnd = res.end;
|
|
50
|
+
res.end = function (...args) {
|
|
51
|
+
const diff = process.hrtime(start);
|
|
52
|
+
const time = (diff[0] * 1e9 + diff[1]) / 1e6; // Convert to milliseconds
|
|
53
|
+
res.setHeader('X-Response-Time', `${time.toFixed(2)}ms`);
|
|
54
|
+
return originalEnd.apply(this, args);
|
|
55
|
+
};
|
|
56
|
+
next();
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function paginate(data, options = {}) {
|
|
60
|
+
const { page = 1, limit = 10, maxLimit = 100, } = options;
|
|
61
|
+
const actualLimit = Math.min(limit, maxLimit);
|
|
62
|
+
const actualPage = Math.max(1, page);
|
|
63
|
+
const startIndex = (actualPage - 1) * actualLimit;
|
|
64
|
+
const endIndex = startIndex + actualLimit;
|
|
65
|
+
const paginatedData = data.slice(startIndex, endIndex);
|
|
66
|
+
const total = data.length;
|
|
67
|
+
const totalPages = Math.ceil(total / actualLimit);
|
|
68
|
+
return {
|
|
69
|
+
data: paginatedData,
|
|
70
|
+
pagination: {
|
|
71
|
+
page: actualPage,
|
|
72
|
+
limit: actualLimit,
|
|
73
|
+
total,
|
|
74
|
+
totalPages,
|
|
75
|
+
hasNext: actualPage < totalPages,
|
|
76
|
+
hasPrev: actualPage > 1,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parse pagination params from request
|
|
82
|
+
*/
|
|
83
|
+
function getPaginationParams(req) {
|
|
84
|
+
const page = parseInt(req.query.page) || 1;
|
|
85
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
86
|
+
return { page, limit };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* JSON response helper with consistent format
|
|
90
|
+
*/
|
|
91
|
+
function jsonResponse(res, options) {
|
|
92
|
+
const { status = 200, data, error, message, meta } = options;
|
|
93
|
+
const response = {};
|
|
94
|
+
if (data !== undefined) {
|
|
95
|
+
response.data = data;
|
|
96
|
+
}
|
|
97
|
+
if (message) {
|
|
98
|
+
response.message = message;
|
|
99
|
+
}
|
|
100
|
+
if (error) {
|
|
101
|
+
response.error = error;
|
|
102
|
+
}
|
|
103
|
+
if (meta) {
|
|
104
|
+
response.meta = meta;
|
|
105
|
+
}
|
|
106
|
+
return res.status(status).json(response);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* API response formatter middleware
|
|
110
|
+
*/
|
|
111
|
+
function formatResponse() {
|
|
112
|
+
return (req, res, next) => {
|
|
113
|
+
const originalJson = res.json;
|
|
114
|
+
res.json = function (data) {
|
|
115
|
+
const formattedResponse = {
|
|
116
|
+
success: res.statusCode >= 200 && res.statusCode < 300,
|
|
117
|
+
statusCode: res.statusCode,
|
|
118
|
+
data,
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
};
|
|
121
|
+
return originalJson.call(this, formattedResponse);
|
|
122
|
+
};
|
|
123
|
+
next();
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Request timeout middleware
|
|
128
|
+
*/
|
|
129
|
+
function timeout(ms) {
|
|
130
|
+
return async (req, res, next) => {
|
|
131
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
reject(new Error(`Request timeout after ${ms}ms`));
|
|
134
|
+
}, ms);
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
await Promise.race([
|
|
138
|
+
next(),
|
|
139
|
+
timeoutPromise,
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (error instanceof Error && error.message.includes('timeout')) {
|
|
144
|
+
res.status(408).json({
|
|
145
|
+
error: 'Request timeout',
|
|
146
|
+
message: error.message,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Body size limiter
|
|
157
|
+
*/
|
|
158
|
+
function bodyLimit(maxSize) {
|
|
159
|
+
return (req, res, next) => {
|
|
160
|
+
const contentLength = parseInt(req.headers['content-length'] || '0');
|
|
161
|
+
if (contentLength > maxSize) {
|
|
162
|
+
res.status(413).json({
|
|
163
|
+
error: 'Payload too large',
|
|
164
|
+
maxSize: `${maxSize} bytes`,
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
next();
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Method filter middleware
|
|
173
|
+
*/
|
|
174
|
+
function allowMethods(methods) {
|
|
175
|
+
const allowedMethods = methods.map(m => m.toUpperCase());
|
|
176
|
+
return (req, res, next) => {
|
|
177
|
+
if (!req.method || !allowedMethods.includes(req.method.toUpperCase())) {
|
|
178
|
+
res.status(405).json({
|
|
179
|
+
error: 'Method not allowed',
|
|
180
|
+
allowed: allowedMethods,
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
next();
|
|
185
|
+
};
|
|
186
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,3 +10,6 @@ export * from "./performance/Lazy";
|
|
|
10
10
|
export * from "./performance/cacheHeaders";
|
|
11
11
|
export * from "./performance/securityHeaders";
|
|
12
12
|
export * from "./analytics/webVitals";
|
|
13
|
+
export * from "./backend/middleware";
|
|
14
|
+
export * from "./backend/cache";
|
|
15
|
+
export * from "./backend/optimization";
|
package/dist/index.js
CHANGED
|
@@ -30,3 +30,7 @@ __exportStar(require("./performance/cacheHeaders"), exports);
|
|
|
30
30
|
__exportStar(require("./performance/securityHeaders"), exports);
|
|
31
31
|
// Analytics
|
|
32
32
|
__exportStar(require("./analytics/webVitals"), exports);
|
|
33
|
+
// Backend
|
|
34
|
+
__exportStar(require("./backend/middleware"), exports);
|
|
35
|
+
__exportStar(require("./backend/cache"), exports);
|
|
36
|
+
__exportStar(require("./backend/optimization"), exports);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rkk-next",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "SEO, routing
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "SEO, routing, performance optimization and backend utilities SDK for Next.js",
|
|
5
5
|
"author": "Rohit Kumar Kundu",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsc",
|
|
22
22
|
"dev": "tsc --watch",
|
|
23
|
-
"
|
|
24
|
-
"test": "
|
|
23
|
+
"test": "jest",
|
|
24
|
+
"test:watch": "jest --watch",
|
|
25
|
+
"test:coverage": "jest --coverage",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
25
27
|
},
|
|
26
28
|
"peerDependencies": {
|
|
27
29
|
"next": ">=12",
|
|
@@ -29,9 +31,15 @@
|
|
|
29
31
|
"react-dom": ">=17"
|
|
30
32
|
},
|
|
31
33
|
"devDependencies": {
|
|
34
|
+
"@testing-library/jest-dom": "^6.1.5",
|
|
35
|
+
"@testing-library/react": "^14.1.2",
|
|
36
|
+
"@types/jest": "^29.5.11",
|
|
32
37
|
"@types/node": "^20.10.0",
|
|
33
38
|
"@types/react": "^18.2.45",
|
|
34
39
|
"@types/react-dom": "^18.2.18",
|
|
40
|
+
"jest": "^29.7.0",
|
|
41
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
42
|
+
"ts-jest": "^29.1.1",
|
|
35
43
|
"typescript": "^5.3.3"
|
|
36
44
|
},
|
|
37
45
|
"keywords": [
|
|
@@ -46,6 +54,13 @@
|
|
|
46
54
|
"react",
|
|
47
55
|
"meta-tags",
|
|
48
56
|
"json-ld",
|
|
49
|
-
"prefetch"
|
|
57
|
+
"prefetch",
|
|
58
|
+
"middleware",
|
|
59
|
+
"api-routes",
|
|
60
|
+
"backend",
|
|
61
|
+
"caching",
|
|
62
|
+
"rate-limiting",
|
|
63
|
+
"cors",
|
|
64
|
+
"express"
|
|
50
65
|
]
|
|
51
66
|
}
|