Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
ReactNext.jsJune 22, 20264 min read

Next.js Server Actions Decorator Pattern: Building Resilient Interceptors

Next.js Server Actions decorators allow you to centralize cross-cutting concerns like auth and logging. Learn to build resilient interceptors for your mutations.

Next.jsReactTypeScriptArchitectureServer ActionsFrontend

Last month, our team spent about three days tracking down a silent failure in a mutation that lacked consistent logging. We had dozens of Server Actions scattered across the codebase, each manually handling its own auth check, Zod validation, and error serialization. It was a maintenance nightmare that eventually led us to adopt a cleaner approach: the decorator pattern.

By treating our mutations as first-class citizens in a middleware pipeline, we moved cross-cutting concerns out of the business logic and into a predictable, reusable wrapper.

The Problem with Manual Interception

When you write Next.js Server Actions: Implementing Type-Safe Mutations and Middleware, it’s easy to fall into the trap of writing the same if (!user) throw Error(...) logic inside every single function. This approach is brittle. If you decide to change your logging provider or add a distributed tracing header, you have to touch every file in your actions/ directory.

We initially tried wrapping our actions in higher-order functions. While this worked, the TypeScript inference for the arguments often broke, leading to any types that defeated the purpose of our schema validation.

Implementing the Decorator Pattern

Instead of standard HOCs, we built a simple, chainable interface for our actions. Think of this as a "composed" pipeline. We define a generic createAction factory that accepts an array of interceptors.

TYPESCRIPT
type Interceptor = (next: (args: any) => Promise<any>) => (args: any) => Promise<any>;

export function createAction<T>(
  schema: ZodSchema<T>,
  interceptors: Interceptor[],
  handler: (data: T) => Promise<any>
) {
  const baseAction = async (data: T) => {
    const validated = schema.parse(data);
    return handler(validated);
  };

  // Reduce the interceptors to wrap the base action
  return interceptors.reduceRight((acc, interceptor) => interceptor(acc), baseAction);
}

This structure allows us to inject behavior without modifying the core logic. For example, implementing a simple logger interceptor becomes trivial:

TYPESCRIPT
const loggerInterceptor: Interceptor = (next) => async (args) => {
  console.time(CE9178">'action-execution');
  try {
    const result = await next(args);
    return result;
  } catch (error) {
    console.error(CE9178">'Action failed:', error);
    throw error;
  } finally {
    console.timeEnd(CE9178">'action-execution');
  }
};

Why This Matters for Distributed Systems Architecture

In a production environment, you aren't just logging errors; you're managing state across regions. When implementing Next.js Server Actions: Implementing Idempotency and Atomic Mutations, the decorator pattern becomes essential. You can create an idempotencyInterceptor that checks a Redis cache before even hitting the database.

This keeps your business logic pure. The developer doesn't need to know how the idempotency check works; they just include the decorator in the action definition.

Handling Request Interception at Scale

If you’re building Next.js Circuit Breaker Pattern: Building Resilient Server Actions, you can apply the same logic. By wrapping your database calls in an interceptor that tracks failure rates, you can stop the bleeding across your entire application instantly.

Here is how we compose these for a typical user update mutation:

  1. Validation Interceptor: Ensures the payload matches our Zod schema.
  2. Auth Interceptor: Verifies session cookies.
  3. Circuit Breaker: Prevents hammering an overloaded downstream service.
  4. Business Logic: The actual database update.

Trade-offs and Lessons Learned

The biggest downside to this approach is debugging stack traces. When you wrap functions in five different interceptors, the error stack can get deep and noisy. We had to implement a custom error formatter to clean up the Error.stack property so our Sentry logs wouldn't look like a recursive nightmare.

Another point of contention: should these interceptors run on the Edge or Node.js runtime? We found that for things like rate limiting or simple auth, the Edge is perfect. But for complex database-backed idempotency, you’ll want to ensure your interceptors are aware of your database connection pooling constraints.

I’m still not 100% satisfied with our type inference when we have more than three decorators. TypeScript’s recursive type depth limits can kick in if you aren't careful. For now, we manually define the types for the action parameters, which is a small price to pay for the architectural consistency we’ve gained.

If you’re embarking on this, start by moving your logging to an interceptor first. It’s the lowest-risk refactor and gives you an immediate win for observability. From there, you can slowly migrate your auth and validation logic into the pipeline as your comfort level grows.

Back to Blog

Similar Posts

Next.jsReactJune 22, 20264 min read

Next.js Rate Limiting: Secure Server Actions and Middleware Patterns

Master Next.js rate limiting to protect your Server Actions and API routes. Learn to implement distributed middleware for resilient, high-traffic applications.

Read more
Close-up of server racks in a data center highlighting modern technology infrastructure.
React
Next.js
June 21, 2026
4 min read

Next.js Server Actions: Implementing Type-Safe Mutations and Middleware

Next.js Server Actions can be brittle without the right guardrails. Learn to implement type-safe mutations, Zod validation, and middleware for production.

Read more
Next.jsReactJune 22, 20264 min read

Next.js Server Actions: Implementing Idempotent Mutation Retries

Next.js Server Actions require careful handling to prevent duplicate mutations. Learn to implement Idempotency and State Machines for reliable distributed systems.

Read more