Next.js : Arrêtez le Spaghetti Code dans vos Server Actions
Mis à jour le 7 janvier 2026

Next.js : Arrêtez le Spaghetti Code dans vos Server Actions

En 2026, Next.js vous laisse tout faire, y compris n'importe quoi. Vous appelez votre ORM directement dans vos composants UI ? C'est un suicide architectural. J'ai vu trop de projets s'effondrer sous le poids de la "colocation" mal comprise. Voici comment structurer un CRUD qui survit à la hype, aux refontes et à votre future équipe.

Nous sommes en 2026. Le App Router est stable, les Server Actions sont la norme, et pourtant, je vois encore le même code toxique qu'en 2023.

La promesse de Vercel est séduisante : "Écrivez votre SQL à côté de votre bouton". C'est un piège.

Pour un prototype de hackathon ? Allez-y. Pour une app qui doit vivre plus de 6 mois ? C'est criminel. Aujourd'hui, on démonte le mythe de la "simplicité" pour construire quelque chose de solide.

Le Problème : L'Infection de l'UI par la Data

Quand vous importez prisma ou drizzle directement dans page.tsx ou une Server Action inline, vous couplez votre interface à votre infrastructure.

Si demain vous voulez changer d'ORM, exposer une API REST publique ou simplement tester votre logique métier sans lancer un navigateur, vous êtes coincés. Vous devez réécrire l'application.

Le piège des Server Actions : Ne traitez jamais une Server Action comme votre logique métier. C'est un contrôleur HTTP déguisé. Elle ne doit faire que trois choses : valider l'input, appeler un Service, et gérer la réponse UI.

L'Architecture en Couches (Oui, même en JS)

Oubliez la complexité de Java Spring, mais gardez la discipline. Nous avons besoin de trois niveaux distincts pour un CRUD robuste :

  1. Interface (UI) : Composants React stupides.
  2. Infrastructure (Actions) : Validation, Auth check, Parsing.
  3. Domaine (Services) : La vérité métier, agnostique du framework.
Flux Unidirectionnel : UI → Validation → Service → DB
Flux Unidirectionnel : UI → Validation → Service → DB

1. La Loi de la Validation (Zod/Valibot)

En 2026, si vous n'avez pas un schéma strict pour chaque entrée, vous codez à l'aveugle. Nous utilisons Zod (ou Valibot pour le tree-shaking) pour définir le contrat.

// src/domain/products/schema.ts
import { z } from 'zod';

export const CreateProductSchema = z.object({
  name: z.string().min(3, "Le nom est trop court"),
  price: z.number().positive("Le prix doit être positif"),
  sku: z.string().regex(/^[A-Z0-9-]+$/, "Format SKU invalide")
});

export type CreateProductInput = z.infer<typeof CreateProductSchema>;

2. Le Service : Le Gardien du Métier

C'est ici que vit votre code. Pas dans Next.js. Ce fichier ne doit rien savoir de FormData ou de revalidatePath.

Il prend des données propres, applique les règles métier, et parle à la DB.

// src/domain/products/service.ts
import { db } from '@/lib/db';
import { CreateProductInput } from './schema';

export const ProductService = {
  create: async (data: CreateProductInput) => {
    // Règle métier : Pas de doublons de SKU
    const existing = await db.product.findUnique({ where: { sku: data.sku } });
    if (existing) {
      throw new Error("Ce SKU existe déjà");
    }

    // Création pure
    return db.product.create({ data });
  }
};
Testabilité immédiate : En isolant `ProductService`, vous pouvez écrire des tests unitaires avec Vitest sans jamais mocker `next/headers` ou `next/navigation`. C'est là que vous gagnez du temps sur le long terme.

3. La Server Action : Le Passe-Plat

Maintenant, et seulement maintenant, on touche à Next.js. La Server Action est notre couche de transport. Elle attrape la requête, sécurise l'entrée, et délègue.

// src/app/actions/create-product.ts
'use server';

import { redirect } from 'next/navigation';
import { CreateProductSchema } from '@/domain/products/schema';
import { ProductService } from '@/domain/products/service';
import { auth } from '@/lib/auth'; // Votre auth de choix

export async function createProductAction(prevState: any, formData: FormData) {
  // 1. Auth Check
  const session = await auth();
  if (!session) return { error: "Non autorisé" };

  // 2. Validation & Parsing
  const rawData = Object.fromEntries(formData.entries());
  const parsed = CreateProductSchema.safeParse({
    ...rawData,
    price: Number(rawData.price)
  });

  if (!parsed.success) {
    return { errors: parsed.error.flatten().fieldErrors };
  }

  try {
    // 3. Appel au Service
    await ProductService.create(parsed.data);
  } catch (e) {
    // Gestion d'erreur propre
    return { error: e instanceof Error ? e.message : "Erreur inconnue" };
  }

  // 4. Side Effects (Spécifique Next.js)
  redirect('/dashboard/products');
}

Pourquoi ça change tout ?

Cette structure vous protège de vous-même.

  • Vous voulez passer à une API Route ? Importez ProductService dans votre route handler GET. Zéro duplication.
  • Vous voulez changer de DB ? Vous ne touchez que service.ts.
  • Le Frontend change ? La logique métier reste intacte.

Arrêtez de coder pour la démo de 5 minutes. Codez pour la maintenance de 5 ans.

Un projet en tête ?

Discutons de vos besoins techniques et voyons comment je peux vous aider à concrétiser votre projet.

Me contacter