Heximon Logo
Deploy

Netlify

NetlifyFunctionsStrategy, NetlifyEdgeStrategy, .netlify/ deploy tree, scheduled functions, Netlify Async Workloads queue, mixed-runtime split.

Netlify's scheduled functions are native — each cron handler becomes its own function with a static config.schedule export, registered the moment you deploy. No drain cron, no polling: Heximon's Netlify support is strictly better here than the generic Nitro preset, which emits no scheduled functions at all.

Configure the strategy

heximon.config.ts
import { defineHeximonConfig } from "@heximon/build";
import { NetlifyFunctionsStrategy } from "@heximon/netlify";
import { HttpPlugin } from "@heximon/http/compiler";

export default defineHeximonConfig({
  platform: new NetlifyFunctionsStrategy(),
  plugins: [new HttpPlugin()],
});

NetlifyFunctionsStrategy is a node-class strategy, platform name "netlify" — Netlify Functions 2.0 run an AWS-Lambda-backed Node.js runtime. NetlifyEdgeStrategy is the opt-in edge-class alternative, running on Deno.

Response streaming and the Functions 2.0 handler shape need compatibilityDate >= 2024-05-07 in your netlify.toml — without it Netlify falls back to the legacy v1 function format.

What vp build emits

vp build writes a Netlify deploy tree at the project root .netlify/, so netlify deploy runs the compiled app with no Nitro:

.netlify/functions-internal/
  server/
    index.mjs            # the compiled bundle
    server.mjs            # export { default } from "./index.mjs" + the static `config` const
  scheduled-0/
    scheduled-0.mjs       # one scheduled function per cron

Netlify statically analyzes a function file for its top-level config const, so the catch-all routing config ({ path: "/*", preferStatic: true, excludedPath: ["/.netlify/*"] }) lives in the explicit server.mjs wrapper rather than being re-exported through the compiled bundle.

Netlify Functions deploy externalized — the platform bundles node_modules itself — so the strategy keeps the standard Node SSR externalization; only node:* builtins are forced external.

The Edge tier (NetlifyEdgeStrategy) writes .netlify/edge-functions/ instead — a self-contained bundle (Deno doesn't load node_modules) routed by a manifest.json catch-all, with no config wrapper and no scheduled functions.

Scheduled work (cron)

List SchedulePlugin and the plugin selects NetlifyScheduleDriver — a clockless driver the platform triggers by invoking a scheduled function directly. Each discovered cron gets its own function with a static config.schedule export as its registration:

.netlify/functions-internal/scheduled-0/scheduled-0.mjs (generated)
import { scheduled as heximonScheduled } from "../server/index.mjs";

export const config = { schedule: "0 3 * * *" };

export default async () => {
  await heximonScheduled({ cron: "0 3 * * *", scheduledTime: Date.now() });
  return new Response(null, { status: 202 });
};
import type { ScheduledHandler } from "@heximon/schedule";

export class NightlyCleanup implements ScheduledHandler<"0 3 * * *", { name: "Nightly cleanup" }> {
  public handle(scheduledAt: Date): void {
    // …
  }
}

Cron works only on the Functions target — Netlify Edge has no scheduled-function support at all, and a scheduled handler compiled against NetlifyEdgeStrategy is rejected at build time with a clear diagnostic.

Queue transport (Netlify Async Workloads)

List QueuePlugin and bind NetlifyAsyncWorkloadsQueue (from @heximon/queue/netlify) as your Queue provider. The strategy binds NetlifyAsyncWorkloadsQueueConsumer as the push consumer, and vp build emits a consumer function:

.netlify/functions-internal/queue-consumer/queue-consumer.mjs (generated)
import { asyncWorkloadFn } from "@netlify/async-workloads";
import { queueConsumer } from "../server/index.mjs";

export const asyncWorkloadConfig = { events: ["orders-placed"] };

export default asyncWorkloadFn(async (event) => {
  await queueConsumer(event);
});
import { NetlifyAsyncWorkloadsQueue } from "@heximon/queue/netlify";
import type { QueueEventTypes } from "@heximon/queue";

export interface OrderEventMap extends QueueEventTypes {
  "orders-placed": { orderId: string; total: number };
}

export class OrderQueue extends NetlifyAsyncWorkloadsQueue<OrderEventMap> {}
import { Module } from "@heximon/runtime";
import { OrderQueue } from "./orders/order-queue.js";
import { OrderPlacedHandler } from "./orders/order-placed.handler.js";
import { Queue } from "@heximon/queue";

export class AppModule extends Module({
  providers: [{ provide: Queue, useClass: OrderQueue }],
  queue: { handlers: [OrderPlacedHandler] },
}) {}

@netlify/async-workloads is not a declared peer dependency of @heximon/queue — its pre-release dependency tree is too heavy to force into the toolchain — so a real Netlify-queue deploy adds it to the app's owndependencies. Netlify Async Workloads has no built-in delay feature, so there's no publishWithDelay: reach for a durable timer or a polling queue transport for delayed delivery instead.

Mixed-runtime split

NetlifyFunctionsStrategy owns both a node tier (Functions) and an edge tier (Edge Functions), so it declares supportsRuntimeSplit = true.

Mark a controller runtime: "edge" and vp build partitions the DI graph, bundling the edge partition into a self-contained dist/edge.js and emitting both a .netlify/functions-internal/server/ node catch-all and a prefix-routed Netlify Edge Function in .netlify/edge-functions/. Netlify Edge runs before Functions, so the edge function intercepts its own prefixes and the node function serves everything else.

Because Netlify Edge runs on Deno (broad native node: compatibility), the edge tier here needs no unenv polyfill layer — that complexity is specific to Vercel's V8-isolate Edge tier.

Dev

Netlify has no local micro-VM emulator either. The strategy's dev host returns the browser channel unchanged — development runs as a standard Node HTTP server on both the Functions and Edge targets, with the full build experience preserved.

Ship it

netlify deploy          # add --prod for production

A manual prebuilt deploy needs a committed netlify.toml pinning [functions] directory = ".netlify/functions-internal" and a no-op [build] command.

Both host strategies implement the @heximon/deploy capability, so heximon deploy runs the same command for you — plus a netlify api getCurrentUser preflight and a first-deploy site-linkage gate (halting with the one-time netlify sites:create / netlify link command on a fresh site).

Netlify's cron is a native scheduled function, not a public route, so there's no CRON_SECRET gate the way there is on Vercel.

See also

  • Queue — the Queue port and channel handler discovery.
  • ScheduleScheduledHandler and the cron discovery mechanism.
  • Mixed-runtime split — the shared runtime: "edge" controller partitioning.
  • heximon deploy — the CLI's Netlify-specific preflight, --create, and --provision flags.
Copyright © 2026