DEV Community

Cover image for Building a Centralized Action for Large-Scale SaaS with Next.js
JAKER HOSSAIN
JAKER HOSSAIN

Posted on

Building a Centralized Action for Large-Scale SaaS with Next.js

Learn how to implement a centralized mutation system for multi-tenant SaaS using Next.js Server Actions. Explore RBAC, multi-subdomain handling, cache revalidation, and scalable API patterns for secure, maintainable backend architecture.

A. Architecture Overview

In large SaaS projects, handling server actions individually in every module creates:

1- Code duplication
2- Inconsistent authorization checks
3- Cache invalidation issues
4 -Multi-tenant subdomain problems

The centralized mutation engine consolidates all API mutations into one reusable function. It handles authentication, authorization, shop/subdomain context, request payloads, fetch execution, and cache revalidation.

B. Core Utilities

logger – Structured logging for debugging and monitoring.

resolveModelName – Converts route names like saved-attribute → saved_attribute.

hasPermission & parseAccessRules – Implements RBAC to check if a user can perform a specific action.

getBaseUrlWithSubdomain – Resolves the correct API base URL based on the shop subdomain.

C. Authentication Handling

if (requireAuth) {
  accessToken = cookieStore.get("accessToken")?.value;
  if (!accessToken) return { message: "You are not authorized to perform this action!" };
}
Enter fullscreen mode Exit fullscreen mode

-Validates JWT access token.
-Immediately stops unauthorized requests.

D. Shop / Subdomain Context

if (requireShopId) {
  subdomain = cookieStore.get("shopSubdomain")?.value;
  if (!subdomain) return { message: "Shop identifier not found!" };
}
Enter fullscreen mode Exit fullscreen mode

-Supports multi-tenant SaaS using shop subdomains.
-Injects x-app-identifier for API requests.

E. Role-Based Access Control (RBAC)

const rulesRaw = cookieStore.get("accessRules")?.value;
const rules = parseAccessRules(rulesRaw);
const model = resolveModelName(route.split("/")[1]);
const action = methodToAction[method];
if (!hasPermission(rules, model, action)) return { success: false, message: "Permission denied" };
Enter fullscreen mode Exit fullscreen mode

-Maps HTTP methods → CRUD actions.
-Validates permissions dynamically.
-Ensures consistent security across the SaaS.

F. Dynamic Payload Handling

let body: string | undefined = undefined;
if (method === "DELETE" && ids?.length) body = JSON.stringify({ ids });
else if (method !== "DELETE" && data) body = data;
Enter fullscreen mode Exit fullscreen mode

-Supports single ID deletion, bulk deletion, and POST/PUT payloads.

G. Fetch Execution

const response = await fetch(url, { method, headers, body, cache: "no-store" });
const result = await response.json();
Enter fullscreen mode Exit fullscreen mode

-Executes server-side mutations with fresh data (no-store).
-Centralizes API interaction logic.

H. Cache Revalidation

paths.forEach(path => typeof path === "string" && revalidatePath(path));
tags.forEach(tag => typeof tag === "string" && revalidateTag(tag));
Enter fullscreen mode Exit fullscreen mode

-Automatically revalidates Next.js paths or tags after mutation.
-Ensures UI always reflects latest backend data.

I. Error Handling & Logging

catch (error) {
  logger(error);
  return { success: false, message: "An error occurred during the mutation." };
}
Enter fullscreen mode Exit fullscreen mode

-Standardizes error handling.
-Logs all server-side errors for monitoring.

J. Benefits

  1. Reusability – Single mutation handler for all models.
  2. Security – Global RBAC enforcement.
  3. Multi-Tenant Ready – Handles shop-specific subdomains.
  4. Consistency – Unified logging, error handling, and cache revalidation.
  5. Scalability – Easy to extend as SaaS grows.

K. Usage Example

