Next.js Integration

Run reliable scheduled tasks on Next.js with webhook-based cron jobs. Perfect for serverless applications with built-in signature verification and monitoring.

Overview

Schedo integrates seamlessly with Next.js through webhook-based scheduling. Instead of traditional cron jobs that require persistent processes, Schedo sends HTTP requests to your Next.js API routes at scheduled times.

Key Benefits

  • Serverless-first: Works perfectly with Next.js serverless functions
  • No infrastructure: No need for external cron services or GitHub Actions
  • Secure: Built-in cryptographic signature verification
  • Monitored: Comprehensive webhook delivery tracking with Schedo.dev dashboard
  • Simple: Just one API endpoint to get started
  • Full visibility: Track execution times, success rates, and error details

Quick Start

1. Install the SDK

npm install @useschedo/node-sdk

2. Create an API Route

Create a webhook endpoint in your Next.js application:

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { SignatureVerifier } from "@useschedo/node-sdk";

const verifier = new SignatureVerifier(
  process.env.SCHEDO_SIGNATURE_KEY!
);

export async function POST(req: NextRequest) {
  try {
    const body = await req.json();

    // Verify webhook signature
    const signature = req.headers.get("x-schedo-signature");
    if (!signature) {
      return NextResponse.json(
        { error: "Missing signature" },
        { status: 401 }
      );
    }

    const isValid = verifier.verifyBytesSignature(
      Buffer.from(JSON.stringify(body)),
      signature
    );

    if (!isValid) {
      return NextResponse.json(
        { error: "Invalid signature" },
        { status: 401 }
      );
    }

    // Your scheduled task logic here
    await processScheduledTask(body);

    return NextResponse.json({
      success: true,
      timestamp: new Date().toISOString()
    });
  } catch (error) {
    console.error("Cron job error:", error);
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 }
    );
  }
}

async function processScheduledTask(payload: any) {
  // Example: Send daily reports
  console.log("Processing scheduled task:", payload);

  // Your business logic here
  // - Send emails
  // - Process data
  // - Update database
  // - Call external APIs
}

3. Set Environment Variables

Add your Schedo signature key to your environment variables:

.env.local
SCHEDO_SIGNATURE_KEY=your_signature_key_here

Get your signature key from the Schedo Dashboard under Settings > API Keys.

4. Deploy Your Application

Deploy your Next.js application to your preferred platform:

vercel --prod

5. Create a Webhook Job

In the Schedo dashboard:

  1. Click “Create Webhook Job”
  2. Enter your endpoint URL: https://your-app.vercel.app/api/cron
  3. Set your schedule (e.g., 0 9 * * * for daily at 9 AM)
  4. Click “Create”

Advanced Configuration

Custom Headers

Add custom headers to your webhook requests:

// In Schedo dashboard, add custom headers
{
  "Authorization": "Bearer your-token",
  "Content-Type": "application/json"
}

Request Payload

Customize the payload sent to your endpoint:

{
  "jobId": "job_123",
  "scheduledAt": "2024-01-15T09:00:00Z",
  "customData": {
    "reportType": "daily",
    "recipients": ["admin@company.com"]
  }
}

Error Handling

Implement robust error handling in your API route:

export async function POST(req: NextRequest) {
  try {
    // ... verification code ...

    const result = await processScheduledTask(body);

    return NextResponse.json({
      success: true,
      result: result,
      processedAt: new Date().toISOString()
    });
  } catch (error) {
    // Log detailed error information
    console.error("Scheduled task failed:", {
      error: error.message,
      stack: error.stack,
      payload: body,
      timestamp: new Date().toISOString()
    });

    // Return error response (Schedo will retry based on your settings)
    return NextResponse.json(
      {
        error: "Task execution failed",
        details: error.message
      },
      { status: 500 }
    );
  }
}

Use Cases

Daily Reports

async function sendDailyReport(payload: any) {
  const yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);

  // Fetch data
  const analytics = await getAnalytics(yesterday);

  // Generate report
  const report = await generateReport(analytics);

  // Send via email
  await sendEmail({
    to: payload.customData.recipients,
    subject: `Daily Report - ${yesterday.toDateString()}`,
    html: report
  });
}

Database Cleanup

async function cleanupOldData(payload: any) {
  const cutoffDate = new Date();
  cutoffDate.setDays(cutoffDate.getDate() - 30);

  // Clean up old logs
  await db.logs.deleteMany({
    createdAt: { lt: cutoffDate }
  });

  // Clean up temporary files
  await cleanupTempFiles(cutoffDate);

  console.log(`Cleanup completed: removed data older than ${cutoffDate}`);
}

