A comprehensive toolkit for Contentstack Launch (the high-performance frontend hosting service by Contentstack). This library provides production-ready utilities to simplify Edge Functions development, security, and performance optimization.
npm install @aryanbansal-launch/edge-utilsRun this command from your project root (where package.json is located):
npx create-launch-edgeThis automatically creates:
functions/directoryfunctions/[proxy].edge.jswith production-ready boilerplate
- Open
functions/[proxy].edge.js - Customize the utilities based on your needs
- Deploy to Contentstack Launch
Need help? Run:
npx launch-helpEdge 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:
User Request β Edge Function β Your Application
Every utility follows this pattern:
const result = utilityFunction(request, options);
if (result) return result; // Early return if condition met
// Continue to next check...Here's how utilities work together in functions/[proxy].edge.js:
import {
blockDefaultDomains,
handleNextJS_RSC,
blockAICrawlers,
ipAccessControl,
protectWithBasicAuth,
redirectIfMatch,
getGeoHeaders,
jsonResponse,
passThrough
} from "@aryanbansal-launch/edge-utils";
export default async function handler(request, context) {
// 1οΈβ£ SECURITY LAYER
// Block default domains (SEO best practice)
const domainCheck = blockDefaultDomains(request);
if (domainCheck) return domainCheck;
// Block AI bots and crawlers
const botCheck = blockAICrawlers(request);
if (botCheck) return botCheck;
// IP-based access control
const ipCheck = ipAccessControl(request, {
allow: ["203.0.113.10", "198.51.100.5"]
});
if (ipCheck) return ipCheck;
// 2οΈβ£ AUTHENTICATION LAYER
// Protect staging environments
const auth = await protectWithBasicAuth(request, {
hostnameIncludes: "staging.myapp.com",
username: "admin",
password: "securepass123"
});
if (auth && auth.status === 401) return auth;
// 3οΈβ£ FRAMEWORK FIXES
// Fix Next.js RSC header issues
const rscCheck = await handleNextJS_RSC(request, {
affectedPaths: ["/shop", "/products", "/about"]
});
if (rscCheck) return rscCheck;
// 4οΈβ£ ROUTING LAYER
// Handle redirects
const redirect = redirectIfMatch(request, {
path: "/old-page",
to: "/new-page",
status: 301
});
if (redirect) return redirect;
// 5οΈβ£ PERSONALIZATION
// Get user's location
const geo = getGeoHeaders(request);
if (geo.country === "US") {
console.log(`US visitor from ${geo.city}, ${geo.region}`);
}
// 6οΈβ£ CUSTOM API ENDPOINTS
const url = new URL(request.url);
if (url.pathname === "/api/health") {
return jsonResponse({
status: "healthy",
region: geo.region,
timestamp: Date.now()
});
}
// 7οΈβ£ DEFAULT: Pass to origin
return passThrough(request);
}Block AI crawlers and bots from accessing your site.
Parameters:
request(Request) - The incoming request objectbots(string[], optional) - Custom list of bot user-agents to block
Returns: Response | null
- Returns
403 Forbiddenif bot detected - Returns
nullif no bot detected (continue processing)
Default Blocked Bots: The following bots are blocked by default (case-insensitive):
claudebot- Anthropic's Claude AI crawlergptbot- OpenAI's GPT crawlergooglebot- Google's web crawlerbingbot- Microsoft Bing's crawlerahrefsbot- Ahrefs SEO crawleryandexbot- Yandex search engine crawlersemrushbot- SEMrush SEO tool crawlermj12bot- Majestic SEO crawlerfacebookexternalhit- Facebook's link preview crawlertwitterbot- Twitter's link preview crawler
Example:
// Use default bot list
const response = blockAICrawlers(request);
if (response) return response;
// Custom bot list
const response = blockAICrawlers(request, [
"gptbot",
"claudebot",
"my-custom-bot"
]);
if (response) return response;Use Cases:
- Protect content from AI scraping
- Reduce server load from aggressive crawlers
- Comply with content usage policies
Block access via default Launch domains (*.contentstackapps.com).
Parameters:
request(Request) - The incoming request objectoptions(object, optional)domainToBlock(string) - Custom domain to block (default: "contentstackapps.com")
Returns: Response | null
- Returns
403 Forbiddenif default domain detected - Returns
nullif custom domain used
Example:
// Block default Launch domain
const response = blockDefaultDomains(request);
if (response) return response;
// Block custom domain
const response = blockDefaultDomains(request, {
domainToBlock: "myolddomain.com"
});
if (response) return response;Use Cases:
- Force users to custom domain for SEO
- Prevent duplicate content indexing
- Professional branding
Learn More: Blocking Default Launch Domains
Whitelist or blacklist IPs at the edge.
Parameters:
request(Request) - The incoming request objectoptions(object)allow(string[], optional) - Whitelist of allowed IPsdeny(string[], optional) - Blacklist of denied IPs
Returns: Response | null
- Returns
403 Forbiddenif IP blocked - Returns
nullif IP allowed
Example:
// Whitelist specific IPs (only these can access)
const response = ipAccessControl(request, {
allow: ["203.0.113.10", "198.51.100.5"]
});
if (response) return response;
// Blacklist specific IPs
const response = ipAccessControl(request, {
deny: ["192.0.2.100", "192.0.2.101"]
});
if (response) return response;
// Combine both (deny takes precedence)
const response = ipAccessControl(request, {
allow: ["203.0.113.0/24"], // Allow subnet
deny: ["203.0.113.50"] // Except this one
});
if (response) return response;Use Cases:
- Restrict admin panels to office IPs
- Block malicious IPs
- Create staging environment access control
Learn More: IP-Based Access Control
Add Basic Authentication to protect environments.
Parameters:
request(Request) - The incoming request objectoptions(object)hostnameIncludes(string) - Protect URLs containing this hostnameusername(string) - Username for authenticationpassword(string) - Password for authenticationrealm(string, optional) - Auth realm name (default: "Protected Area")
Returns: Promise<Response> | null
- Returns
401 Unauthorizedif auth fails - Returns authenticated response if credentials valid
- Returns
nullif hostname doesn't match
Example:
// Protect staging environment
const auth = await protectWithBasicAuth(request, {
hostnameIncludes: "staging.myapp.com",
username: "admin",
password: "securepass123",
realm: "Staging Environment"
});
if (auth && auth.status === 401) return auth;
// Protect specific path pattern
const url = new URL(request.url);
if (url.pathname.startsWith("/admin")) {
const auth = await protectWithBasicAuth(request, {
hostnameIncludes: url.hostname,
username: "admin",
password: "adminpass"
});
if (auth && auth.status === 401) return auth;
}Use Cases:
- Protect staging/dev environments
- Quick password protection for demos
- Restrict access to admin areas
Security Note: For production, use proper authentication systems. Basic Auth credentials are base64-encoded, not encrypted.
Learn More: Password Protection for Environments
Conditional redirects based on path and method.
Parameters:
request(Request) - The incoming request objectoptions(object)path(string) - Path to matchto(string) - Destination pathmethod(string, optional) - HTTP method to match (e.g., "GET", "POST")status(number, optional) - HTTP status code (default: 301)
Returns: Response | null
- Returns redirect response if path matches
- Returns
nullif no match
Example:
// Simple redirect
const redirect = redirectIfMatch(request, {
path: "/old-page",
to: "/new-page",
status: 301 // Permanent redirect
});
if (redirect) return redirect;
// Temporary redirect
const redirect = redirectIfMatch(request, {
path: "/maintenance",
to: "/coming-soon",
status: 302 // Temporary redirect
});
if (redirect) return redirect;
// Method-specific redirect
const redirect = redirectIfMatch(request, {
path: "/api/old-endpoint",
method: "POST",
to: "/api/v2/endpoint",
status: 308 // Permanent redirect (preserves method)
});
if (redirect) return redirect;Use Cases:
- SEO-friendly URL changes
- Migrate old URLs to new structure
- A/B testing redirects
- Maintenance mode redirects
Learn More: Edge URL Redirects
Contentstack Launch offers two ways to handle redirects. Choose the right approach based on your needs:
Best for: Static, predictable redirects that don't require logic
Pros:
- β‘ Faster: No edge function execution overhead
- π― Simpler: Pure configuration, no code needed
- π¦ Bulk-friendly: Easy to manage hundreds/thousands of redirects
- π§ Easy updates: Use
npx launch-configCLI with CSV/JSON import
Cons:
- β No dynamic logic (can't check cookies, headers, geo, etc.)
- β No conditional redirects based on request data
- β Limited to exact path or wildcard matching
Setup:
# Interactive CLI
npx launch-config
# Or bulk import from CSV/JSON
npx launch-config
# Choose option 2 or 3 for bulk importExample launch.json:
{
"redirects": [
{
"source": "/old-blog/:slug",
"destination": "/blog/:slug",
"statusCode": 301
},
{
"source": "/products/old-*",
"destination": "/products/new-*",
"statusCode": 308
}
]
}When to use:
- β SEO migrations with hundreds of URL changes
- β
Simple path rewrites (e.g.,
/old-pathβ/new-path) - β Bulk product SKU redirects
- β Static site restructuring
- β Predictable, rule-based redirects
Best for: Dynamic redirects requiring logic or request inspection
Pros:
- π§ Smart: Access cookies, headers, geo-location, user-agent
- π¨ Flexible: Complex conditional logic
- π Dynamic: Redirect based on A/B tests, feature flags, user data
- π Contextual: Different redirects per country/region
Cons:
- π Slightly slower (edge function execution)
- π§ Requires code changes
- π More complex for simple redirects
Setup:
import { redirectIfMatch } from '@aryanbansal-launch/edge-utils';
export default async function handler(request) {
// Dynamic redirect based on cookie
const cookie = request.headers.get('cookie');
if (cookie?.includes('beta=true')) {
return Response.redirect(new URL('/beta-features', request.url), 302);
}
// Geo-based redirect
const country = request.headers.get('x-cs-country');
if (country === 'FR') {
return Response.redirect('https://fr.mysite.com', 302);
}
// Conditional redirect
const redirect = redirectIfMatch(request, {
path: "/old-page",
to: "/new-page",
method: "GET",
status: 301
});
if (redirect) return redirect;
return passThrough(request);
}When to use:
- β Geo-location based redirects
- β A/B testing redirects
- β Cookie/session-based routing
- β User-agent specific redirects (mobile vs desktop)
- β Feature flag redirects
- β Maintenance mode with exceptions
- β Complex conditional logic
| Scenario | Use Config | Use Edge Function |
|---|---|---|
| 500+ simple URL redirects | β | β |
| SEO migration from old site | β | β |
| Redirect based on country | β | β |
| A/B test routing | β | β |
| Cookie-based redirects | β | β |
| Mobile vs desktop routing | β | β |
| Simple path changes | β | β |
| Maintenance mode (all users) | β | β |
| Maintenance mode (except admins) | β | β |
| Bulk product SKU changes | β | β |
π‘ Pro Tip: You can use both approaches together! Use launch.json for bulk static redirects and edge functions for dynamic logic.
Example Combined Approach:
// launch.json - handles 1000+ static SEO redirects
{
"redirects": [
{ "source": "/old-blog/:slug", "destination": "/blog/:slug", "statusCode": 301 }
// ... 1000 more redirects
]
}
// functions/[proxy].edge.js - handles dynamic logic
export default async function handler(request) {
// Geo-based redirect (dynamic)
const country = request.headers.get('x-cs-country');
if (country === 'FR') {
return Response.redirect('https://fr.mysite.com', 302);
}
// Static redirects handled by launch.json automatically
return passThrough(request);
}Fix Next.js React Server Components header issues.
Parameters:
request(Request) - The incoming request objectoptions(object)affectedPaths(string[]) - Array of paths with RSC issues
Returns: Promise<Response> | null
- Returns modified response if RSC issue detected
- Returns
nullif no issue
Example:
// Fix specific pages
const rsc = await handleNextJS_RSC(request, {
affectedPaths: ["/shop", "/products", "/about"]
});
if (rsc) return rsc;
// Fix all dynamic routes
const rsc = await handleNextJS_RSC(request, {
affectedPaths: ["/blog", "/products", "/categories"]
});
if (rsc) return rsc;What It Does:
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.
Use Cases:
- Fix "JSON showing instead of page" issues
- Resolve RSC caching problems
- Ensure proper page hydration
Learn More: Handling Next.js RSC Issues
Extract geo-location data from Launch headers.
Parameters:
request(Request) - The incoming request object
Returns: Object with geo data
{
country: string | null, // ISO country code (e.g., "US")
region: string | null, // Region/state code (e.g., "CA")
city: string | null, // City name (e.g., "San Francisco")
latitude: string | null, // Latitude coordinate
longitude: string | null // Longitude coordinate
}Example:
// Get user location
const geo = getGeoHeaders(request);
// Country-based logic
if (geo.country === "US") {
console.log(`US visitor from ${geo.city}, ${geo.region}`);
}
// Region-specific content
if (geo.country === "US" && geo.region === "CA") {
// Show California-specific content
}
// Distance-based logic
if (geo.latitude && geo.longitude) {
const userLat = parseFloat(geo.latitude);
const userLon = parseFloat(geo.longitude);
// Calculate distance to store, etc.
}
// Redirect based on location
const geo = getGeoHeaders(request);
if (geo.country === "FR") {
return Response.redirect("https://fr.mysite.com", 302);
}Use Cases:
- Personalize content by location
- Show region-specific pricing
- Redirect to country-specific sites
- Display nearest store locations
- Comply with regional regulations
Learn More: Geolocation Headers in Launch
Create JSON responses easily.
Parameters:
body(object) - JSON-serializable objectinit(ResponseInit, optional) - Additional response options (status, headers, etc.)
Returns: Response with Content-Type: application/json
Example:
// Simple JSON response
return jsonResponse({ status: "ok", message: "Success" });
// With custom status
return jsonResponse(
{ error: "Not found" },
{ status: 404 }
);
// With custom headers
return jsonResponse(
{ data: [...] },
{
status: 200,
headers: {
"Cache-Control": "max-age=3600",
"X-Custom-Header": "value"
}
}
);
// API endpoint example
const url = new URL(request.url);
if (url.pathname === "/api/user") {
const geo = getGeoHeaders(request);
return jsonResponse({
user: "john_doe",
location: {
country: geo.country,
city: geo.city
},
timestamp: Date.now()
});
}Use Cases:
- Create API endpoints at the edge
- Return structured error messages
- Build serverless functions
Forward request to origin server.
Parameters:
request(Request) - The incoming request object
Returns: Promise<Response> from origin
Example:
// Default: pass everything through
return passThrough(request);
// After all checks
export default async function handler(request, context) {
// ... all your checks ...
// If nothing matched, pass to origin
return passThrough(request);
}Use Cases:
- Default fallback after edge logic
- Forward requests that don't need edge processing
Generate launch.json configuration programmatically.
Parameters:
options(object)redirects(LaunchRedirect[], optional)rewrites(LaunchRewrite[], optional)cache(object, optional)cachePriming(object)urls(string[])
Returns: LaunchConfig object
Types:
interface LaunchRedirect {
source: string;
destination: string;
statusCode?: number;
response?: {
headers?: Record<string, string>;
};
}
interface LaunchRewrite {
source: string;
destination: string;
}Example:
import { generateLaunchConfig } from "@aryanbansal-launch/edge-utils";
import fs from "fs";
const config = generateLaunchConfig({
redirects: [
{
source: "/old-blog/:slug",
destination: "/blog/:slug",
statusCode: 301
},
{
source: "/products",
destination: "/shop",
statusCode: 308
}
],
rewrites: [
{
source: "/api/:path*",
destination: "https://api.mybackend.com/:path*"
}
],
cache: {
cachePriming: {
urls: ["/", "/about", "/products", "/contact"]
}
}
});
// Write to launch.json
fs.writeFileSync("launch.json", JSON.stringify(config, null, 2));Use Cases:
- Generate config from CMS data
- Automate bulk redirects
- Dynamic configuration management
export default async function handler(request, context) {
const url = new URL(request.url);
const geo = getGeoHeaders(request);
// 1. Block bots to reduce costs
const botCheck = blockAICrawlers(request);
if (botCheck) return botCheck;
// 2. Geo-based redirects
if (geo.country === "UK" && !url.hostname.includes("uk.")) {
return Response.redirect(`https://uk.myshop.com${url.pathname}`, 302);
}
// 3. Maintenance mode for specific regions
if (geo.country === "US" && url.pathname.startsWith("/checkout")) {
return jsonResponse(
{ error: "Checkout temporarily unavailable in your region" },
{ status: 503 }
);
}
// 4. Product redirects
const redirect = redirectIfMatch(request, {
path: "/products/old-sku-123",
to: "/products/new-sku-456",
status: 301
});
if (redirect) return redirect;
return passThrough(request);
}export default async function handler(request, context) {
const url = new URL(request.url);
// 1. Protect staging with Basic Auth
if (url.hostname.includes("staging")) {
const auth = await protectWithBasicAuth(request, {
hostnameIncludes: "staging",
username: "team",
password: process.env.STAGING_PASSWORD || "defaultpass"
});
if (auth && auth.status === 401) return auth;
}
// 2. Restrict dev environment to office IPs
if (url.hostname.includes("dev")) {
const ipCheck = ipAccessControl(request, {
allow: ["203.0.113.0/24"] // Office IP range
});
if (ipCheck) return ipCheck;
}
// 3. Block default domain on production
if (url.hostname.includes("myapp.com")) {
const domainCheck = blockDefaultDomains(request);
if (domainCheck) return domainCheck;
}
return passThrough(request);
}export default async function handler(request, context) {
const url = new URL(request.url);
const geo = getGeoHeaders(request);
// 1. Fix RSC issues on dynamic pages
const rscCheck = await handleNextJS_RSC(request, {
affectedPaths: ["/blog", "/products", "/categories"]
});
if (rscCheck) return rscCheck;
// 2. Edge API endpoints
if (url.pathname === "/api/geo") {
return jsonResponse({
country: geo.country,
region: geo.region,
city: geo.city
});
}
if (url.pathname === "/api/health") {
return jsonResponse({
status: "healthy",
timestamp: Date.now(),
region: geo.region
});
}
// 3. Block bots from expensive pages
if (url.pathname.startsWith("/search")) {
const botCheck = blockAICrawlers(request);
if (botCheck) return botCheck;
}
return passThrough(request);
}Initialize edge functions with production-ready boilerplate.
What it does:
- Checks you're in project root (where
package.jsonexists) - Creates
functions/directory if needed - Generates
functions/[proxy].edge.jswith example code
Usage:
cd /path/to/your/project
npx create-launch-edgeOutput:
π create-launch-edge: Contentstack Launch Initializer
β¨ New: Created /functions directory
β¨ New: Created /functions/[proxy].edge.js
π Setup Complete!
Next Steps:
1. Open functions/[proxy].edge.js
2. Customize your redirects, auth, and RSC paths
3. Deploy your project to Contentstack Launch
Interactive CLI to manage launch.json configuration with support for bulk imports.
What it does:
- Add/manage redirects (one-by-one or bulk import)
- Configure rewrites
- Set up cache priming URLs
- Preserves existing configuration
Usage:
npx launch-configInteractive Prompts:
π Launch Configuration Generator
How would you like to add redirects?
1) One by one (interactive)
2) Bulk import from CSV file
3) Bulk import from JSON file
4) Skip
Choose (1-4): 2
Enter CSV file path (e.g., ./redirects.csv): ./redirects.csv
β Imported 150 redirects from CSV.
Do you want to add a Rewrite? (y/n): y
Source path (e.g., /api/*): /api/*
Destination URL: https://backend.myapp.com/api/*
β Rewrite added.
Do you want to add Cache Priming URLs? (y/n): y
Note: Only relative paths are supported. No Regex/Wildcards.
Enter URLs separated by commas (e.g., /home,/about,/shop): /,/about,/products
β
Successfully updated launch.json!
CSV Format (redirects.csv):
source,destination,statusCode
/old-blog/post-1,/blog/post-1,301
/old-blog/post-2,/blog/post-2,301
/products/old-sku-123,/products/new-sku-456,308
/legacy/*,/new/*,301JSON Format (redirects.json):
[
{
"source": "/old-blog/post-1",
"destination": "/blog/post-1",
"statusCode": 301
},
{
"source": "/old-blog/post-2",
"destination": "/blog/post-2",
"statusCode": 301
},
{
"source": "/products/old-sku-123",
"destination": "/products/new-sku-456",
"statusCode": 308
}
]Use Cases for Bulk Import:
- Migrating from another platform with hundreds of URLs
- SEO redirects from spreadsheet/database exports
- Bulk product SKU changes
- Content restructuring with many path changes
Example Files:
See examples/redirects.csv and examples/redirects.json in this package for ready-to-use templates.
Learn More:
Display complete reference guide with all methods and examples.
Usage:
npx launch-helpShows:
- All available methods with parameters
- Return types and examples
- CLI commands
- Quick links to documentation
This library is exclusively optimized for Contentstack Launch. It assumes an environment where:
- Standard Web APIs (
Request,Response,fetch) are available - Edge runtime environment
- Contentstack Launch geo-location headers
Not compatible with:
- Node.js servers
- Traditional hosting platforms
- Other edge platforms (Cloudflare Workers, Vercel Edge, etc.)
Contributions are welcome! Please feel free to submit issues or pull requests.
Repository: https://github.com/AryanBansal-launch/launch-edge-utils
Distributed under the MIT License. See LICENSE for more information.
Made with β€οΈ for the Contentstack Launch community