Ghost
Ghost
Integration Guide

Install Ghost Shield

Identify, block, and analyze AI bot traffic in your application.
✨ Designed for Next.js (v13+)

Edge Caching

Rules are fetched once and cached in memory. Zero latency impact.

Fail Open

If our API goes down, your site stays up. Security never breaks UX.

Async Logs

Analytics are sent after the response. No blocking the user.

1

Get your API Key (Optional)

Required for Analytics and Custom Rules. Not required for basic protection.

Want to see who you blocked?Get Pro Key
2

Configure Environment

Add your key to your .env.local file.

bash
GHOST_API_KEY=sk_ghost_...
3

Install the Shield

Create a new file at lib/shield.ts and paste the following code.
This is a zero-dependency module designed for Next.js Edge Runtime.

typescript
/* lib/shield.ts */
import { NextRequest, NextResponse } from 'next/server';

// Configuration Interface
interface ShieldConfig {
    ghostApiUrl: string; 
    apiKey?: string; 
    allow?: string[]; // Whitelist slugs e.g. ['googlebot']
    debug?: boolean;
}

interface ShieldRules {
    blocked: {
        user_agents: string[];
        ips: string[];
    };
}

// In-Memory Cache (Edge Friendly)
let cachedRules: ShieldRules | null = null;
let lastFetch = 0;
const CACHE_TTL = 3600 * 1000; // 1 Hour

export class GhostShield {
    private config: ShieldConfig;

    constructor(apiKey?: string, apiUrl = "https://ghost.tech/api/v1/rules") {
        this.config = { apiKey, ghostApiUrl: apiUrl };
    }

    async inspect(request: NextRequest): Promise<{ isBlocked: boolean }> {
        try {
            const rules = await this.getRules();
            if (!rules) return { isBlocked: false }; // Fail Open

            const ua = request.headers.get('user-agent') || '';
            const ip = request.headers.get('x-forwarded-for') || ''; 

            let isBlocked = false;

            // 1. Check User Agent
            if (this.checkUserAgent(ua, rules.blocked.user_agents)) {
                // Check Allow List (Whitelist)
                const isAllowed = this.config.allow?.some(slug => 
                    ua.toLowerCase().includes(slug.toLowerCase())
                );
                if (!isAllowed) {
                     isBlocked = true;
                }
            }

            // 2. Check IP (Simple CIDR or Exact Match)
            if (!isBlocked && this.checkIp(ip, rules.blocked.ips)) {
                isBlocked = true;
            }

            // 3. Logistics
            if (this.config.apiKey) {
                // Fire and Forget Log
                this.logRequest({
                    apiKey: this.config.apiKey,
                    userAgent: ua,
                    path: request.nextUrl.pathname,
                    verdict: isBlocked ? 'blocked' : 'allowed'
                }).catch(console.error);
            }

            return { isBlocked };
        } catch (err) {
            console.error('[Ghost] Shield Error:', err);
            return { isBlocked: false }; // Fail Open
        }
    }

    private async getRules(): Promise<ShieldRules | null> {
        const now = Date.now();
        if (cachedRules && (now - lastFetch < CACHE_TTL)) return cachedRules;

        try {
            const res = await fetch(this.config.ghostApiUrl, {
                headers: { 'User-Agent': 'Ghost-Shield/1.0' },
                signal: AbortSignal.timeout(1500),
            });
            if (res.ok) {
                cachedRules = await res.json();
                lastFetch = now;
                return cachedRules;
            }
        } catch (e) { /* Ignore fetch errors */ }
        return cachedRules;
    }

    private checkUserAgent(ua: string, patterns: string[]): boolean {
        return patterns.some(pattern => ua.includes(pattern));
    }

    private checkIp(ip: string, list: string[]): boolean {
        return list.includes(ip); 
    }

    private async logRequest(data: any) {
        // Use string concatenation for safety in docs
        const endpoint = new URL(this.config.ghostApiUrl).origin + '/api/v1/log';
        await fetch(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        });
    }
}
4

Activate Middleware

Using Next.js 16?

In Next.js 16+, middleware.ts is deprecated. Create proxy.ts instead and export proxy instead of middleware.

Import and use the shield in your middleware.ts (or proxy.ts).

typescript
import { NextRequest, NextResponse } from 'next/server';
import { GhostShield } from '@/lib/shield';

const shield = new GhostShield(process.env.GHOST_API_KEY);

// For Next.js 16+: export async function proxy(req: NextRequest)
export async function middleware(req: NextRequest) {
  // 1. Inspect the request
  const decision = await shield.inspect(req);

  // 2. Act on the decision
  if (decision.isBlocked) {
    return new NextResponse('Access Denied by Ghost Shield', { status: 403 });
  }

  // 3. Continue
  return NextResponse.next();
}
5

Verify Protection

Deploy your app. Then, simulate a bot request to test the shield.

bash
curl -A "GPTBot" https://your-site.com

You should receive a 403 Forbidden response. Check your Ghost Dashboard to see the analytics appearing in real-time.