How to Create API Routes That Use Server Actions on Edge Runtime in Next.js

You can expose Server Actions through HTTP endpoints by creating a Next.js App Router API route that exports runtime = 'edge' and imports your "use server" functions, allowing them to execute on Cloudflare Workers with access to Edge-only bindings like R2, KV, and AI.

The ifindev/fullstack-next-cloudflare repository demonstrates a production-ready pattern for combining Next.js Server Actions with Cloudflare's Edge runtime. By creating API routes that import and invoke Server Actions while running on the edge, you enable low-latency serverless functions that retain the ergonomic benefits of Next.js actions, including revalidatePath and redirect.

Understanding the Edge Runtime Architecture

In this architecture, Server Actions contain your server-only business logic in files marked with "use server", while API routes provide HTTP endpoints that expose this logic to external clients or non-Next.js frontends. When you export runtime = 'edge' from a route file in src/app/api/…/route.ts, Next.js compiles the handler as a Cloudflare Worker rather than a Node.js server.

Because the API route and the imported Server Action share the same edge execution environment, the action can safely use Cloudflare-specific globals like env.AI, R2 buckets, and KV storage. This pattern is implemented in the ifindev/fullstack-next-cloudflare codebase to leverage Cloudflare's distributed edge network while maintaining Next.js developer ergonomics.

Creating an Edge API Route

To create API routes that use Server Actions on the edge runtime, you configure the runtime export and import your server functions into the route handler.

Configure the Runtime

Add the runtime export at the top of your route file to enable edge execution:

// src/app/api/todos/route.ts
export const runtime = 'edge';

This declaration tells Next.js to compile this endpoint as an edge function, making it compatible with Cloudflare Workers and enabling access to edge bindings.

Implement the Route Handler

Import your Server Action and invoke it within the HTTP handler. The following example from the repository pattern forwards a multipart/form-data request to the createTodoAction Server Action:

// src/app/api/todos/route.ts
export const runtime = 'edge';

import { createTodoAction } from '@/modules/todos/actions/create-todo.action';

export async function POST(request: Request) {
  // Parse a multipart/form-data request (e.g., from a Next.js Form)
  const formData = await request.formData();

  // The Server Action does all validation, DB writes, R2 uploads, etc.
  // It returns a Next.js Response (or throws, which Next will handle)
  return createTodoAction(formData);
}

The createTodoAction function, defined in src/modules/todos/actions/create-todo.action.ts, contains the "use server" directive and handles database inserts, R2 file uploads, and cache revalidation. Because both files run on the edge, the action executes in the same Worker context as the route.

Accessing Cloudflare Edge Bindings

When running on the edge runtime, you can access Cloudflare-specific bindings like AI, R2, and KV directly within your Server Actions or route handlers. The repository uses getCloudflareContext from @opennextjs/cloudflare to access these bindings.

The following example from src/app/api/summarize/route.ts demonstrates accessing the AI binding within an edge API route:

// src/app/api/summarize/route.ts
export const runtime = 'edge';

import { getCloudflareContext } from '@opennextjs/cloudflare';
import { SummarizerService, summarizeRequestSchema } from '@/services/summarizer.service';
import handleApiError from '@/lib/api-error';

export async function POST(request: Request) {
  try {
    const { env } = await getCloudflareContext();          // Edge-only Cloudflare context
    const body = await request.json();
    const { text, config } = summarizeRequestSchema.parse(body);

    // AI is only available on the Edge
    const summarizer = new SummarizerService(env.AI);
    const result = await summarizer.summarize(text, config);

    return new Response(JSON.stringify({ success: true, data: result }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
    });
  } catch (e) {
    return handleApiError(e);
  }
}

This pattern works with any Server Action that requires edge-only bindings—simply import the action into a route configured with runtime = 'edge'.

Full-Stack Implementation Flow

The complete implementation spans three layers: the Server Action containing business logic, the Edge API route exposing it via HTTP, and the client component consuming the endpoint.

