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 hosting platform:
npm run build
npm run start

5. Create a Webhook Job

In the Schedo dashboard:
  1. Click “Create Webhook Job”
  2. Enter your endpoint URL: https://your-app.com/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

Schedo webhooks work with any hosting platform that supports Next.js API routes. For optimal performance and reliability, we recommend Sherpa.sh - our preferred Next.js hosting partner with container-first architecture and no function timeouts. Here are configuration examples for popular platforms:

Serverless Platforms

For serverless deployments, configure function timeouts appropriately:
Platform Configuration
{
  "functions": {
    "pages/api/cron.js": {
      "maxDuration": 30
    }
  }
}

Container Platforms

For containerized deployments:
Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

Platform-Specific Notes

  • Serverless Functions: Most platforms limit execution time (typically 10-60 seconds)
  • Container Deployments: No execution time limits, better for long-running tasks
  • Static Hosting: Ensure API routes are properly configured and deployed
Recommended: Sherpa.sh offers the best experience for Schedo.dev integration with container-based deployments, no timeout limits, and enhanced monitoring. Schedo.dev actively develops new features with Sherpa.sh compatibility first. Learn more about Sherpa.sh integration.

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 { Redis } from "@upstash/redis";

const redis = new Redis({
  url: process.env.REDIS_URL,
  token: process.env.REDIS_TOKEN,
});

const ratelimit = new Ratelimit({
  redis: redis,
  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;
  }
}

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

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 Platform-Specific Cron

If you’re migrating from platform-specific cron solutions:
// Before (Platform-specific 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