DEV Community

Cover image for How to Use Arcjet in Netlify Edge Functions
Ben Sabic
Ben Sabic

Posted on

How to Use Arcjet in Netlify Edge Functions

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setup and Installation
  4. Basic Implementation
  5. Security Features
  6. Advanced Patterns
  7. Testing Locally
  8. Deployment
  9. Best Practices
  10. Troubleshooting

Introduction

What is Arcjet?

Arcjet helps developers protect their apps in just a few lines of code. Implement bot protection, rate limiting, email verification, PII detection, & defend against common attacks. It's a security-as-code SDK that integrates directly into your application, providing protection at the edge.

What are Netlify Edge Functions?

Edge Functions connect the Netlify platform and workflow with an open runtime standard at the network edge. This enables you to build fast, personalized web experiences with an ecosystem of development tools. They run on the Deno runtime and execute close to your users for optimal performance.

Why Use Arcjet with Netlify Edge Functions?

  • Performance: Both run at the edge, minimizing latency
  • Security: Add protection without additional infrastructure
  • Developer Experience: Simple integration with just a few lines of code
  • Flexibility: Configure different rules for different routes

Prerequisites

Before starting, ensure you have:

  • Node.js 18+ installed
  • A Netlify account
  • The Netlify CLI installed: npm install -g netlify-cli
  • An Arcjet account (free tier available at arcjet.com)
  • Basic knowledge of JavaScript/TypeScript

Setup and Installation

Step 1: Create a New Netlify Site

# Create a new directory for your project mkdir my-secure-edge-app cd my-secure-edge-app # Initialize a new Netlify site netlify init # Create the edge functions directory mkdir -p netlify/edge-functions 
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Netlify

Create a netlify.toml file in your project root:

[build] publish = "public" [[edge_functions]] path = "/api/*" function = "api-handler" [[edge_functions]] path = "/protected/*" function = "protected-route" 
Enter fullscreen mode Exit fullscreen mode

Step 3: Set Up Arcjet

  1. Sign up for a free Arcjet account at app.arcjet.com
  2. Create a new site in the Arcjet dashboard
  3. Copy your API key
  4. Add the key to your Netlify environment variables:
# Using Netlify CLI netlify env:set ARCJET_KEY "your-arcjet-key-here" 
Enter fullscreen mode Exit fullscreen mode

Basic Implementation

Creating Your First Protected Edge Function

Create netlify/edge-functions/api-handler.ts:

import arcjet, { shield, tokenBucket } from "https://esm.sh/@arcjet/deno@1.0.0-alpha.34"; // Initialize Arcjet with your site key const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, characteristics: ["ip"], // Track by IP address rules: [ // Shield protects against common attacks shield({ mode: "LIVE", // Use "DRY_RUN" for testing }), // Rate limiting with token bucket tokenBucket({ mode: "LIVE", refillRate: 10, // 10 tokens per interval interval: 60, // Per minute capacity: 50, // Maximum tokens }), ], }); export default async (request: Request, context: Context) => { // Protect the request const decision = await aj.protect(request); if (decision.isDenied()) { return new Response( JSON.stringify({ error: "Forbidden", reason: decision.reason }), { status: 403, headers: { "Content-Type": "application/json" } } ); } // Your API logic here return new Response( JSON.stringify({ message: "Hello from protected API!", geo: context.geo // Netlify provides geolocation data }), { status: 200, headers: { "Content-Type": "application/json" } } ); }; export const config = { path: "/api/*" }; 
Enter fullscreen mode Exit fullscreen mode

Security Features

1. Bot Detection

Protect against automated attacks and scrapers:

