@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/bin/launch-config.js +58 -13
- package/bin/launch-help.js +192 -0
- package/dist/ai/gemini.js +153 -0
- package/dist/utils/inject.js +45 -0
- package/examples/redirects.csv +12 -0
- package/examples/redirects.json +53 -0
- package/package.json +22 -6
- package/readme.md +1013 -51
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
|
-
##
|
|
10
|
+
## 📋 Table of Contents
|
|
11
11
|
|
|
12
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
##
|
|
54
|
+
## 🔄 Usage Flow
|
|
27
55
|
|
|
28
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
60
|
+
```
|
|
61
|
+
User Request → Edge Function → Your Application
|
|
62
|
+
```
|
|
40
63
|
|
|
41
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
67
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
83
|
-
const ipCheck = ipAccessControl(request, {
|
|
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
|
-
//
|
|
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: "
|
|
112
|
+
password: "securepass123"
|
|
91
113
|
});
|
|
92
114
|
if (auth && auth.status === 401) return auth;
|
|
93
115
|
|
|
94
|
-
//
|
|
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: "/
|
|
126
|
+
path: "/old-page",
|
|
97
127
|
to: "/new-page",
|
|
98
128
|
status: 301
|
|
99
129
|
});
|
|
100
130
|
if (redirect) return redirect;
|
|
101
131
|
|
|
102
|
-
//
|
|
132
|
+
// 5️⃣ PERSONALIZATION
|
|
133
|
+
// Get user's location
|
|
103
134
|
const geo = getGeoHeaders(request);
|
|
104
|
-
if (geo.country === "
|
|
105
|
-
|
|
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
|
-
//
|
|
149
|
+
// 7️⃣ DEFAULT: Pass to origin
|
|
109
150
|
return passThrough(request);
|
|
110
151
|
}
|
|
111
152
|
```
|
|
112
153
|
|
|
113
154
|
---
|
|
114
155
|
|
|
115
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
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
|
|
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**
|