The Server Action

The Server Action in src/modules/todos/actions/create-todo.action.ts handles validation, database operations, and cache management. It uses "use server" and can call revalidatePath and redirect because it runs within the Next.js server context, even when invoked from an edge API route.

The Edge API Route

As shown previously, the route in src/app/api/todos/route.ts exports runtime = 'edge' and forwards requests to the Server Action, allowing the action to execute within the Cloudflare Worker environment.

The Client Component

The client submits data directly to the edge endpoint. The Server Action handles the redirect and revalidation, so the client requires minimal logic:

// src/app/dashboard/todos/new-todo.page.tsx
'use client';

export default function NewTodo() {
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    // Submit the form directly to the Edge API route
    const formData = new FormData(e.currentTarget);
    const res = await fetch('/api/todos', { method: 'POST', body: formData });

    if (res.ok) {
      // Success: redirect handled by the Server Action (revalidatePath + redirect)
      // No extra client-side navigation needed
    } else {
      // handle error...
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* …form fields… */}
      <button type="submit">Create Todo</button>
    </form>
  );
}

When the form posts to /api/todos, the edge route forwards to createTodoAction, which performs validation, persists data, optionally uploads images to R2, revalidates the list page via revalidatePath, and issues a redirect—all executing at the edge with minimal latency.

Required Configuration

Two configuration files enable this architecture: the Next.js configuration for local development and the Wrangler configuration for deployment.

Next.js Configuration

The src/next.config.ts file loads the OpenNext Cloudflare integration, which is required for edge runtime support during local development:

// src/next.config.ts
// Loads @opennextjs/cloudflare for local dev; required for Edge runtime support

This configuration enables the edge runtime throughout the application, allowing runtime = 'edge' exports to function correctly in development.

Wrangler Configuration

The wrangler.jsonc file defines the Cloudflare Worker bindings (R2, AI, KV) that your Server Actions consume when running on the edge:

// wrangler.jsonc
// Cloudflare Workers configuration – defines bindings (R2, AI, KV) 
// that Server Actions can consume when running on the edge

Without these bindings declared in wrangler.jsonc, the env object accessed via getCloudflareContext would not contain the necessary resources.

Summary

  • Export runtime = 'edge' from API routes in src/app/api/…/route.ts to compile handlers as Cloudflare Workers
  • Import Server Actions directly into edge routes—shared runtime allows actions to use Edge-only APIs like R2, KV, and AI
  • Use getCloudflareContext from @opennextjs/cloudflare to access bindings within Server Actions or route handlers
  • Configure src/next.config.ts with OpenNext Cloudflare integration for local edge runtime support
  • Define bindings in wrangler.jsonc to make Cloudflare resources available to your edge functions

Frequently Asked Questions

Can I use Server Actions outside of Next.js forms by exposing them as API routes?

Yes. While Server Actions are typically invoked directly from Next.js components, creating an API route with runtime = 'edge' that imports and calls the action allows external clients, mobile apps, or third-party services to execute the same logic over standard HTTP.

Why must I export runtime = 'edge' instead of using the Node.js runtime?

The runtime = 'edge' export compiles your handler as a Cloudflare Worker, enabling access to Cloudflare-specific bindings like env.AI, R2 storage, and KV. Node.js runtime routes cannot access these edge-only resources, and they execute in a different environment than Cloudflare Workers.

How does revalidatePath work when a Server Action is called from an API route?

The revalidatePath function works identically whether the Server Action is invoked directly from a component or through an edge API route. Because the action runs within the Next.js edge runtime context, it retains full access to Next.js caching APIs and will revalidate the specified paths accordingly.

What is the role of @opennextjs/cloudflare in this architecture?

The @opennextjs/cloudflare package provides the getCloudflareContext function and enables the edge runtime compatibility layer for Next.js. According to the ifindev/fullstack-next-cloudflare source code, this integration is essential for accessing Cloudflare bindings and ensuring Server Actions execute correctly on the edge runtime.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →