Journal / Backend, APIs, and System Design

Backend, APIs, and System Design

Server Actions, Edge Functions, and the Modern Backend Surface

The modern backend surface for web applications encompasses multiple execution environments: traditional API servers (Node.js, Python, Go running on centralized servers), edge functions (short lived JavaScript functions running close to the user on distributed edge networks), and server actions (server functions specific to Next.js, called directly from client components without an explicit API route). Each environment has different latency characteristics, available APIs, and appropriate use cases. The choice between them affects performance, development model, and infrastructure cost.

What you need to know

  • Server actions colocate server logic with the client code that calls it, eliminating boilerplate API routes for common mutations. Use them for form submissions and operations that are not public.
  • Edge functions run close to the user with minimal cold start but have constrained runtime environments. Use them for auth middleware, routing, and fast cacheable operations.
  • Traditional serverless functions have full Node.js access and regional database connectivity. Use them for complex business logic, operations that run long, and operations that lean heavily on the database.
  • Servers that run continuously are appropriate for WebSocket connections, high throughput workloads, and applications that need a cache held in memory.
  • Database connectivity is the most common constraint when choosing the edge: not all databases support connection protocols that work from the edge.

The core argument

The modern web application backend is not a single thing. It is a set of execution environments, each with different latency, cost, and capability trade offs, and the job of a backend architect is to match logic to the appropriate environment rather than to centralize everything in one place.

The shift that Next.js server components and server actions represent is significant: they move the boundary between client and server from an explicit API contract to an implicit function call. A developer can write a function, mark it with use server, and call it from a client component as if it were local. The network call is handled by the framework. This colocation of client and server logic reduces the boilerplate of maintaining separate API routes for operations specific to the application, at the cost of making the boundary between the two less explicit.

The edge function opportunity is real for operations that need to run on every request: authentication token validation, request routing, feature flag evaluation, and A/B test assignment. Running these operations in a centralized region introduces 50 to 200ms of round trip latency for users geographically distant from the server. Running them at the edge eliminates that latency entirely. The constraint is that most databases cannot be accessed directly from the edge runtime, which limits edge functions to stateless operations or operations backed by a cache.

Common mistakes

  1. Putting logic that leans heavily on the database in edge functions. Edge functions that need to reach a centralized database for every request incur the latency of the edge reaching that database, which may be worse than the user to server latency they were designed to eliminate. Edge functions are effective for logic that can run without a database round trip: reading from edge KV stores, validating JWTs (the public key can be cached at the edge), or routing based on request headers.

  2. Using server actions for public API endpoints. Server actions are specific to the framework and tightly coupled to the Next.js rendering model. External clients (mobile apps, third-party integrations, automation tools) cannot call server actions directly. Public API endpoints that need to be consumed by external clients require explicit API routes, not server actions. Use server actions only for operations that are inherently part of the Next.js application flow.

  3. Not handling server action errors in the client. Server actions can throw errors that need to surface to the user as form validation feedback or error messages. Without explicit error handling in the calling component, server action errors produce silent failures or unhandled exception boundaries. Handle server action results with try/catch or use the returned result pattern to propagate errors to the UI.

  4. Creating serverless function cold start problems in flows the user sees directly. Serverless functions with cold starts that occur in the critical path of a user action (form submission, page load) produce intermittent latency spikes. For user facing operations where consistent latency matters, use warm serverless functions (minimum instances), edge functions for the fast path, or traditional servers that run continuously. Cold starts in background jobs and asynchronous operations are acceptable; cold starts in synchronous operations the user is waiting on are not.

  5. Not considering the development experience difference between edge and serverless environments. Edge runtime debugging is harder than serverless debugging because the constrained environment produces errors that do not occur locally and because not all local development tools emulate the edge runtime accurately. Budget additional development and testing time for edge function work compared to traditional serverless or API server development.

Where to start

  1. Identify the operations in the product that would benefit from edge execution. Look for: operations that run on every request (authentication, routing), operations where latency is visible to the user, who may be geographically distant from the primary server region, and operations that are currently creating API route boilerplate for simple mutations. These are the candidates for edge functions and server actions respectively.

  2. Migrate form submissions and simple mutations to server actions. For any Next.js application with explicit API routes that are only called from a single client component, migrate to server actions. The migration reduces boilerplate and keeps client and server logic colocated. Start with one form submission (contact form, settings update) and verify the error handling and optimistic update patterns work before migrating more complex operations.

  3. Test database connectivity from the edge before designing features that depend on it. Before building features that assume the edge can reach the database, verify that the database provider supports edge connections (Neon, PlanetScale, Turso). Test the latency and connection reliability in the target edge regions. Discovering that the database does not support edge connections partway through implementation is an expensive way to learn it.

FAQ

Frequently asked

  • What are Next.js server actions and when should they be used?
  • What are edge functions and what constraints do they have?
  • What is the difference between edge functions and serverless functions?
  • When should a traditional API server be used instead of serverless or edge?
  • How do you decide where business logic should live across these environments?

Author

The reason my name is on this page

My name is on this page because I wrote what is on this page. Yashveer Singh. Full stack developer. Founder of Yashveer Labs. The portfolio is on the homepage. The projects are live. The code is real. The work is provable. If you have read this far, you already know whether the voice matches the standard you are looking for. The next move is yours.

Start the conversation See the work DM on Instagram