import arcjet, { detectBot } from "https://esm.sh/@arcjet/deno@1.0.0-alpha.34"; const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, rules: [ detectBot({ mode: "LIVE", allow: [], // Explicitly allow no bots // Or allow specific bots: // allow: ["GOOGLE_CRAWLER", "BING_CRAWLER"], }), ], }); 
Enter fullscreen mode Exit fullscreen mode

2. Rate Limiting Patterns

Different rate limiting strategies for various use cases:

import arcjet, { fixedWindow, slidingWindow, tokenBucket } from "https://esm.sh/@arcjet/deno@1.0.0-alpha.34"; // Fixed window - Simple time-based limits const fixedWindowRule = fixedWindow({ mode: "LIVE", window: "1h", // 1 hour window max: 100, // 100 requests per window }); // Sliding window - More accurate rate limiting const slidingWindowRule = slidingWindow({ mode: "LIVE", interval: 60, // 60 seconds max: 10, // 10 requests per interval }); // Token bucket - Allows bursts const tokenBucketRule = tokenBucket({ mode: "LIVE", refillRate: 1, // 1 token per interval interval: 1, // Every second capacity: 10, // Burst capacity }); 
Enter fullscreen mode Exit fullscreen mode

3. Email Validation

Validate email addresses at the edge:

import arcjet, { validateEmail } from "https://esm.sh/@arcjet/deno@1.0.0-alpha.34"; const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, rules: [], // Email validation is called separately }); export default async (request: Request) => { const body = await request.json(); const email = body.email; // Validate email const emailDecision = await aj.validateEmail(email); if (!emailDecision.isValid()) { return new Response( JSON.stringify({ error: "Invalid email", details: emailDecision.details }), { status: 400 } ); } // Continue with valid email... }; 
Enter fullscreen mode Exit fullscreen mode

4. Sensitive Information Detection

Detect and redact PII:

import arcjet, { sensitiveInfo } from "https://esm.sh/@arcjet/deno@1.0.0-alpha.34"; const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, rules: [ sensitiveInfo({ mode: "LIVE", detect: ["EMAIL", "PHONE", "CREDIT_CARD"], redact: true, }), ], }); 
Enter fullscreen mode Exit fullscreen mode

Advanced Patterns

Per-User Rate Limiting

Track rate limits by authenticated user:

export default async (request: Request, context: Context) => { // Extract user ID from JWT or session const userId = await getUserIdFromRequest(request); const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, characteristics: ["userId"], // Track by user ID rules: [ tokenBucket({ mode: "LIVE", refillRate: 100, interval: 3600, // Per hour capacity: 100, }), ], }); const decision = await aj.protect(request, { userId }); if (decision.isDenied()) { return new Response("Rate limit exceeded", { status: 429 }); } // Process request... }; 
Enter fullscreen mode Exit fullscreen mode

Geolocation-Based Rules

Use Netlify's geo data with Arcjet:

export default async (request: Request, context: Context) => { const country = context.geo?.country?.code || "UNKNOWN"; // Apply stricter limits for certain regions const rules = country === "SUSPICIOUS_REGION" ? [tokenBucket({ mode: "LIVE", refillRate: 1, interval: 60, capacity: 5 })] : [tokenBucket({ mode: "LIVE", refillRate: 10, interval: 60, capacity: 50 })]; const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, rules, }); const decision = await aj.protect(request); // Handle decision... }; 
Enter fullscreen mode Exit fullscreen mode

Conditional Protection

Apply different rules based on routes:

export default async (request: Request, context: Context) => { const url = new URL(request.url); const path = url.pathname; // Stricter rules for sensitive endpoints const rules = path.startsWith("/admin") ? [ shield({ mode: "LIVE" }), tokenBucket({ mode: "LIVE", refillRate: 1, interval: 60, capacity: 10 }), detectBot({ mode: "LIVE", allow: [] }), ] : [ shield({ mode: "LIVE" }), tokenBucket({ mode: "LIVE", refillRate: 10, interval: 60, capacity: 100 }), ]; const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, rules, }); // Continue with protection... }; 
Enter fullscreen mode Exit fullscreen mode

Testing Locally

Using Netlify Dev

You can use Netlify CLI to test edge functions locally before deploying them to Netlify.

# Start local development server netlify dev # Your edge functions will be available at: # http://localhost:8888/api/* # http://localhost:8888/protected/* 
Enter fullscreen mode Exit fullscreen mode

Testing Arcjet Rules

Create a test script test-protection.js:

// Test rate limiting async function testRateLimit() { const endpoint = "http://localhost:8888/api/test"; for (let i = 0; i < 15; i++) { const response = await fetch(endpoint); console.log(`Request ${i + 1}: ${response.status}`); if (response.status === 403) { const body = await response.json(); console.log("Blocked:", body.reason); } } } // Test bot detection async function testBotDetection() { const response = await fetch("http://localhost:8888/api/test", { headers: { "User-Agent": "bot/1.0" } }); console.log("Bot test:", response.status); } testRateLimit(); testBotDetection(); 
Enter fullscreen mode Exit fullscreen mode

Deployment

Deploy to Netlify

# Deploy to production netlify deploy --prod # Or use Git-based deployments git push origin main 
Enter fullscreen mode Exit fullscreen mode

Monitor in Arcjet Dashboard

After deployment:

  1. Visit your Arcjet dashboard
  2. View real-time analytics
  3. Monitor blocked requests
  4. Adjust rules as needed

Best Practices

1. Start with DRY_RUN Mode

When in DRY_RUN mode, each rule will return its decision, but the end conclusion will always be ALLOW. This allows you to run Arcjet in passive / demo mode to test rules before enabling them.

// Start with DRY_RUN for testing shield({ mode: "DRY_RUN" }) // Switch to LIVE when ready shield({ mode: "LIVE" }) 
Enter fullscreen mode Exit fullscreen mode

2. Use Custom Characteristics

Track requests by meaningful identifiers:

const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, characteristics: ["userId", "apiKey", "ip"], rules: [ tokenBucket({ mode: "LIVE", refillRate: 100, interval: 3600, capacity: 1000, }), ], }); // Pass characteristics when protecting const decision = await aj.protect(request, { userId: user.id, apiKey: apiKey, }); 
Enter fullscreen mode Exit fullscreen mode

3. Handle Errors Gracefully

try { const decision = await aj.protect(request); if (decision.isDenied()) { // Return appropriate error response return new Response("Forbidden", { status: 403 }); } } catch (error) { // Arcjet fails open by default console.error("Arcjet error:", error); // Continue processing the request } 
Enter fullscreen mode Exit fullscreen mode

4. Optimize for Performance

  • Initialize Arcjet outside request handlers
  • Use appropriate caching strategies
  • Minimize custom characteristic calculations

Troubleshooting

Common Issues

1. ARCJET_KEY not found

// Check if key is set if (!Deno.env.get("ARCJET_KEY")) { console.error("ARCJET_KEY environment variable not set"); } 
Enter fullscreen mode Exit fullscreen mode

2. Import errors

Make sure to use the correct import URL:

// Correct import arcjet from "https://esm.sh/@arcjet/deno@1.0.0-alpha.34"; // Incorrect (npm: prefix doesn't work reliably in all cases) import arcjet from "npm:@arcjet/deno"; 
Enter fullscreen mode Exit fullscreen mode

3. Local development issues

If you see this error then you are probably running an older version of Next.js. For Netlify Edge Functions, ensure you're using the latest Netlify CLI.

Debug Mode

Enable debug logging:

const aj = arcjet({ key: Deno.env.get("ARCJET_KEY")!, rules: [...], // Enable debug logging log: { level: "debug" } }); 
Enter fullscreen mode Exit fullscreen mode

Getting Help

Conclusion

You've now learned how to integrate Arcjet security features with Netlify Edge Functions. This combination provides:

  • Edge-native security: Protection runs close to your users
  • Flexible rules: Configure different protections for different routes
  • Easy integration: Just a few lines of code
  • Comprehensive protection: Shield against bots, attacks, and abuse

Start with basic protection and gradually add more sophisticated rules as your application grows. Remember to monitor your Arcjet dashboard to understand traffic patterns and adjust rules accordingly.

Top comments (0)