Files
myeasycms-v2/.claude/skills/server-action-builder/examples.md
Giancarlo Buomprisco cfa137795b refactor: consolidate AGENTS.md and CLAUDE.md files, update tech stac… (#444)
* refactor: consolidate AGENTS.md and CLAUDE.md files, update tech stack and architecture details

- Merged content from CLAUDE.md into AGENTS.md for better organization.
- Updated tech stack section to reflect the current technologies used, including Next.js, Supabase, and Tailwind CSS.
- Enhanced monorepo structure documentation with detailed directory purposes.
- Streamlined multi-tenant architecture explanation and essential commands.
- Added key patterns for naming conventions and server actions.
- Removed outdated agent files related to Playwright and PostgreSQL, ensuring a cleaner codebase.
- Bumped version to 2.23.7 to reflect changes.
2026-01-18 10:44:40 +01:00

4.5 KiB

Server Action Examples

Real examples from the Makerkit codebase.

Team Billing Action

Location: apps/web/app/home/[account]/billing/_lib/server/server-actions.ts

'use server';

import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { revalidatePath } from 'next/cache';

import { UpdateBillingSchema } from '../schemas/billing.schema';
import { createBillingService } from './billing.service';

export const updateBillingAction = enhanceAction(
  async function (data, user) {
    const logger = await getLogger();
    const ctx = { name: 'update-billing', userId: user.id, accountId: data.accountId };

    logger.info(ctx, 'Updating billing settings');

    const service = createBillingService();
    await service.updateBilling(data);

    logger.info(ctx, 'Billing settings updated');

    revalidatePath(`/home/${data.accountSlug}/billing`);

    return { success: true };
  },
  {
    auth: true,
    schema: UpdateBillingSchema,
  },
);

Personal Settings Action

Location: apps/web/app/home/(user)/settings/_lib/server/server-actions.ts

'use server';

import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { revalidatePath } from 'next/cache';

import { UpdateProfileSchema } from '../schemas/profile.schema';

export const updateProfileAction = enhanceAction(
  async function (data, user) {
    const logger = await getLogger();
    const ctx = { name: 'update-profile', userId: user.id };

    logger.info(ctx, 'Updating user profile');

    const client = getSupabaseServerClient();

    const { error } = await client
      .from('accounts')
      .update({ name: data.name })
      .eq('id', user.id);

    if (error) {
      logger.error({ ...ctx, error }, 'Failed to update profile');
      throw error;
    }

    logger.info(ctx, 'Profile updated successfully');

    revalidatePath('/home/settings');

    return { success: true };
  },
  {
    auth: true,
    schema: UpdateProfileSchema,
  },
);

Action with Redirect

'use server';

import { enhanceAction } from '@kit/next/actions';
import { redirect } from 'next/navigation';

import { CreateProjectSchema } from '../schemas/project.schema';
import { createProjectService } from './project.service';

export const createProjectAction = enhanceAction(
  async function (data, user) {
    const service = createProjectService();
    const project = await service.create(data);

    // Redirect after creation
    redirect(`/home/${data.accountSlug}/projects/${project.id}`);
  },
  {
    auth: true,
    schema: CreateProjectSchema,
  },
);

Delete Action with Confirmation

'use server';

import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { revalidatePath } from 'next/cache';

import { DeleteItemSchema } from '../schemas/item.schema';

export const deleteItemAction = enhanceAction(
  async function (data, user) {
    const logger = await getLogger();
    const ctx = { name: 'delete-item', userId: user.id, itemId: data.itemId };

    logger.info(ctx, 'Deleting item');

    const client = getSupabaseServerClient();

    const { error } = await client
      .from('items')
      .delete()
      .eq('id', data.itemId)
      .eq('account_id', data.accountId); // RLS will also validate

    if (error) {
      logger.error({ ...ctx, error }, 'Failed to delete item');
      throw error;
    }

    logger.info(ctx, 'Item deleted successfully');

    revalidatePath(`/home/${data.accountSlug}/items`);

    return { success: true };
  },
  {
    auth: true,
    schema: DeleteItemSchema,
  },
);

Error Handling with isRedirectError

'use server';

import { enhanceAction } from '@kit/next/actions';
import { isRedirectError } from 'next/dist/client/components/redirect-error';
import { redirect } from 'next/navigation';

export const submitFormAction = enhanceAction(
  async function (data, user) {
    const logger = await getLogger();
    const ctx = { name: 'submit-form', userId: user.id };

    try {
      logger.info(ctx, 'Submitting form');

      // Process form
      await processForm(data);

      logger.info(ctx, 'Form submitted, redirecting');

      redirect('/success');
    } catch (error) {
      // Don't treat redirects as errors
      if (!isRedirectError(error)) {
        logger.error({ ...ctx, error }, 'Form submission failed');
        throw error;
      }
      throw error; // Re-throw redirect
    }
  },
  {
    auth: true,
    schema: FormSchema,
  },
);