Server Actions
A Server Action is a function that runs on the server but is invoked from a client component as if it were a local function call. The Next.jsNext.jsReact framework used by HiveCFM Core. Handles routing, server rendering, and API routes in one bundle. compiler rewrites the call into a signed POST under the hood.
If you squint, a Server Action is roughly analogous to an ASP.NET Core minimal-API endpoint that is auto-bound to the calling page — no routing to configure, no DTO to wire, no HTTP client on the client. You just import the function and call it.
The mechanics
Two pieces mark a function as a Server Action:
- The directive
"use server"at the top of the file (or inside the function). - An
asyncexported function.
// app/(app)/.../actions.ts
"use server";
export async function archiveSurvey(id: string) {
// runs on the Node server
}On the client:
"use client";
import { archiveSurvey } from "./actions";
<button onClick={() => archiveSurvey(id)}>Archive</button>The Next.jsNext.jsReact framework used by HiveCFM Core. Handles routing, server rendering, and API routes in one bundle. compiler sees the cross-boundary import, strips the function body out of the client bundle, and replaces the call with a fetch to an internal endpoint.
When to use a Server Action vs an API route
| You want to… | Use |
|---|---|
| Submit a form or button click from the HiveCFM UI | Server Action |
| Accept a POST from the SurveySurveyThe core HiveCFM object. A definition of questions, logic, and targeting — shown to respondents to collect feedback. Widget or SDK | API route under api/v2/client/... |
| Accept a request from a third party, a partner, or a webhook | API route |
| Return data that needs to be cached by an external CDN | API route |
| Mutate DB + invalidate the current page | Server Action (call revalidatePath) |
Rule of thumb: if the caller is HiveCFM itself, use a Server Action; if the caller is anything else, use an API route.
The HiveCFM convention — authenticatedActionClient
Raw "use server" functions work, but HiveCFM wraps them in an action client that handles auth, zod validation, and structured error return. Pattern:
// apps/web/app/(app)/environments/[environmentId]/actions.ts
"use server";
import { z } from "zod";
import { ZId } from "@hivecfm/types/common";
import { ZProjectUpdateInput } from "@hivecfm/types/project";
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler";
const ZCreateProjectAction = z.object({
organizationId: ZId,
data: ZProjectUpdateInput,
});
export const createProjectAction = authenticatedActionClient
.schema(ZCreateProjectAction)
.action(
withAuditLogging("created", "project", async ({ ctx, parsedInput }) => {
// ctx.user is guaranteed non-null here
await checkAuthorizationUpdated({ userId: ctx.user.id, ... });
return createProject(parsedInput.organizationId, parsedInput.data);
}),
);What the chain buys you:
authenticatedActionClient— rejects the call with a 401 if the NextAuthNextAuthThe auth library HiveCFM Core uses to handle sessions, OAuth providers, and credentials. session is missing, and populatesctx.user..schema(...)— zod validates the input; invalid calls short-circuit with field-level errors..action(...)— the business logic, with a typedparsedInput.withAuditLogging(...)— writes an audit row for enterprise plans.checkAuthorizationUpdated(...)— RBAC: does this user have permission on this organisation/environment?
This is the .NET analogue of stacking [Authorize], [ValidateModel], and an audit filter on a controller action — except it is composed with plain function wrappers instead of attributes.
Real examples in the repo
Grep "use server" in hivecfm-core/apps/web/ to find them all. A handful worth reading:
apps/web/app/(app)/environments/[environmentId]/actions.ts— create project, update organisation.apps/web/app/(app)/environments/[environmentId]/workspace/integrations/actions.ts— connect/disconnect integrations.apps/web/app/setup/organization/create/actions.ts— the first-run organisation bootstrap.apps/web/app/(app)/(onboarding)/lib/onboarding.ts— onboarding step transitions.
Cache invalidation after a mutation
An action that changes data should tell Next.jsNext.jsReact framework used by HiveCFM Core. Handles routing, server rendering, and API routes in one bundle. what to re-render:
import { revalidatePath, revalidateTag } from "next/cache";
// Re-render every instance of this route pattern
revalidatePath("/environments/[environmentId]/surveys", "page");
// Or: invalidate everything tagged "surveys" (if the read used fetch with a tag)
revalidateTag("surveys");Without the call, the server component will still render yesterday’s data because Next.jsNext.jsReact framework used by HiveCFM Core. Handles routing, server rendering, and API routes in one bundle. cached the render.
Never put sensitive branching on a value the client sends. Actions run on the server, but the client still provides the arguments. Always re-check authorisation inside the action, even if the button is only shown to admins in the UI.
Read next
- Frontend / State and Data — the client side of calling actions.
- API routes — the alternative path for external callers.