Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A Next.js 15 Route Handler is the job endpoint, not the clock. Your hosting platform or an external scheduler must make an HTTP request to that endpoint on a cadence. On Vercel, you connect the two with a crons entry in vercel.json; on a self-hosted deployment, the host-level scheduler or job runner performs the request.

This boundary determines your design. Secure the endpoint as an internet-facing API, use UTC cron syntax, make side effects idempotent because one scheduled time can execute twice, and choose a plan or scheduler whose cadence and timing tolerance match the work.

The architecture: endpoint first, scheduler second

In the App Router, a file such as app/api/daily-report/route.ts defines an HTTP endpoint. Its GET function performs the work when a request arrives. Nothing in that file causes Next.js to wake up at 05:00, run every hour, or remember a schedule. A separate system must invoke the URL.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

That separation gives you two independently testable parts:

  • Route Handler: validates the request, executes or queues the task, records its result, and returns an HTTP response.
  • Scheduler: decides when to send an HTTP request, which deployment to call, and how its own invocation behavior is handled.

Vercel’s cron integration supplies the scheduler for a production deployment. A self-hosted Next.js application needs a scheduler supplied by its operating environment. If you call the endpoint manually with curl, you are exercising the same boundary the scheduler uses.

Build the Route Handler in Next.js 15

Choose a stable URL

Put the handler under the app directory and use a path that describes one bounded operation. For example:

app/
└── api/
    └── daily-report/
        └── route.ts

The resulting URL is /api/daily-report. Keep scheduled operations separate when they have different permissions, frequencies, or failure handling. One giant endpoint makes it harder to retry one task without repeating unrelated work.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Expose the method the scheduler calls

Vercel cron invokes the configured path with an HTTP GET request. Implement that method explicitly and return a useful status code. This example checks a bearer secret before starting work:

import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const authorization = request.headers.get('authorization');
  const expected = process.env.CRON_SECRET;

  if (!expected || authorization !== `Bearer ${expected}`) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  try {
    // Perform a bounded unit of work here, or enqueue it for a worker.
    const result = await runDailyReport();

    return NextResponse.json({
      ok: true,
      processed: result.processed,
    });
  } catch (error) {
    console.error('daily-report failed', error);
    return NextResponse.json({ error: 'Task failed' }, { status: 500 });
  }
}

async function runDailyReport() {
  // Replace this with your application logic.
  return { processed: 0 };
}

Use the exact authentication contract you configure. A random CRON_SECRET of at least 16 characters is the documented Vercel recommendation. Store it as an environment variable rather than committing it to the repository. Reject missing, malformed, or incorrect credentials before touching the database or an external API.

Because the endpoint is reachable over HTTP, treat it like any other privileged API: use HTTPS, limit what the handler can do, avoid returning sensitive data, and log enough context to investigate failures without logging the secret.

Decide whether the request does the work or starts it

Short, bounded work can run inside the request. For longer work, have the handler create a durable job record or enqueue work for a worker that your hosting setup actually runs. A Route Handler does not create a background worker by itself. Ensure the handler’s response reflects what really happened: returning success before durable handoff can hide a lost task, while holding a request open for an unbounded process can hit platform limits.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Be deliberate about GET behavior

Next.js documents route configuration options that affect GET caching. A scheduled endpoint normally represents a fresh operation, so review the route’s caching and dynamic behavior rather than assuming every invocation will execute exactly as written. Keep any cache-related setting in the route configuration appropriate for your Next.js 15 deployment and verify the resulting behavior in production.

Connect the handler to Vercel Cron Jobs

Add the schedule to vercel.json

Vercel’s quickstart uses an App Router route and a crons array with a path and a five-field schedule:

{
  "crons": [
    {
      "path": "/api/daily-report",
      "schedule": "0 5 * * *"
    }
  ]
}

0 5 * * * means once a day at 05:00 UTC. The build creates the cron job, and the job invokes the production deployment URL with an HTTP GET request. Preview deployments are not the target for these cron jobs.

After changing the path or expression, redeploy. Schedule changes take effect through redeployment; editing the file in a branch does not alter the already deployed schedule.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set the production secret

  1. Generate a random value with at least 16 characters.
  2. Add it as CRON_SECRET in the production environment settings.
  3. Deploy the same application version that reads the variable.
  4. Ensure the authorization check in the handler matches the header format used by your scheduler configuration.

