@aryanbansal-launch/edge-utils 0.1.10 → 0.1.12

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 CHANGED
@@ -7,48 +7,73 @@ A comprehensive toolkit for [Contentstack Launch](https://www.contentstack.com/d
7
7
 
8
8
  ---
9
9
 
10
- ## Quick Start (Recommended)
10
+ ## 📋 Table of Contents
11
11
 
12
- Bootstrap your edge environment in seconds using our automated initializer. Run this command from your **project root**:
12
+ - [Quick Start](#-quick-start)
13
+ - [Usage Flow](#-usage-flow)
14
+ - [Complete API Reference](#-complete-api-reference)
15
+ - [Real-World Examples](#-real-world-examples)
16
+ - [CLI Commands](#-cli-commands)
17
+ - [Platform Support](#-platform-support)
18
+
19
+ ---
20
+
21
+ ## ⚡ Quick Start
22
+
23
+ ### Step 1: Install the Package
13
24
 
14
25
  ```bash
15
- # Install the package
16
26
  npm install @aryanbansal-launch/edge-utils
27
+ ```
28
+
29
+ ### Step 2: Initialize Edge Functions
17
30
 
18
- # Initialize Edge Functions
31
+ Run this command from your **project root** (where `package.json` is located):
32
+
33
+ ```bash
19
34
  npx create-launch-edge
20
35
  ```
21
36
 
22
- This command will automatically create the `functions/` directory and a production-ready `[proxy].edge.js` boilerplate handler.
37
+ This automatically creates:
38
+ - `functions/` directory
39
+ - `functions/[proxy].edge.js` with production-ready boilerplate
40
+
41
+ ### Step 3: Customize & Deploy
42
+
43
+ 1. Open `functions/[proxy].edge.js`
44
+ 2. Customize the utilities based on your needs
45
+ 3. Deploy to Contentstack Launch
46
+
47
+ **Need help?** Run:
48
+ ```bash
49
+ npx launch-help
50
+ ```
23
51
 
24
52
  ---
25
53
 
26
- ## Features & Deep Dive
54
+ ## 🔄 Usage Flow
27
55
 
28
- ### 🛡️ Security & Access Control
29
- - **[Block AI Crawlers](https://www.contentstack.com/docs/developers/launch/blocking-ai-crawlers)**: Automatically detects and rejects requests from known scrapers (GPTBot, ClaudeBot, etc.) to protect your content and server resources.
30
- - **[Restricted Default Domains](https://www.contentstack.com/docs/developers/launch/blocking-default-launch-domains-from-google-search)**: By default, Launch provides a `*.contentstackapps.com` domain. This utility forces visitors to your custom domain, which is essential for SEO (preventing duplicate content) and professional branding.
31
- - **[IP Access Control](https://www.contentstack.com/docs/developers/launch/ip-based-access-control-using-edge-functions)**: Create a lightweight firewall at the edge to whitelist internal teams or block malicious IPs before they hit your application logic.
32
- - **[Edge Auth](https://www.contentstack.com/docs/developers/launch/password-protection-for-environments)**: Implement [Basic Authentication](https://www.contentstack.com/docs/developers/launch/password-protection-for-environments) directly at the edge to protect staging environments or specific paths. (Note: Hashing is recommended for production environments).
56
+ ### Understanding Edge Functions
33
57
 
34
- ### ⚛️ Next.js Optimization
35
- - **[RSC Header Fix](https://www.contentstack.com/docs/developers/launch/handling-nextjs-rsc-issues-on-launch)**: Next.js React Server Components (RSC) use a special `rsc` header. Sometimes, proxies or caches can incorrectly serve RSC data when a full page load is expected. This utility detects these edge cases and strips the header to ensure the correct response type is served.
58
+ Edge Functions in Contentstack Launch act as **middleware** that runs before requests reach your origin server. Think of them as a chain of checks and transformations:
36
59
 
37
- ### 📍 Performance & Geo-Awareness
38
- - **[Geo-Location Access](https://www.contentstack.com/docs/developers/launch/geolocation-headers-in-launch)**: Contentstack Launch injects geography data into request headers. This utility parses those headers into a clean object (`country`, `city`, `region`, etc.), enabling you to personalize content or restrict features based on user location.
39
- - **[Cache Priming](https://www.contentstack.com/docs/developers/launch/cache-priming)**: Use the `launch-config` CLI to pre-load critical URLs into the edge cache, eliminating "cold start" latency for your first visitors after a deployment.
60
+ ```
61
+ User Request Edge Function Your Application
62
+ ```
40
63
 
41
- ### 🔀 Smart Routing
42
- - **Declarative Redirects**: Handle complex, logic-based redirects at runtime.
43
- - **Runtime vs Config**:
44
- - Use **`launch.json`** ([Static Redirects](https://www.contentstack.com/docs/developers/launch/edge-url-redirects)) for high-performance, simple path-to-path mapping.
45
- - Use **`redirectIfMatch`** (this library) for dynamic redirects that require logic, such as checking cookies, headers, or geo-location before redirecting.
64
+ ### Basic Pattern
46
65
 
47
- ---
66
+ Every utility follows this pattern:
48
67
 
49
- ## 🛠️ Detailed Usage Example
68
+ ```javascript
69
+ const result = utilityFunction(request, options);
70
+ if (result) return result; // Early return if condition met
71
+ // Continue to next check...
72
+ ```
73
+
74
+ ### Complete Handler Example
50
75
 
51
- Your `functions/[proxy].edge.js` acts as a **middleware chain**. You can layer these utilities to create complex edge logic:
76
+ Here's how utilities work together in `functions/[proxy].edge.js`:
52
77
 
53
78
  ```javascript
54
79
  import {
@@ -59,80 +84,1017 @@ import {
59
84
  protectWithBasicAuth,
60
85
  redirectIfMatch,
61
86
  getGeoHeaders,
87
+ jsonResponse,
62
88
  passThrough
63
89
  } from "@aryanbansal-launch/edge-utils";
64
90
 
65
91
  export default async function handler(request, context) {
66
- // 1. 🛡️ Force Custom Domain (SEO Best Practice)
67
- // Blocks access via *.contentstackapps.com
92
+ // 1️⃣ SECURITY LAYER
93
+ // Block default domains (SEO best practice)
68
94
  const domainCheck = blockDefaultDomains(request);
69
95
  if (domainCheck) return domainCheck;
70
96
 
71
- // 2. ⚛️ Fix Next.js RSC Header issues
72
- // Prevents "JSON-only" responses on page refreshes
73
- const rscCheck = await handleNextJS_RSC(request, {
74
- affectedPaths: ["/shop", "/about"]
75
- });
76
- if (rscCheck) return rscCheck;
77
-
78
- // 3. 🤖 Block Aggressive Bots
97
+ // Block AI bots and crawlers
79
98
  const botCheck = blockAICrawlers(request);
80
99
  if (botCheck) return botCheck;
81
100
 
82
- // 4. 🧱 Firewall
83
- const ipCheck = ipAccessControl(request, { allow: ["203.0.113.10"] });
101
+ // IP-based access control
102
+ const ipCheck = ipAccessControl(request, {
103
+ allow: ["203.0.113.10", "198.51.100.5"]
104
+ });
84
105
  if (ipCheck) return ipCheck;
85
106
 
86
- // 5. 🔐 Password Protection
107
+ // 2️⃣ AUTHENTICATION LAYER
108
+ // Protect staging environments
87
109
  const auth = await protectWithBasicAuth(request, {
88
110
  hostnameIncludes: "staging.myapp.com",
89
111
  username: "admin",
90
- password: "securepassword123"
112
+ password: "securepass123"
91
113
  });
92
114
  if (auth && auth.status === 401) return auth;
93
115
 
94
- // 6. 🔀 Logic-Based Redirects
116
+ // 3️⃣ FRAMEWORK FIXES
117
+ // Fix Next.js RSC header issues
118
+ const rscCheck = await handleNextJS_RSC(request, {
119
+ affectedPaths: ["/shop", "/products", "/about"]
120
+ });
121
+ if (rscCheck) return rscCheck;
122
+
123
+ // 4️⃣ ROUTING LAYER
124
+ // Handle redirects
95
125
  const redirect = redirectIfMatch(request, {
96
- path: "/legacy-page",
126
+ path: "/old-page",
97
127
  to: "/new-page",
98
128
  status: 301
99
129
  });
100
130
  if (redirect) return redirect;
101
131
 
102
- // 7. 📍 Personalization
132
+ // 5️⃣ PERSONALIZATION
133
+ // Get user's location
103
134
  const geo = getGeoHeaders(request);
104
- if (geo.country === "UK") {
105
- // Custom logic for UK visitors...
135
+ if (geo.country === "US") {
136
+ console.log(`US visitor from ${geo.city}, ${geo.region}`);
137
+ }
138
+
139
+ // 6️⃣ CUSTOM API ENDPOINTS
140
+ const url = new URL(request.url);
141
+ if (url.pathname === "/api/health") {
142
+ return jsonResponse({
143
+ status: "healthy",
144
+ region: geo.region,
145
+ timestamp: Date.now()
146
+ });
106
147
  }
107
148
 
108
- // 8. 🚀 Pass through to Origin
149
+ // 7️⃣ DEFAULT: Pass to origin
109
150
  return passThrough(request);
110
151
  }
111
152
  ```
112
153
 
113
154
  ---
114
155
 
115
- ## ⚙️ Configuration CLI
156
+ ## 📚 Complete API Reference
157
+
158
+ ### 🛡️ Security & Access Control
159
+
160
+ #### `blockAICrawlers(request, bots?)`
161
+
162
+ Block AI crawlers and bots from accessing your site.
163
+
164
+ **Parameters:**
165
+ - `request` (Request) - The incoming request object
166
+ - `bots` (string[], optional) - Custom list of bot user-agents to block
167
+
168
+ **Returns:** `Response | null`
169
+ - Returns `403 Forbidden` if bot detected
170
+ - Returns `null` if no bot detected (continue processing)
171
+
172
+ **Default Blocked Bots:**
173
+ The following bots are blocked by default (case-insensitive):
174
+ - `claudebot` - Anthropic's Claude AI crawler
175
+ - `gptbot` - OpenAI's GPT crawler
176
+ - `googlebot` - Google's web crawler
177
+ - `bingbot` - Microsoft Bing's crawler
178
+ - `ahrefsbot` - Ahrefs SEO crawler
179
+ - `yandexbot` - Yandex search engine crawler
180
+ - `semrushbot` - SEMrush SEO tool crawler
181
+ - `mj12bot` - Majestic SEO crawler
182
+ - `facebookexternalhit` - Facebook's link preview crawler
183
+ - `twitterbot` - Twitter's link preview crawler
184
+
185
+ **Example:**
186
+ ```javascript
187
+ // Use default bot list
188
+ const response = blockAICrawlers(request);
189
+ if (response) return response;
190
+
191
+ // Custom bot list
192
+ const response = blockAICrawlers(request, [
193
+ "gptbot",
194
+ "claudebot",
195
+ "my-custom-bot"
196
+ ]);
197
+ if (response) return response;
198
+ ```
199
+
200
+ **Use Cases:**
201
+ - Protect content from AI scraping
202
+ - Reduce server load from aggressive crawlers
203
+ - Comply with content usage policies
204
+
205
+ ---
206
+
207
+ #### `blockDefaultDomains(request, options?)`
208
+
209
+ Block access via default Launch domains (`*.contentstackapps.com`).
210
+
211
+ **Parameters:**
212
+ - `request` (Request) - The incoming request object
213
+ - `options` (object, optional)
214
+ - `domainToBlock` (string) - Custom domain to block (default: "contentstackapps.com")
215
+
216
+ **Returns:** `Response | null`
217
+ - Returns `403 Forbidden` if default domain detected
218
+ - Returns `null` if custom domain used
219
+
220
+ **Example:**
221
+ ```javascript
222
+ // Block default Launch domain
223
+ const response = blockDefaultDomains(request);
224
+ if (response) return response;
225
+
226
+ // Block custom domain
227
+ const response = blockDefaultDomains(request, {
228
+ domainToBlock: "myolddomain.com"
229
+ });
230
+ if (response) return response;
231
+ ```
232
+
233
+ **Use Cases:**
234
+ - Force users to custom domain for SEO
235
+ - Prevent duplicate content indexing
236
+ - Professional branding
237
+
238
+ **Learn More:** [Blocking Default Launch Domains](https://www.contentstack.com/docs/developers/launch/blocking-default-launch-domains-from-google-search)
239
+
240
+ ---
241
+
242
+ #### `ipAccessControl(request, options)`
243
+
244
+ Whitelist or blacklist IPs at the edge.
245
+
246
+ **Parameters:**
247
+ - `request` (Request) - The incoming request object
248
+ - `options` (object)
249
+ - `allow` (string[], optional) - Whitelist of allowed IPs
250
+ - `deny` (string[], optional) - Blacklist of denied IPs
251
+
252
+ **Returns:** `Response | null`
253
+ - Returns `403 Forbidden` if IP blocked
254
+ - Returns `null` if IP allowed
255
+
256
+ **Example:**
257
+ ```javascript
258
+ // Whitelist specific IPs (only these can access)
259
+ const response = ipAccessControl(request, {
260
+ allow: ["203.0.113.10", "198.51.100.5"]
261
+ });
262
+ if (response) return response;
263
+
264
+ // Blacklist specific IPs
265
+ const response = ipAccessControl(request, {
266
+ deny: ["192.0.2.100", "192.0.2.101"]
267
+ });
268
+ if (response) return response;
269
+
270
+ // Combine both (deny takes precedence)
271
+ const response = ipAccessControl(request, {
272
+ allow: ["203.0.113.0/24"], // Allow subnet
273
+ deny: ["203.0.113.50"] // Except this one
274
+ });
275
+ if (response) return response;
276
+ ```
277
+
278
+ **Use Cases:**
279
+ - Restrict admin panels to office IPs
280
+ - Block malicious IPs
281
+ - Create staging environment access control
282
+
283
+ **Learn More:** [IP-Based Access Control](https://www.contentstack.com/docs/developers/launch/ip-based-access-control-using-edge-functions)
284
+
285
+ ---
286
+
287
+ #### `protectWithBasicAuth(request, options)`
288
+
289
+ Add Basic Authentication to protect environments.
290
+
291
+ **Parameters:**
292
+ - `request` (Request) - The incoming request object
293
+ - `options` (object)
294
+ - `hostnameIncludes` (string) - Protect URLs containing this hostname
295
+ - `username` (string) - Username for authentication
296
+ - `password` (string) - Password for authentication
297
+ - `realm` (string, optional) - Auth realm name (default: "Protected Area")
298
+
299
+ **Returns:** `Promise<Response> | null`
300
+ - Returns `401 Unauthorized` if auth fails
301
+ - Returns authenticated response if credentials valid
302
+ - Returns `null` if hostname doesn't match
303
+
304
+ **Example:**
305
+ ```javascript
306
+ // Protect staging environment
307
+ const auth = await protectWithBasicAuth(request, {
308
+ hostnameIncludes: "staging.myapp.com",
309
+ username: "admin",
310
+ password: "securepass123",
311
+ realm: "Staging Environment"
312
+ });
313
+ if (auth && auth.status === 401) return auth;
314
+
315
+ // Protect specific path pattern
316
+ const url = new URL(request.url);
317
+ if (url.pathname.startsWith("/admin")) {
318
+ const auth = await protectWithBasicAuth(request, {
319
+ hostnameIncludes: url.hostname,
320
+ username: "admin",
321
+ password: "adminpass"
322
+ });
323
+ if (auth && auth.status === 401) return auth;
324
+ }
325
+ ```
326
+
327
+ **Use Cases:**
328
+ - Protect staging/dev environments
329
+ - Quick password protection for demos
330
+ - Restrict access to admin areas
331
+
332
+ **Security Note:** For production, use proper authentication systems. Basic Auth credentials are base64-encoded, not encrypted.
333
+
334
+ **Learn More:** [Password Protection for Environments](https://www.contentstack.com/docs/developers/launch/password-protection-for-environments)
335
+
336
+ ---
337
+
338
+ ### 🔀 Routing & Redirects
116
339
 
117
- Manage your `launch.json` file interactively to handle bulk settings:
340
+ #### `redirectIfMatch(request, options)`
118
341
 
342
+ Conditional redirects based on path and method.
343
+
344
+ **Parameters:**
345
+ - `request` (Request) - The incoming request object
346
+ - `options` (object)
347
+ - `path` (string) - Path to match
348
+ - `to` (string) - Destination path
349
+ - `method` (string, optional) - HTTP method to match (e.g., "GET", "POST")
350
+ - `status` (number, optional) - HTTP status code (default: 301)
351
+
352
+ **Returns:** `Response | null`
353
+ - Returns redirect response if path matches
354
+ - Returns `null` if no match
355
+
356
+ **Example:**
357
+ ```javascript
358
+ // Simple redirect
359
+ const redirect = redirectIfMatch(request, {
360
+ path: "/old-page",
361
+ to: "/new-page",
362
+ status: 301 // Permanent redirect
363
+ });
364
+ if (redirect) return redirect;
365
+
366
+ // Temporary redirect
367
+ const redirect = redirectIfMatch(request, {
368
+ path: "/maintenance",
369
+ to: "/coming-soon",
370
+ status: 302 // Temporary redirect
371
+ });
372
+ if (redirect) return redirect;
373
+
374
+ // Method-specific redirect
375
+ const redirect = redirectIfMatch(request, {
376
+ path: "/api/old-endpoint",
377
+ method: "POST",
378
+ to: "/api/v2/endpoint",
379
+ status: 308 // Permanent redirect (preserves method)
380
+ });
381
+ if (redirect) return redirect;
382
+ ```
383
+
384
+ **Use Cases:**
385
+ - SEO-friendly URL changes
386
+ - Migrate old URLs to new structure
387
+ - A/B testing redirects
388
+ - Maintenance mode redirects
389
+
390
+ **Learn More:** [Edge URL Redirects](https://www.contentstack.com/docs/developers/launch/edge-url-redirects)
391
+
392
+ ---
393
+
394
+ ### 📊 Redirects: Config vs Edge Functions
395
+
396
+ Contentstack Launch offers **two ways** to handle redirects. Choose the right approach based on your needs:
397
+
398
+ #### Option 1: Config-Based Redirects (`launch.json`)
399
+
400
+ **Best for:** Static, predictable redirects that don't require logic
401
+
402
+ **Pros:**
403
+ - ⚡ **Faster**: No edge function execution overhead
404
+ - 🎯 **Simpler**: Pure configuration, no code needed
405
+ - 📦 **Bulk-friendly**: Easy to manage hundreds/thousands of redirects
406
+ - 🔧 **Easy updates**: Use `npx launch-config` CLI with CSV/JSON import
407
+
408
+ **Cons:**
409
+ - ❌ No dynamic logic (can't check cookies, headers, geo, etc.)
410
+ - ❌ No conditional redirects based on request data
411
+ - ❌ Limited to exact path or wildcard matching
412
+
413
+ **Setup:**
119
414
  ```bash
415
+ # Interactive CLI
120
416
  npx launch-config
417
+
418
+ # Or bulk import from CSV/JSON
419
+ npx launch-config
420
+ # Choose option 2 or 3 for bulk import
421
+ ```
422
+
423
+ **Example `launch.json`:**
424
+ ```json
425
+ {
426
+ "redirects": [
427
+ {
428
+ "source": "/old-blog/:slug",
429
+ "destination": "/blog/:slug",
430
+ "statusCode": 301
431
+ },
432
+ {
433
+ "source": "/products/old-*",
434
+ "destination": "/products/new-*",
435
+ "statusCode": 308
436
+ }
437
+ ]
438
+ }
121
439
  ```
122
440
 
123
- ### Supported Settings:
124
- - **Bulk Redirects**: Add multiple sources and destinations easily.
125
- - **Rewrites**: Map internal paths to external APIs or micro-services.
126
- - **Cache Priming**: Add a comma-separated list of URLs to warm up the CDN.
441
+ **When to use:**
442
+ - SEO migrations with hundreds of URL changes
443
+ - Simple path rewrites (e.g., `/old-path` `/new-path`)
444
+ - Bulk product SKU redirects
445
+ - ✅ Static site restructuring
446
+ - ✅ Predictable, rule-based redirects
447
+
448
+ ---
449
+
450
+ #### Option 2: Edge Function Redirects (This Package)
451
+
452
+ **Best for:** Dynamic redirects requiring logic or request inspection
453
+
454
+ **Pros:**
455
+ - 🧠 **Smart**: Access cookies, headers, geo-location, user-agent
456
+ - 🎨 **Flexible**: Complex conditional logic
457
+ - 🔄 **Dynamic**: Redirect based on A/B tests, feature flags, user data
458
+ - 🌍 **Contextual**: Different redirects per country/region
459
+
460
+ **Cons:**
461
+ - 🐌 Slightly slower (edge function execution)
462
+ - 🔧 Requires code changes
463
+ - 📝 More complex for simple redirects
464
+
465
+ **Setup:**
466
+ ```javascript
467
+ import { redirectIfMatch } from '@aryanbansal-launch/edge-utils';
468
+
469
+ export default async function handler(request) {
470
+ // Dynamic redirect based on cookie
471
+ const cookie = request.headers.get('cookie');
472
+ if (cookie?.includes('beta=true')) {
473
+ return Response.redirect(new URL('/beta-features', request.url), 302);
474
+ }
475
+
476
+ // Geo-based redirect
477
+ const country = request.headers.get('x-cs-country');
478
+ if (country === 'FR') {
479
+ return Response.redirect('https://fr.mysite.com', 302);
480
+ }
481
+
482
+ // Conditional redirect
483
+ const redirect = redirectIfMatch(request, {
484
+ path: "/old-page",
485
+ to: "/new-page",
486
+ method: "GET",
487
+ status: 301
488
+ });
489
+ if (redirect) return redirect;
490
+
491
+ return passThrough(request);
492
+ }
493
+ ```
494
+
495
+ **When to use:**
496
+ - ✅ Geo-location based redirects
497
+ - ✅ A/B testing redirects
498
+ - ✅ Cookie/session-based routing
499
+ - ✅ User-agent specific redirects (mobile vs desktop)
500
+ - ✅ Feature flag redirects
501
+ - ✅ Maintenance mode with exceptions
502
+ - ✅ Complex conditional logic
503
+
504
+ ---
505
+
506
+ #### Quick Decision Guide
507
+
508
+ | Scenario | Use Config | Use Edge Function |
509
+ |----------|-----------|-------------------|
510
+ | 500+ simple URL redirects | ✅ | ❌ |
511
+ | SEO migration from old site | ✅ | ❌ |
512
+ | Redirect based on country | ❌ | ✅ |
513
+ | A/B test routing | ❌ | ✅ |
514
+ | Cookie-based redirects | ❌ | ✅ |
515
+ | Mobile vs desktop routing | ❌ | ✅ |
516
+ | Simple path changes | ✅ | ❌ |
517
+ | Maintenance mode (all users) | ✅ | ❌ |
518
+ | Maintenance mode (except admins) | ❌ | ✅ |
519
+ | Bulk product SKU changes | ✅ | ❌ |
520
+
521
+ **💡 Pro Tip:** You can use **both** approaches together! Use `launch.json` for bulk static redirects and edge functions for dynamic logic.
522
+
523
+ **Example Combined Approach:**
524
+ ```javascript
525
+ // launch.json - handles 1000+ static SEO redirects
526
+ {
527
+ "redirects": [
528
+ { "source": "/old-blog/:slug", "destination": "/blog/:slug", "statusCode": 301 }
529
+ // ... 1000 more redirects
530
+ ]
531
+ }
532
+
533
+ // functions/[proxy].edge.js - handles dynamic logic
534
+ export default async function handler(request) {
535
+ // Geo-based redirect (dynamic)
536
+ const country = request.headers.get('x-cs-country');
537
+ if (country === 'FR') {
538
+ return Response.redirect('https://fr.mysite.com', 302);
539
+ }
540
+
541
+ // Static redirects handled by launch.json automatically
542
+ return passThrough(request);
543
+ }
544
+ ```
545
+
546
+ ---
547
+
548
+ ### ⚛️ Next.js Optimization
549
+
550
+ #### `handleNextJS_RSC(request, options)`
551
+
552
+ Fix Next.js React Server Components header issues.
553
+
554
+ **Parameters:**
555
+ - `request` (Request) - The incoming request object
556
+ - `options` (object)
557
+ - `affectedPaths` (string[]) - Array of paths with RSC issues
558
+
559
+ **Returns:** `Promise<Response> | null`
560
+ - Returns modified response if RSC issue detected
561
+ - Returns `null` if no issue
562
+
563
+ **Example:**
564
+ ```javascript
565
+ // Fix specific pages
566
+ const rsc = await handleNextJS_RSC(request, {
567
+ affectedPaths: ["/shop", "/products", "/about"]
568
+ });
569
+ if (rsc) return rsc;
570
+
571
+ // Fix all dynamic routes
572
+ const rsc = await handleNextJS_RSC(request, {
573
+ affectedPaths: ["/blog", "/products", "/categories"]
574
+ });
575
+ if (rsc) return rsc;
576
+ ```
577
+
578
+ **What It Does:**
579
+ Next.js RSC uses a special `rsc: 1` header. Sometimes, caches incorrectly serve RSC data (JSON) when a full page load is expected. This utility detects these cases and strips the header to ensure correct response type.
580
+
581
+ **Use Cases:**
582
+ - Fix "JSON showing instead of page" issues
583
+ - Resolve RSC caching problems
584
+ - Ensure proper page hydration
585
+
586
+ **Learn More:** [Handling Next.js RSC Issues](https://www.contentstack.com/docs/developers/launch/handling-nextjs-rsc-issues-on-launch)
587
+
588
+ ---
589
+
590
+ ### 📍 Geo-Location
591
+
592
+ #### `getGeoHeaders(request)`
593
+
594
+ Extract geo-location data from Launch headers.
595
+
596
+ **Parameters:**
597
+ - `request` (Request) - The incoming request object
598
+
599
+ **Returns:** Object with geo data
600
+ ```typescript
601
+ {
602
+ country: string | null, // ISO country code (e.g., "US")
603
+ region: string | null, // Region/state code (e.g., "CA")
604
+ city: string | null, // City name (e.g., "San Francisco")
605
+ latitude: string | null, // Latitude coordinate
606
+ longitude: string | null // Longitude coordinate
607
+ }
608
+ ```
609
+
610
+ **Example:**
611
+ ```javascript
612
+ // Get user location
613
+ const geo = getGeoHeaders(request);
614
+
615
+ // Country-based logic
616
+ if (geo.country === "US") {
617
+ console.log(`US visitor from ${geo.city}, ${geo.region}`);
618
+ }
619
+
620
+ // Region-specific content
621
+ if (geo.country === "US" && geo.region === "CA") {
622
+ // Show California-specific content
623
+ }
624
+
625
+ // Distance-based logic
626
+ if (geo.latitude && geo.longitude) {
627
+ const userLat = parseFloat(geo.latitude);
628
+ const userLon = parseFloat(geo.longitude);
629
+ // Calculate distance to store, etc.
630
+ }
631
+
632
+ // Redirect based on location
633
+ const geo = getGeoHeaders(request);
634
+ if (geo.country === "FR") {
635
+ return Response.redirect("https://fr.mysite.com", 302);
636
+ }
637
+ ```
638
+
639
+ **Use Cases:**
640
+ - Personalize content by location
641
+ - Show region-specific pricing
642
+ - Redirect to country-specific sites
643
+ - Display nearest store locations
644
+ - Comply with regional regulations
645
+
646
+ **Learn More:** [Geolocation Headers in Launch](https://www.contentstack.com/docs/developers/launch/geolocation-headers-in-launch)
647
+
648
+ ---
649
+
650
+ ### 📤 Response Utilities
651
+
652
+ #### `jsonResponse(body, init?)`
653
+
654
+ Create JSON responses easily.
655
+
656
+ **Parameters:**
657
+ - `body` (object) - JSON-serializable object
658
+ - `init` (ResponseInit, optional) - Additional response options (status, headers, etc.)
659
+
660
+ **Returns:** `Response` with `Content-Type: application/json`
661
+
662
+ **Example:**
663
+ ```javascript
664
+ // Simple JSON response
665
+ return jsonResponse({ status: "ok", message: "Success" });
666
+
667
+ // With custom status
668
+ return jsonResponse(
669
+ { error: "Not found" },
670
+ { status: 404 }
671
+ );
672
+
673
+ // With custom headers
674
+ return jsonResponse(
675
+ { data: [...] },
676
+ {
677
+ status: 200,
678
+ headers: {
679
+ "Cache-Control": "max-age=3600",
680
+ "X-Custom-Header": "value"
681
+ }
682
+ }
683
+ );
684
+
685
+ // API endpoint example
686
+ const url = new URL(request.url);
687
+ if (url.pathname === "/api/user") {
688
+ const geo = getGeoHeaders(request);
689
+ return jsonResponse({
690
+ user: "john_doe",
691
+ location: {
692
+ country: geo.country,
693
+ city: geo.city
694
+ },
695
+ timestamp: Date.now()
696
+ });
697
+ }
698
+ ```
699
+
700
+ **Use Cases:**
701
+ - Create API endpoints at the edge
702
+ - Return structured error messages
703
+ - Build serverless functions
704
+
705
+ ---
706
+
707
+ #### `passThrough(request)`
708
+
709
+ Forward request to origin server.
710
+
711
+ **Parameters:**
712
+ - `request` (Request) - The incoming request object
713
+
714
+ **Returns:** `Promise<Response>` from origin
715
+
716
+ **Example:**
717
+ ```javascript
718
+ // Default: pass everything through
719
+ return passThrough(request);
720
+
721
+ // After all checks
722
+ export default async function handler(request, context) {
723
+ // ... all your checks ...
724
+
725
+ // If nothing matched, pass to origin
726
+ return passThrough(request);
727
+ }
728
+ ```
729
+
730
+ **Use Cases:**
731
+ - Default fallback after edge logic
732
+ - Forward requests that don't need edge processing
733
+
734
+ ---
735
+
736
+ ### ⚙️ Configuration
737
+
738
+ #### `generateLaunchConfig(options)`
739
+
740
+ Generate `launch.json` configuration programmatically.
741
+
742
+ **Parameters:**
743
+ - `options` (object)
744
+ - `redirects` (LaunchRedirect[], optional)
745
+ - `rewrites` (LaunchRewrite[], optional)
746
+ - `cache` (object, optional)
747
+ - `cachePriming` (object)
748
+ - `urls` (string[])
749
+
750
+ **Returns:** `LaunchConfig` object
751
+
752
+ **Types:**
753
+ ```typescript
754
+ interface LaunchRedirect {
755
+ source: string;
756
+ destination: string;
757
+ statusCode?: number;
758
+ response?: {
759
+ headers?: Record<string, string>;
760
+ };
761
+ }
762
+
763
+ interface LaunchRewrite {
764
+ source: string;
765
+ destination: string;
766
+ }
767
+ ```
768
+
769
+ **Example:**
770
+ ```javascript
771
+ import { generateLaunchConfig } from "@aryanbansal-launch/edge-utils";
772
+ import fs from "fs";
773
+
774
+ const config = generateLaunchConfig({
775
+ redirects: [
776
+ {
777
+ source: "/old-blog/:slug",
778
+ destination: "/blog/:slug",
779
+ statusCode: 301
780
+ },
781
+ {
782
+ source: "/products",
783
+ destination: "/shop",
784
+ statusCode: 308
785
+ }
786
+ ],
787
+ rewrites: [
788
+ {
789
+ source: "/api/:path*",
790
+ destination: "https://api.mybackend.com/:path*"
791
+ }
792
+ ],
793
+ cache: {
794
+ cachePriming: {
795
+ urls: ["/", "/about", "/products", "/contact"]
796
+ }
797
+ }
798
+ });
799
+
800
+ // Write to launch.json
801
+ fs.writeFileSync("launch.json", JSON.stringify(config, null, 2));
802
+ ```
803
+
804
+ **Use Cases:**
805
+ - Generate config from CMS data
806
+ - Automate bulk redirects
807
+ - Dynamic configuration management
808
+
809
+ ---
810
+
811
+ ## 🎯 Real-World Examples
812
+
813
+ ### Example 1: E-Commerce Site
814
+
815
+ ```javascript
816
+ export default async function handler(request, context) {
817
+ const url = new URL(request.url);
818
+ const geo = getGeoHeaders(request);
819
+
820
+ // 1. Block bots to reduce costs
821
+ const botCheck = blockAICrawlers(request);
822
+ if (botCheck) return botCheck;
823
+
824
+ // 2. Geo-based redirects
825
+ if (geo.country === "UK" && !url.hostname.includes("uk.")) {
826
+ return Response.redirect(`https://uk.myshop.com${url.pathname}`, 302);
827
+ }
828
+
829
+ // 3. Maintenance mode for specific regions
830
+ if (geo.country === "US" && url.pathname.startsWith("/checkout")) {
831
+ return jsonResponse(
832
+ { error: "Checkout temporarily unavailable in your region" },
833
+ { status: 503 }
834
+ );
835
+ }
836
+
837
+ // 4. Product redirects
838
+ const redirect = redirectIfMatch(request, {
839
+ path: "/products/old-sku-123",
840
+ to: "/products/new-sku-456",
841
+ status: 301
842
+ });
843
+ if (redirect) return redirect;
844
+
845
+ return passThrough(request);
846
+ }
847
+ ```
848
+
849
+ ### Example 2: Multi-Environment Setup
850
+
851
+ ```javascript
852
+ export default async function handler(request, context) {
853
+ const url = new URL(request.url);
854
+
855
+ // 1. Protect staging with Basic Auth
856
+ if (url.hostname.includes("staging")) {
857
+ const auth = await protectWithBasicAuth(request, {
858
+ hostnameIncludes: "staging",
859
+ username: "team",
860
+ password: process.env.STAGING_PASSWORD || "defaultpass"
861
+ });
862
+ if (auth && auth.status === 401) return auth;
863
+ }
864
+
865
+ // 2. Restrict dev environment to office IPs
866
+ if (url.hostname.includes("dev")) {
867
+ const ipCheck = ipAccessControl(request, {
868
+ allow: ["203.0.113.0/24"] // Office IP range
869
+ });
870
+ if (ipCheck) return ipCheck;
871
+ }
872
+
873
+ // 3. Block default domain on production
874
+ if (url.hostname.includes("myapp.com")) {
875
+ const domainCheck = blockDefaultDomains(request);
876
+ if (domainCheck) return domainCheck;
877
+ }
878
+
879
+ return passThrough(request);
880
+ }
881
+ ```
882
+
883
+ ### Example 3: Next.js App with API Routes
884
+
885
+ ```javascript
886
+ export default async function handler(request, context) {
887
+ const url = new URL(request.url);
888
+ const geo = getGeoHeaders(request);
889
+
890
+ // 1. Fix RSC issues on dynamic pages
891
+ const rscCheck = await handleNextJS_RSC(request, {
892
+ affectedPaths: ["/blog", "/products", "/categories"]
893
+ });
894
+ if (rscCheck) return rscCheck;
895
+
896
+ // 2. Edge API endpoints
897
+ if (url.pathname === "/api/geo") {
898
+ return jsonResponse({
899
+ country: geo.country,
900
+ region: geo.region,
901
+ city: geo.city
902
+ });
903
+ }
904
+
905
+ if (url.pathname === "/api/health") {
906
+ return jsonResponse({
907
+ status: "healthy",
908
+ timestamp: Date.now(),
909
+ region: geo.region
910
+ });
911
+ }
912
+
913
+ // 3. Block bots from expensive pages
914
+ if (url.pathname.startsWith("/search")) {
915
+ const botCheck = blockAICrawlers(request);
916
+ if (botCheck) return botCheck;
917
+ }
918
+
919
+ return passThrough(request);
920
+ }
921
+ ```
922
+
923
+ ---
924
+
925
+ ## 🛠️ CLI Commands
926
+
927
+ ### `npx create-launch-edge`
928
+
929
+ Initialize edge functions with production-ready boilerplate.
930
+
931
+ **What it does:**
932
+ 1. Checks you're in project root (where `package.json` exists)
933
+ 2. Creates `functions/` directory if needed
934
+ 3. Generates `functions/[proxy].edge.js` with example code
935
+
936
+ **Usage:**
937
+ ```bash
938
+ cd /path/to/your/project
939
+ npx create-launch-edge
940
+ ```
941
+
942
+ **Output:**
943
+ ```
944
+ 🚀 create-launch-edge: Contentstack Launch Initializer
945
+
946
+ ✨ New: Created /functions directory
947
+ ✨ New: Created /functions/[proxy].edge.js
948
+
949
+ 🎉 Setup Complete!
950
+
951
+ Next Steps:
952
+ 1. Open functions/[proxy].edge.js
953
+ 2. Customize your redirects, auth, and RSC paths
954
+ 3. Deploy your project to Contentstack Launch
955
+ ```
956
+
957
+ ---
958
+
959
+ ### `npx launch-config`
960
+
961
+ Interactive CLI to manage `launch.json` configuration with support for bulk imports.
962
+
963
+ **What it does:**
964
+ - Add/manage redirects (one-by-one or bulk import)
965
+ - Configure rewrites
966
+ - Set up cache priming URLs
967
+ - Preserves existing configuration
968
+
969
+ **Usage:**
970
+ ```bash
971
+ npx launch-config
972
+ ```
973
+
974
+ **Interactive Prompts:**
975
+ ```
976
+ 🚀 Launch Configuration Generator
977
+
978
+ How would you like to add redirects?
979
+ 1) One by one (interactive)
980
+ 2) Bulk import from CSV file
981
+ 3) Bulk import from JSON file
982
+ 4) Skip
983
+ Choose (1-4): 2
984
+ Enter CSV file path (e.g., ./redirects.csv): ./redirects.csv
985
+ ✔ Imported 150 redirects from CSV.
986
+
987
+ Do you want to add a Rewrite? (y/n): y
988
+ Source path (e.g., /api/*): /api/*
989
+ Destination URL: https://backend.myapp.com/api/*
990
+ ✔ Rewrite added.
991
+
992
+ Do you want to add Cache Priming URLs? (y/n): y
993
+ Note: Only relative paths are supported. No Regex/Wildcards.
994
+ Enter URLs separated by commas (e.g., /home,/about,/shop): /,/about,/products
995
+
996
+ ✅ Successfully updated launch.json!
997
+ ```
998
+
999
+ #### Bulk Import Formats
1000
+
1001
+ **CSV Format** (`redirects.csv`):
1002
+ ```csv
1003
+ source,destination,statusCode
1004
+ /old-blog/post-1,/blog/post-1,301
1005
+ /old-blog/post-2,/blog/post-2,301
1006
+ /products/old-sku-123,/products/new-sku-456,308
1007
+ /legacy/*,/new/*,301
1008
+ ```
1009
+
1010
+ **JSON Format** (`redirects.json`):
1011
+ ```json
1012
+ [
1013
+ {
1014
+ "source": "/old-blog/post-1",
1015
+ "destination": "/blog/post-1",
1016
+ "statusCode": 301
1017
+ },
1018
+ {
1019
+ "source": "/old-blog/post-2",
1020
+ "destination": "/blog/post-2",
1021
+ "statusCode": 301
1022
+ },
1023
+ {
1024
+ "source": "/products/old-sku-123",
1025
+ "destination": "/products/new-sku-456",
1026
+ "statusCode": 308
1027
+ }
1028
+ ]
1029
+ ```
1030
+
1031
+ **Use Cases for Bulk Import:**
1032
+ - Migrating from another platform with hundreds of URLs
1033
+ - SEO redirects from spreadsheet/database exports
1034
+ - Bulk product SKU changes
1035
+ - Content restructuring with many path changes
1036
+
1037
+ **Example Files:**
1038
+ See `examples/redirects.csv` and `examples/redirects.json` in this package for ready-to-use templates.
1039
+
1040
+ **Learn More:**
1041
+ - [Static Redirects](https://www.contentstack.com/docs/developers/launch/edge-url-redirects)
1042
+ - [Cache Priming](https://www.contentstack.com/docs/developers/launch/cache-priming)
1043
+
1044
+ ---
1045
+
1046
+ ### `npx launch-help`
1047
+
1048
+ Display complete reference guide with all methods and examples.
1049
+
1050
+ **Usage:**
1051
+ ```bash
1052
+ npx launch-help
1053
+ ```
1054
+
1055
+ **Shows:**
1056
+ - All available methods with parameters
1057
+ - Return types and examples
1058
+ - CLI commands
1059
+ - Quick links to documentation
127
1060
 
128
1061
  ---
129
1062
 
130
1063
  ## 🌐 Platform Support
131
1064
 
132
- This library is exclusively optimized for **[Contentstack Launch](https://www.contentstack.com/docs/developers/launch)**. It assumes an environment where `Request`, `Response`, and standard Edge Global APIs are available.
1065
+ This library is exclusively optimized for **[Contentstack Launch](https://www.contentstack.com/docs/developers/launch)**. It assumes an environment where:
1066
+ - Standard Web APIs (`Request`, `Response`, `fetch`) are available
1067
+ - Edge runtime environment
1068
+ - Contentstack Launch geo-location headers
1069
+
1070
+ **Not compatible with:**
1071
+ - Node.js servers
1072
+ - Traditional hosting platforms
1073
+ - Other edge platforms (Cloudflare Workers, Vercel Edge, etc.)
1074
+
1075
+ ---
1076
+
1077
+ ## 🤝 Contributing
1078
+
1079
+ Contributions are welcome! Please feel free to submit issues or pull requests.
1080
+
1081
+ **Repository:** https://github.com/AryanBansal-launch/launch-edge-utils
133
1082
 
134
1083
  ---
135
1084
 
136
1085
  ## 📄 License
137
1086
 
138
1087
  Distributed under the MIT License. See `LICENSE` for more information.
1088
+
1089
+ ---
1090
+
1091
+ ## 🔗 Useful Links
1092
+
1093
+ - **[Contentstack Launch Documentation](https://www.contentstack.com/docs/developers/launch)**
1094
+ - **[Edge Functions Guide](https://www.contentstack.com/docs/developers/launch/edge-functions)**
1095
+ - **[NPM Package](https://www.npmjs.com/package/@aryanbansal-launch/edge-utils)**
1096
+ - **[GitHub Repository](https://github.com/AryanBansal-launch/launch-edge-utils)**
1097
+
1098
+ ---
1099
+
1100
+ **Made with ❤️ for the Contentstack Launch community**