API Synchronization

async function syncExternalData(payload: any) {
  try {
    // Fetch from external API
    const externalData = await fetch('https://api.external.com/data', {
      headers: { 'Authorization': `Bearer ${process.env.EXTERNAL_API_KEY}` }
    }).then(res => res.json());

    // Update local database
    await db.externalData.upsert({
      where: { id: externalData.id },
      update: externalData,
      create: externalData
    });

    console.log(`Synced ${externalData.length} records`);
  } catch (error) {
    console.error('Sync failed:', error);
    throw error; // Will trigger retry in Schedo
  }
}

Deployment Platforms

Vercel

Schedo works perfectly with Vercel’s serverless functions:

vercel.json
{
  "functions": {
    "pages/api/cron.js": {
      "maxDuration": 30
    }
  }
}

Netlify

For Netlify Functions:

netlify.toml
[functions]
  node_bundler = "esbuild"

[[functions]]
  path = "pages/api/cron.js"
  timeout = 30

Railway

Railway supports Next.js out of the box. Just connect your repository and deploy.

Security Best Practices

Environment Variables

Always store sensitive data in environment variables:

# Production environment
SCHEDO_SIGNATURE_KEY=your_production_key
DATABASE_URL=your_database_url
EMAIL_API_KEY=your_email_key

Signature Verification

Always verify webhook signatures:

// ❌ Don't skip signature verification
export async function POST(req: NextRequest) {
  // Process without verification - DANGEROUS!
  await processScheduledTask(await req.json());
}

// ✅ Always verify signatures
export async function POST(req: NextRequest) {
  const isValid = verifier.verifyBytesSignature(/* ... */);
  if (!isValid) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  // Safe to process
}

Rate Limiting

Implement rate limiting for additional security:

import { Ratelimit } from "@upstash/ratelimit";
import { kv } from "@vercel/kv";

const ratelimit = new Ratelimit({
  redis: kv,
  limiter: Ratelimit.slidingWindow(10, "1 m"),
});

export async function POST(req: NextRequest) {
  const ip = req.ip ?? "127.0.0.1";
  const { success } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json({ error: "Rate limited" }, { status: 429 });
  }

  // Continue processing...
}

IP Allowlisting

For enhanced security, you can restrict your cron endpoints to only accept requests from Schedo.dev servers. This prevents unauthorized access to your webhook endpoints.

IP allowlisting is available as an enterprise feature. Contact our support team at support@schedo.dev to enable IP allowlisting for your account and receive the current list of Schedo.dev IP addresses.

Once enabled, you can implement IP filtering in your Next.js application:

const ALLOWED_IPS = [
  // IPs will be provided by Schedo.dev support team
  '10.0.0.1',
  '10.0.0.2',
  // Add more IPs as provided
];

export async function POST(req: NextRequest) {
  const clientIP = req.ip ?? req.headers.get('x-forwarded-for') ?? '127.0.0.1';

  if (!ALLOWED_IPS.includes(clientIP)) {
    console.warn(`Blocked request from unauthorized IP: ${clientIP}`);
    return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
  }

  // Continue with signature verification and processing...
}

Benefits of IP allowlisting:

  • Additional Security Layer: Prevents direct access to your webhook endpoints
  • Compliance: Meets security requirements for sensitive applications
  • Audit Trail: Clear logging of blocked unauthorized requests
  • Peace of Mind: Ensures only Schedo.dev can trigger your scheduled tasks

Monitoring and Debugging

Schedo.dev Dashboard Monitoring

Schedo.dev provides comprehensive monitoring and visibility for your Next.js cron jobs directly in the dashboard:

  • Execution History: View all webhook calls with timestamps and response codes
  • Success/Failure Rates: Track job reliability over time
  • Response Times: Monitor your endpoint performance
  • Error Details: See detailed error messages and stack traces
  • Retry Attempts: Track failed requests and retry patterns
  • Payload Inspection: View request and response data for debugging

The dashboard automatically captures and displays:

  • HTTP status codes from your Next.js endpoint
  • Response times and payload sizes
  • Error messages and failure reasons
  • Execution trends and patterns

Application-Level Logging

Implement comprehensive logging in your Next.js application:

