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/[email protected]";

// 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/[email protected]";

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/[email protected]";

// 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/[email protected]";

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/[email protected]";

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/[email protected]";

// 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)