Do not assume a route is private because its path is obscure. Anyone who discovers the URL can send requests; the secret is the control that prevents unauthorized execution.

Understand Vercel’s cron expression rules

Vercel uses five cron fields in this order:

Field Meaning
1 Minute
2 Hour
3 Day of month
4 Month
5 Day of week

The timezone is UTC. Named alternatives such as MON, SUN, JAN, and DEC are unsupported. Vercel also documents that you cannot constrain both day of month and day of week in the same expression. Convert a local business time to UTC yourself and account for daylight-saving changes if the task must follow a civil-time zone.

Examples

Expression Meaning in UTC
0 5 * * * Every day at 05:00
30 2 * * * Every day at 02:30
0 0 1 * * The first day of each month at 00:00
15 * * * * Every hour, at minute 15

The examples show syntax, not a guarantee that the request arrives at that exact second. Plan limits and platform timing determine how close execution is to the scheduled instant.

Choose a Vercel plan with realistic timing expectations

Vercel’s usage and pricing page, updated January 28, 2026, lists these cron limits:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Plan Cron jobs per project Minimum cadence Timing precision
Hobby 100 At most once per day Hourly precision, approximately ±59 minutes
Pro 100 Once per minute Per-minute precision
Enterprise 100 Once per minute Per-minute precision

These are vendor-published limits, not independent performance measurements. Vercel explicitly says invocation timing cannot be assured. A task that only needs to run sometime each day can tolerate Hobby’s window; a deadline-sensitive process should not treat a cron expression as an exact-time guarantee. Recheck the current plan page before committing to an operational schedule because limits can change.

Vercel documentation states that cron jobs are available on all plans. Availability does not erase the different cadence and precision limits shown above.

Make every scheduled operation safe to run twice

Vercel’s management guidance says an execution can happen twice for one scheduled time. Design for at-least-once invocation rather than assuming exactly-once execution. The right protection depends on the operation:

Use a durable idempotency key

Derive a key from the logical period being processed, such as daily-report:2026-10-18. Store it with a unique database constraint. The first request inserts the key and performs the work; a later request sees the existing key and exits without duplicating side effects. Create the key from the intended business period, not from the current millisecond, so duplicates map to the same operation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Record each item before applying an irreversible effect

For email, billing, inventory, or notifications, keep a per-item delivery or processing record. Check that record inside a transaction or with an atomic claim before sending or updating an external system. If the external API supports its own idempotency key, pass the same stable key on every attempt.

Use a lease for overlapping runs

If one run may still be active when another begins, store a short-lived lease with an owner and expiry. Acquire it atomically; release it when finished. An expired lease must be recoverable so a crashed invocation does not block the task forever. A lease reduces overlap but does not replace per-operation idempotency.

Separate discovery from processing

Have the scheduled request select a bounded page of eligible records and mark them claimed. A worker can process those claims and retry failures independently. This keeps the HTTP invocation predictable and gives you a place to inspect what was completed.

Test the endpoint before enabling the schedule

  1. Run the Next.js application locally and call the route without credentials. Confirm it returns 401 and performs no work.
  2. Call it with the development secret in an authorization header. Confirm the expected task result and a useful success status.
  3. Send the same authorized request twice. Verify that the second call does not duplicate the business effect.
  4. Force a task failure and confirm the handler returns 500, records an actionable log, and leaves work in a retryable state.
  5. Deploy to production, set the production CRON_SECRET, and invoke the production URL manually with the same header before waiting for the first scheduled run.
  6. After deployment, inspect the platform’s cron view and application logs to confirm the configured path and schedule belong to the production deployment.

A representative manual request looks like this, with your real production host and secret supplied outside source control:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -i 
  -H "Authorization: Bearer $CRON_SECRET" 
  https://your-production-host.example/api/daily-report

Do not place a live secret directly in shell history or a pasted support ticket. Use an environment variable or another secure secret mechanism.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Self-hosted Next.js 15: supply the missing scheduler