export async function POST(req: NextRequest) {
  const startTime = Date.now();
  const requestId = crypto.randomUUID();

  console.log(`[${requestId}] Webhook received`, {
    headers: Object.fromEntries(req.headers.entries()),
    timestamp: new Date().toISOString()
  });

  try {
    const result = await processScheduledTask(body);

    console.log(`[${requestId}] Task completed successfully`, {
      duration: Date.now() - startTime,
      result: result
    });

    return NextResponse.json({ success: true, requestId });
  } catch (error) {
    console.error(`[${requestId}] Task failed`, {
      error: error.message,
      duration: Date.now() - startTime
    });

    throw error;
  }
}

Health Check Endpoint

Create a health check endpoint:

app/api/health/route.ts
import { NextResponse } from "next/server";

export async function GET() {
  return NextResponse.json({
    status: "healthy",
    timestamp: new Date().toISOString(),
    version: process.env.npm_package_version
  });
}

Schedo.dev Monitoring & Visibility

Schedo.dev provides comprehensive monitoring and visibility for your Next.js cron jobs directly in the dashboard. This gives you complete insight into your webhook executions without needing to implement custom monitoring solutions.

Dashboard Features

Real-time Monitoring

  • Live webhook execution status
  • Success/failure rates and trends
  • Average response times and performance metrics
  • Active job status and next scheduled execution times

Detailed Execution History

  • Complete log of all webhook calls with timestamps
  • HTTP status codes and response details
  • Request and response payload inspection
  • Error messages and stack traces for failed executions

Performance Analytics

  • Response time trends over different time periods
  • Success rate percentages and reliability metrics
  • Retry attempt tracking and patterns
  • Webhook delivery success rates

Error Tracking & Debugging

  • Detailed error messages from your Next.js endpoints
  • Failed request retry attempts and outcomes
  • Webhook timeout and connection error tracking
  • Custom error categorization and filtering

Webhook Execution Insights

The Schedo.dev dashboard automatically captures and displays:

// Your endpoint responses are automatically tracked
export async function POST(req: NextRequest) {
  try {
    await processScheduledTask(body);

    // ✅ Success logged automatically in Schedo.dev dashboard
    return NextResponse.json({
      success: true,
      processedItems: 150,
      duration: "2.3s"
    });
  } catch (error) {
    // ❌ Error details automatically captured in dashboard
    return NextResponse.json(
      { error: error.message },
      { status: 500 }
    );
  }
}

What gets tracked:

  • HTTP status codes (200, 500, 404, etc.)
  • Response times and payload sizes
  • Custom response data for business metrics
  • Error messages and failure categorization
  • Execution frequency and scheduling accuracy

Troubleshooting

Common Issues

Signature Verification Fails

// Make sure to stringify the body exactly as received
const bodyString = JSON.stringify(body);
const isValid = verifier.verifyBytesSignature(
  Buffer.from(bodyString),
  signature
);

Function Timeout

// For long-running tasks, break them into smaller chunks
async function processLargeDataset(data: any[]) {
  const batchSize = 100;

  for (let i = 0; i < data.length; i += batchSize) {
    const batch = data.slice(i, i + batchSize);
    await processBatch(batch);

    // Prevent timeout
    if (i % 500 === 0) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }
  }
}

Memory Limits

// Stream large data instead of loading everything into memory
import { Readable } from 'stream';

async function processLargeFile() {
  const stream = createReadStream('large-file.json');

  for await (const chunk of stream) {
    await processChunk(chunk);
  }
}

Debug Mode

Enable debug logging in development:

const DEBUG = process.env.NODE_ENV === 'development';

if (DEBUG) {
  console.log('Debug: Request headers', req.headers);
  console.log('Debug: Request body', body);
}

Migration Guide

From Vercel Cron

If you’re migrating from Vercel Cron:

// Before (Vercel Cron)
export async function GET() {
  await runScheduledTask();
  return Response.json({ success: true });
}

// After (Schedo webhook)
export async function POST(req: NextRequest) {
  // Add signature verification
  const isValid = verifier.verifyBytesSignature(/* ... */);
  if (!isValid) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  await runScheduledTask();
  return NextResponse.json({ success: true });
}

From GitHub Actions

Replace GitHub Actions cron with Schedo webhooks:

# Remove this from .github/workflows/cron.yml
name: Scheduled Task
on:
  schedule:
    - cron: '0 9 * * *'
jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger endpoint
        run: curl -X POST ${{ secrets.WEBHOOK_URL }}
// Add this to your Next.js app instead
export async function POST(req: NextRequest) {
  // Your scheduled task logic here
  await runScheduledTask();
  return NextResponse.json({ success: true });
}

FAQ

Next Steps