await mutation({
  route: "/customer",
  method: "POST",
  data: JSON.stringify({ name: "John Doe" }),
  pathToRevalidate: "/customers",
  tagsToRevalidate: ["customer-list"],
});
Enter fullscreen mode Exit fullscreen mode

-Handles authentication, RBAC, payload, and cache invalidation in one call.

                 ┌────────────────────┐
                 │  Client / Frontend │
                 │ (React / Next.js)  │
                 └─────────┬──────────┘
                           │
                           ▼
                 ┌────────────────────┐
                 │  Server Action Call│
                 │   mutation(params) │
                 └─────────┬──────────┘
                           │
             ┌─────────────┴─────────────┐
             │                           │
             ▼                           ▼
   ┌───────────────────┐        ┌───────────────────┐
   │  AUTHENTICATION   │        │ SHOP / SUBDOMAIN  │
   │ check JWT token   │        │ get shopidentifier│
   │ from cookies      │        │ from cookies      │
   └─────────┬─────────┘        └─────────┬─────────┘
             │                           │
             └─────────────┬─────────────┘
                           ▼
                 ┌────────────────────┐
                 │ ROLE-BASED ACCESS  │
                 │ CONTROL (RBAC)     │
                 │ hasPermission()    │
                 │ parseAccessRules() │
                 └─────────┬──────────┘
                           ▼
                 ┌────────────────────┐
                 │  Construct Headers │
                 │ - Authorization    │
                 │ - x-app-identifier │
                 └─────────┬──────────┘
                           ▼
                 ┌─────────────────────┐
                 │   Determine Payload │
                 │  - DELETE ids array │
                 │  - POST / PUT data  │
                 └─────────┬─────────  ┘
                           ▼
                 ┌────────────────────┐
                 │     FETCH Request  │
                 │   method, headers, │
                 │       body         │
                 └─────────┬──────────┘
                           ▼
                 ┌────────────────────┐
                 │  Response Handling │
                 │ - parse JSON       │
                 │ - log errors       │
                 └─────────┬──────────┘
                           ▼
             ┌─────────────┴─────────────┐
             │                           │
             ▼                           ▼
   ┌───────────────────┐        ┌──────────────────┐
   │ PATH REVALIDATION │        │ TAG REVALIDATION │
   │ revalidatePath()  │        │ revalidateTag()  │
   └─────────┬─────────┘        └────────┬─────────┘
             │                           │
             └─────────────┬─────────────┘
                           ▼
                 ┌─────────────────────┐
                 │      CLIENT UI      │
                 │  Updated with fresh │
                 │      data           │
                 └─────────────────────┘

Enter fullscreen mode Exit fullscreen mode

M. Takeaways

  1. Centralized server actions reduce boilerplate and improve maintainability.
  2. RBAC ensures consistent security across all API mutations.
  3. Subdomain-aware multi-tenant architecture supports SaaS scalability.
  4. Automatic cache revalidation guarantees UI always displays fresh data.

Implementing a centralized mutation engine in a large-scale SaaS project dramatically improves code maintainability, security, and scalability. By consolidating authentication, role-based access control (RBAC), multi-tenant subdomain handling, payload management, and cache revalidation into a single server action, developers can:

-Eliminate repetitive code across multiple endpoints.

-Enforce consistent security with dynamic permission checks.

-Support multi-tenant architectures efficiently via subdomain-aware API calls.

-Ensure UI consistency by automatically revalidating Next.js paths and tags.

-Scale easily as the SaaS grows, adding new models or endpoints without breaking existing logic.

In essence, this approach creates a robust, maintainable, and secure foundation for handling server-side mutations in any complex SaaS ecosystem. It’s a best-practice architecture that balances developer efficiency, security, and performance, making your backend easier to manage and your frontend more reliable.

👨‍💻 About the Author
Name: JAKER HOSSAIN
Username: @jackfd120
Role: Senior Frontend Developer
Portfolio: https://www.poranfolio.space/

Top comments (0)