Best practices

Error handling for Server Functions

Return expected outcomes as data and show safe messages for Server Function failures.

Server Functions need two kinds of error handling: return expected business outcomes as data, and handle invalid input or operational failures at the client boundary. This applies to mutation, query, and stream calls.

Return expected outcomes as data

For an expected outcome such as a reserved name, return a tagged success value from the handler. Mutation callers can render that value with useActionState; query and stream callers can render it like any other result.

// src/greet.ts
"use server";

import { Effect, Schema } from "effect";
import { EFFRONT } from "./effront";

export const greet = EFFRONT.ServerFn.make({
  input: Schema.Struct({ name: Schema.NonEmptyString }),
  handler: ({ name }) =>
    Effect.succeed(
      name === "Admin"
        ? { _tag: "ReservedName" as const }
        : { _tag: "Greeting" as const, message: `Hello, ${name}.` },
    ),
});

This keeps expected outcomes separate from input validation and unexpected failures.

Show safe failure messages

query and stream report client-side operational failures through the Effect error channel as ServerFnError from @effront/core/query.

ErrorMeaningShow users
ServerFnInputErrorThe input could not be decoded or validated.A message explaining the input is invalid.
ServerFnDefectThe handler failed unexpectedly.A generic retry message.
ServerFnTransportErrorThe browser could not complete the request.A connection and retry message.

Handle known tags and do not expose a defect's detail or stack. The following example uses lookupTicket from the Query guide.

// src/ticket-message.ts
"use client";

import { query } from "@effront/core/query";
import { Effect } from "effect";
import { lookupTicket } from "./ticket";

const lookupMessage = (ticketCode: string) =>
  query(lookupTicket)({ ticketCode }).pipe(
    Effect.map(({ status }) => status),
    Effect.catchTags({
      ServerFnInputError: () => Effect.succeed("Invalid ticket code."),
      ServerFnDefect: () => Effect.succeed("Could not check ticket."),
      ServerFnTransportError: () => Effect.succeed("Connection lost. Please retry."),
    }),
  );

For mutations, schema decoding and unexpected handler failures are action failures rather than updates to useActionState state. Use React's useActionState reference to choose the surrounding error UI.