When you self-host, Next.js serves the Route Handler but does not provide a cron daemon. Your operating system, container platform, cloud scheduler, or separate job runner must make the HTTP request. The exact recipe depends on where the application runs, so select a scheduler that can reach the production URL, store credentials securely, report failures, and express the required cadence.

The implementation remains the same:

  1. Deploy the Next.js application and expose the Route Handler over HTTPS.
  2. Configure the host-level scheduler or job runner to issue an HTTP GET to the route.
  3. Send the authorization secret through the scheduler’s secret store.
  4. Define timeout, logging, and alerting behavior in that scheduler.
  5. Keep the task idempotent in case the scheduler or network causes a duplicate request.

Do not confuse a process manager that keeps your web server alive with a scheduler that initiates periodic work. A web process can be healthy while no scheduled request is ever sent.

Compare deployment choices before you commit

Decision point Vercel Cron Self-hosted or external scheduler
Who invokes the endpoint? Vercel sends an HTTP GET to the production deployment. Your host-level scheduler, platform scheduler, or job runner sends the request.
Where is the schedule declared? vercel.json, using Vercel’s five-field UTC syntax. In the scheduler’s own configuration.
Preview deployments? Cron jobs invoke production deployments, not previews. Depends on which URL and environment your scheduler targets.
Cadence and precision Hobby is once daily with approximately ±59-minute precision; Pro and Enterprise support once-per-minute minimum intervals with per-minute precision. Depends on the scheduler you select and its service limits.
Authentication Protect the route with a secret such as CRON_SECRET. Protect the route with a secret and configure the scheduler’s secure secret storage.
Duplicate tolerance Required because one scheduled time can execute twice. Still required; network retries and scheduler behavior can duplicate requests.
Background workers The cron request reaches your HTTP function; it does not automatically create a worker. You control whether a separate worker or queue runs alongside the web process.

Choose the simplest option that meets the work’s timing and reliability needs. If daily, approximate timing is acceptable, a Vercel configuration may be sufficient. If you need a strict local-time calendar, high-frequency coordination, long-running workers, or scheduler behavior you control directly, evaluate a separate job runner and keep the Route Handler as the authenticated entry point.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common failures and fixes

The route works manually but never runs automatically

  • Confirm the route path in vercel.json exactly matches the deployed URL, including the /api prefix.
  • Confirm the schedule is in the production deployment and redeploy after changes.
  • Check that you are not waiting for a preview deployment; Vercel cron targets production.
  • Check the plan’s cadence and timing window before treating a delayed invocation as a failure.

Every invocation returns 401

  • Verify CRON_SECRET exists in the production environment, not only in local development.
  • Verify the scheduler sends the same authorization format the handler expects.
  • Redeploy after changing environment configuration when your deployment process requires it.

The task runs twice and creates duplicate records

  • Add a unique idempotency key for the logical run.
  • Use atomic claims or transactions around side effects.
  • Inspect logs to distinguish a duplicate invocation from two different scheduled periods.

The schedule appears one or more hours off

  • Convert the intended local time to UTC; Vercel cron uses UTC.
  • Check whether daylight-saving changes alter the local offset.
  • Remember that Hobby timing is approximate and can vary by about 59 minutes.

The request times out

  • Reduce the amount of work per invocation and process a bounded batch.
  • Persist progress so a later invocation can continue safely.
  • Move long-running processing to a worker or queue that your hosting setup actually operates.

Production checklist

  • The Route Handler is in an app/**/route.ts file and exports the HTTP method the scheduler uses.
  • The handler authenticates before performing side effects.
  • CRON_SECRET is random, at least 16 characters, and stored only in a secure environment variable.
  • The route’s caching and dynamic configuration match the need for fresh execution.
  • The cron path and expression are present in vercel.json when using Vercel.
  • The expression uses UTC and obeys the five-field limitations.
  • The schedule was redeployed to production after every change.
  • The task has an idempotency key, unique constraint, lease, or equivalent duplicate protection.
  • Work is bounded or handed to a durable worker instead of relying on an unbounded HTTP request.
  • Logs identify the task, logical period, outcome, and duration without exposing secrets.
  • Failure alerts and a replay procedure exist before the first real run.
  • The selected plan or scheduler’s timing tolerance is acceptable for the business deadline.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.