Building a Centralized Action for Large-Scale SaaS with Next.js
Source: Dev.to
Architecture Overview
In large SaaS projects, handling server actions individually in every module creates code duplication.
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.
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.
Authentication Handling
if (requireAuth) {
const accessToken = cookieStore.get("accessToken")?.value;
if (!accessToken) {
return { message: "You are not authorized to perform this action!" };
}
// Validate JWT access token...
}
Shop / Subdomain Context
if (requireShopId) {
const subdomain = cookieStore.get("shopSubdomain")?.value;
if (!subdomain) {
return { message: "Shop identifier not found!" };
}
// Use subdomain for multi‑tenant routing...
}
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" };
}
Maps HTTP methods → CRUD actions.
Dynamic Payload Handling
let body: string | undefined = undefined;
if (method === "DELETE" && ids?.length) {
body = JSON.stringify({ ids });
} else if (method !== "DELETE" && data) {
body = data;
}
Supports single‑ID deletion, bulk deletion, and POST/PUT payloads.
Fetch Execution
const response = await fetch(url, {
method,
headers,
body,
cache: "no-store",
});
const result = await response.json();
Executes server‑side mutations with fresh data (no-store).
Cache Revalidation
paths.forEach(path => typeof path === "string" && revalidatePath(path));
tags.forEach(tag => typeof tag === "string" && revalidateTag(tag));
Automatically revalidates Next.js paths or tags after a mutation.
Error Handling & Logging
try {
// ...mutation logic...
} catch (error) {
logger(error);
return { success: false, message: "An error occurred during the mutation." };
}
Standardizes error handling.
Benefits
- Reusability – Single mutation handler for all models.
- Security – Global RBAC enforcement.
- Multi‑Tenant Ready – Handles shop‑specific subdomains.
- Consistency – Unified logging, error handling, and cache revalidation.
- Scalability – Easy to extend as the SaaS grows.
Usage Example
await mutation({
route: "/customer",
method: "POST",
data: JSON.stringify({ name: "John Doe" }),
pathToRevalidate: "/customers",
tagsToRevalidate: ["customer‑list"],
});
Handles authentication, RBAC, payload, and cache invalidation in one call.
Flow Diagram
┌────────────────────┐
│ Client / Frontend │
│ (React / Next.js) │
└─────────┬──────────┘
│
▼
┌────────────────────┐
│ Server Action Call│
│ mutation(params) │
└─────────┬──────────┘
│
┌────────┴─────────┐
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ AUTHENTICATION│ │ SHOP / SUBDOMAIN│
│ check JWT token│ │ get shop identifier│
│ 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 │ │ TAG │ │ CLIENT│
│ REVAL │ │ REVAL │ │ UI │
└───────┘ └───────┘ └───────┘
Takeaways
- Centralized server actions reduce boilerplate and improve maintainability.
- RBAC ensures consistent security across all API mutations.
- Subdomain‑aware multi‑tenant architecture supports SaaS scalability.
- Automatic cache revalidation guarantees the 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, 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 and actions with minimal friction.