UNPKG

@spoolcms/nextjs

Version:

The beautiful headless CMS for Next.js developers

163 lines (135 loc) 5.32 kB
/** * Complete webhook route example for Spool CMS * Copy this to: app/api/webhooks/spool/route.ts * * NOTE: For live updates, use the useSpoolLiveUpdates hook instead. * This webhook handler is for production deployments and server-side processing only. * * This example includes: * - Production webhook handling with signature verification * - Comprehensive revalidation logic * - Error handling and logging */ import { createSpoolWebhookHandler } from '@spoolcms/nextjs'; import { revalidatePath } from 'next/cache'; const handleWebhook = createSpoolWebhookHandler({ // Webhook secret for signature verification (recommended for production) secret: process.env.SPOOL_WEBHOOK_SECRET, // Main webhook handler - called for production webhooks onWebhook: async (data, headers) => { const { event, collection, slug, item_id } = data; console.log(`[${headers.deliveryId}] Processing ${event} for ${collection}${slug ? `/${slug}` : ''} (${item_id})`); // Collection-specific revalidation logic const pathsToRevalidate: string[] = []; switch (collection) { case 'blog': // Revalidate blog listing page pathsToRevalidate.push('/blog'); // Revalidate specific blog post if slug exists if (slug) { pathsToRevalidate.push(`/blog/${slug}`); } // Revalidate blog category pages if you have them // pathsToRevalidate.push('/blog/categories'); break; case 'pages': // Revalidate specific page if (slug) { pathsToRevalidate.push(`/${slug}`); } break; case 'products': // Revalidate products listing pathsToRevalidate.push('/products'); // Revalidate specific product page if (slug) { pathsToRevalidate.push(`/products/${slug}`); } break; case 'authors': // Revalidate authors listing pathsToRevalidate.push('/authors'); // Revalidate specific author page and their posts if (slug) { pathsToRevalidate.push(`/authors/${slug}`); pathsToRevalidate.push(`/blog/author/${slug}`); } break; default: // Generic collection handling pathsToRevalidate.push(`/${collection}`); if (slug) { pathsToRevalidate.push(`/${collection}/${slug}`); } } // Always revalidate these common paths pathsToRevalidate.push('/'); // Home page pathsToRevalidate.push('/sitemap.xml'); // Sitemap // Add RSS feed if you have one // pathsToRevalidate.push('/feed.xml'); // Event-specific handling switch (event) { case 'content.deleted': // For deletions, you might want to revalidate parent collections more aggressively pathsToRevalidate.push(`/${collection}`); console.log(`[${headers.deliveryId}] Content deleted - revalidating collection: ${collection}`); break; case 'content.published': // For new publications, you might want to revalidate related content console.log(`[${headers.deliveryId}] Content published - triggering broader revalidation`); break; case 'content.created': // For new content, revalidate listings pathsToRevalidate.push(`/${collection}`); console.log(`[${headers.deliveryId}] New content created in: ${collection}`); break; case 'content.updated': // Standard update - paths already added above break; } // Perform all revalidations const revalidationPromises = pathsToRevalidate.map(async (path) => { try { // Defer revalidation to avoid Next.js 15 render phase restriction await new Promise(resolve => setTimeout(resolve, 0)); revalidatePath(path); console.log(`[${headers.deliveryId}] Revalidated: ${path}`); } catch (error) { console.error(`[${headers.deliveryId}] Failed to revalidate ${path}:`, error); } }); // Wait for all revalidations to complete await Promise.allSettled(revalidationPromises); console.log(`[${headers.deliveryId}] Revalidated ${pathsToRevalidate.length} paths`); }, // Optional: Custom error handler onError: async (error, request) => { console.error('Webhook processing error:', error); // You could send error notifications here // await sendErrorNotification(error); // Return a custom error response return new Response(JSON.stringify({ error: 'Webhook processing failed', message: error.message, timestamp: new Date().toISOString() }), { status: 500, headers: { 'Content-Type': 'application/json', 'X-Spool-Error': 'true' } }); } }); export const POST = handleWebhook; // Optional: Handle CORS preflight requests if needed export async function OPTIONS() { return new Response(null, { status: 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, X-Spool-Signature-256, X-Spool-Delivery, X-Spool-Event', }, }); }