diff --git a/middleware.ts b/middleware.ts index d744c23..0fd2876 100644 --- a/middleware.ts +++ b/middleware.ts @@ -8,7 +8,7 @@ import arcjet, { detectBot, shield, tokenBucket } from "@arcjet/next"; const publicPaths = ["/sign-in", "/sign-up"]; -// Arcjet +// Arcjet - Updated to allow OpenGraph crawlers const aj = arcjet({ key: process.env.ARCJET_KEY!, characteristics: ["ip.src"], @@ -16,7 +16,25 @@ const aj = arcjet({ shield({ mode: "LIVE" }), detectBot({ mode: "LIVE", - allow: ["CATEGORY:SEARCH_ENGINE"], + allow: [ + // Search engines + "CATEGORY:SEARCH_ENGINE", + + // OpenGraph/Social Media Crawlers + "FACEBOOK_CRAWLER", + "FACEBOOK_CRAWLER", + "TWITTER_CRAWLER", + "LINKEDIN_CRAWLER", + "DISCORD_CRAWLER", + "SLACK_CRAWLER", + "TELEGRAM_CRAWLER", + "WHATSAPP_CRAWLER", + "PINTREST_CRAWLER", + "REDDIT_CRAWLER", + + // Meta (Facebook) specific crawlers + "FACEBOOK_CRAWLER", + ], }), tokenBucket({ mode: "LIVE", @@ -28,6 +46,26 @@ const aj = arcjet({ }); export async function middleware(request: NextRequest) { + // Skip Arcjet for static assets and OpenGraph images + const pathname = request.nextUrl.pathname; + + // Don't apply Arcjet to these paths + if ( + pathname.startsWith("/_next") || + pathname.startsWith("/favicon") || + pathname.startsWith("/og-image") || + pathname.startsWith("/twitter-image") || + pathname.startsWith("/apple-touch-icon") || + pathname.endsWith(".png") || + pathname.endsWith(".jpg") || + pathname.endsWith(".jpeg") || + pathname.endsWith(".svg") || + pathname.endsWith(".ico") || + pathname.endsWith(".webp") + ) { + return NextResponse.next(); + } + // 1. Arcjet const decision = await aj.protect(request, { requested: 1 }); @@ -69,7 +107,9 @@ export async function middleware(request: NextRequest) { return NextResponse.next(); } -// exceptions for api routes and static files +// Updated matcher to exclude image files export const config = { - matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], + matcher: [ + "/((?!api|_next/static|_next/image|favicon.ico|.*\\.png|.*\\.jpg|.*\\.jpeg|.*\\.svg|.*\\.ico|.*\\.webp).*)", + ], }; diff --git a/next.config.ts b/next.config.ts index ae730a4..0e6dfc0 100644 --- a/next.config.ts +++ b/next.config.ts @@ -24,10 +24,11 @@ const getCspHeader = () => { "'unsafe-inline'", ], - // Image sources + // Image sources - Allow any HTTPS source to prevent blocking OG crawlers "img-src": [ "'self'", "data:", // Base64 encoded images + "https:", // Allow all HTTPS images for OG crawlers "https://via.placeholder.com", // Placeholder images ], @@ -50,7 +51,7 @@ const getCspHeader = () => { // Web workers "worker-src": ["'self'", "blob:"], - // Embedding in frames + // Embedding in frames - Allow same origin for better compatibility "frame-src": ["'self'"], // Block all objects/embeds @@ -59,8 +60,8 @@ const getCspHeader = () => { // Form submission "form-action": ["'self'"], - // Block iframe embedding of our site - "frame-ancestors": ["'none'"], + // Don't block iframe embedding by crawlers - remove this restriction + // "frame-ancestors": ["'none'"], // Commented out to allow OG crawlers // Force HTTPS (only in production) ...(process.env.NODE_ENV === "production" ? { "upgrade-insecure-requests": [] } : {}), @@ -96,8 +97,8 @@ const nextConfig = { images: { formats: ["image/webp"], contentDispositionType: "attachment", - // CSP for Next.js image optimization (separate from main CSP) - contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", + // Relaxed CSP for Next.js image optimization + contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' data: https:;", }, // URL redirects @@ -128,9 +129,9 @@ const nextConfig = { value: "nosniff", }, { - // Block iframe embedding + // Allow iframe embedding for OG crawlers - use SAMEORIGIN instead of DENY key: "X-Frame-Options", - value: "DENY", + value: "SAMEORIGIN", }, { // Enable XSS protection (legacy)