Component / Composable / Server
Vue d'Ensemble de l'Architecture
L'application suit une architecture en couches qui sépare clairement les responsabilités entre la présentation, la logique métier, et la gestion des données. Cette séparation permet une meilleure maintenabilité, testabilité, et évolutivité du code.
graph TB subgraph "Client Browser" UI[Interface Utilisateur] end subgraph "Nuxt 3 Application" subgraph "Frontend Layer" Components[Vue Components] Composables[Composables Directory] Store[Pinia Stores] end subgraph "Server Layer" API[API Routes] SDK[Djust SDK] end end subgraph "External Services" Djust[Djust Back End] CMS[Storyblok CMS] end UI --> Components Components --> Composables Composables --> Store Composables --> API SDK --> Djust API --> SDK Composables --> CMS
Organisation des Dossiers Principaux
1. 📁 components/
- Couche de Présentation
components/
- Couche de PrésentationLe dossier components
contient tous les composants Vue réutilisables organisés selon la méthodologie Atomic Design. Ces composants sont responsables de l'affichage et de l'interaction utilisateur.
Structure Organisationnelle
components/
├── atoms/ # Composants de base (boutons, inputs, images)
├── molecules/ # Combinaisons d'atomes (formulaires, cartes)
├── organisms/ # Structures complexes (header, footer, listes)
├── pages/ # Composants spécifiques aux pages
├── templates/ # Layouts et structures de page
└── layouts/ # Layouts généraux (auth, default, blank)
Responsabilités
- Affichage des données reçues via les props
- Gestion des événements utilisateur (clicks, saisies, etc.)
- Émission d'événements vers les composants parents
- Appel des composables pour la logique métier
- Gestion de l'état local du composant
Exemple d'Interaction
<template>
<div class="product-card">
<h3>{{ product.name }}</h3>
<button @click="addToCart">Add to Cart</button>
</div>
</template>
<script setup lang="ts">
interface Props {
product: Product;
}
const props = defineProps<Props>();
// Appel du composable pour la logique métier
const { addProductToCart } = useCart();
const addToCart = async () => {
try {
await addProductToCart(props.product.id, 1);
// Composant notifie le succès
emit("product-added", props.product);
} catch (error) {
// Gestion d'erreur locale
console.error("Failed to add product:", error);
}
};
</script>
2. 🧩 composables/
- Couche Logique Métier
composables/
- Couche Logique MétierLe dossier composables
contient la logique métier réutilisable sous forme de fonctions composables Vue 3. Ces fonctions encapsulent les appels API, la gestion d'état, et les opérations complexes.
Structure Par Feature
composables/
├── useCart/ # Gestion du panier
│ ├── index.ts # Export principal
│ ├── useCartApi.ts # Appels API
│ ├── useCartGetters.ts # Getters du store
│ └── useCartHelpers.ts # Fonctions utilitaires
├── useDjustAuth/ # Authentification
├── useProduct/ # Gestion produits
└── useOrder/ # Gestion commandes
Responsabilités
- Encapsulation de la logique métier spécifique aux features
- Appels aux API backend via le dossier
server/
- Interaction avec les stores Pinia pour la gestion d'état
- Gestion des erreurs et validation des données
- Transformation des données entre API et composants
Flux de Données
sequenceDiagram participant C as Vue Component participant Comp as Composable participant Store as Pinia Store participant API as Server API participant DB as Database C->>Comp: Call composable function Comp->>Store: Get current state Store-->>Comp: Return state data Comp->>API: Make API request API->>DB: Database operation DB-->>API: Return data API-->>Comp: API response Comp->>Store: Update state Store-->>C: Reactive state update C-->>C: UI re-renders
Exemple de Composable
// composables/useCart/useCartApi.ts
export const useCartApi = () => {
const cartStore = useCartStore();
const addProductToCart = async (productId: string, quantity: number) => {
try {
// Appel vers le server/api
const { data } = await useFetch("/api/carts/add-product", {
method: "POST",
body: { productId, quantity },
});
// Mise à jour du store
cartStore.addProduct(data.value);
return data.value;
} catch (error) {
console.error("Cart API error:", error);
throw error;
}
};
return { addProductToCart };
};
3. 🖥️ server/
- Couche Backend/API
server/
- Couche Backend/APILe dossier server
contient la logique côté serveur qui s'exécute sur le serveur Nuxt. Cette couche gère les API routes et la communication avec le SDK.
Structure du Server
server/
├── api/ # Routes API (/api/*)
│ ├── auth/ # Authentification
│ ├── carts/ # Gestion paniers
│ ├── products/ # Gestion produits
│ └── orders/ # Gestion commandes
├── middleware/ # Middleware serveur
├── plugins/ # Plugins serveur (Sentry, etc.)
└── utils/ # Utilitaires serveur
Responsabilités
- Exposition des API REST pour les composables
- Authentification et autorisation des requêtes
- Communication avec les services externes (APIs tierces)
- Transformation et validation des données
- Gestion des erreurs serveur et logging
- Optimisation des performances (cache, mise en pool)
Exemple d'API Route
// server/api/carts/add-product.post.ts
export default defineEventHandler(async (event) => {
try {
// Validation de l'authentification
const user = await ensureAuthenticated(event);
// Lecture du body de la requête
const { productId, quantity } = await readBody(event);
// Validation des données
if (!productId || !quantity) {
throw createError({
statusCode: 400,
statusMessage: "Product ID and quantity are required",
});
}
// Appel au service externe via SDK
const cartService = new CartService();
const result = await cartService.addProduct(user.id, productId, quantity);
// Retour de la réponse
return {
success: true,
data: result,
};
} catch (error) {
// Gestion d'erreur centralisée
throw createError({
statusCode: error.statusCode || 500,
statusMessage: error.message || "Internal server error",
});
}
});
Server-Side Rendering (SSR) avec Nuxt 3
Qu'est-ce que le SSR ?
Le Server-Side Rendering est une technique où les appels api sont faits côté serveur plutôt que côté client. Nuxt 3 fournit cette fonctionnalité automatiquement avec des optimisations avancées.
Flux de Données Complet
Exemple : Ajout d'un Produit au Panier
graph TD A[User clicks Add to Cart] --> B[Vue Component] B --> C[useCart Composable] C --> D[useFetch to /api/carts/add] D --> E[Server API Route] E --> F[Authentication Check] F --> G[Cart Service] G --> H[Database Update] H --> I[Return Response] I --> J[Update Pinia Store] J --> K[Reactive UI Update] style A fill:#ffeb3b style B fill:#e1f5fe style C fill:#e8f5e8 style E fill:#f3e5f5 style J fill:#fff3e0
Code Détaillé du Flux
1. Composant Vue (components/)
<template>
<button @click="handleAddToCart" :disabled="pending">
{{ pending ? "Adding..." : "Add to Cart" }}
</button>
</template>
<script setup lang="ts">
const props = defineProps<{ productId: string }>();
// Utilisation du composable
const { addProductToCart, pending } = useCart();
const handleAddToCart = async () => {
await addProductToCart(props.productId, 1);
// L'UI se met à jour automatiquement via la réactivité
};
</script>
2. Composable (composables/)
// composables/useCart/useCartApi.ts
export const useCartApi = () => {
const pending = ref(false);
const addProductToCart = async (productId: string, quantity: number) => {
pending.value = true;
try {
// Appel automatiquement optimisé pour SSR/Client
const { data } = await useFetch("/api/carts/add-product", {
method: "POST",
body: { productId, quantity },
});
// Mise à jour du store global
const cartStore = useCartStore();
cartStore.addProduct(data.value);
return data.value;
} finally {
pending.value = false;
}
};
return { addProductToCart, pending };
};
3. API Route (server/)
// server/api/carts/add-product.post.ts
export default defineEventHandler(async (event) => {
// Authentification automatique
const user = await ensureAuthenticated(event);
const { productId, quantity } = await readBody(event);
// Logique métier
const cartService = new CartService();
const result = await cartService.addProduct(user.cartId, productId, quantity);
return {
success: true,
data: result,
};
});
Avantages de Cette Architecture
1. Séparation des Responsabilités
- Components : Interface utilisateur pure
- Composables : Logique métier réutilisable
- Server : API et logique serveur
2. Réutilisabilité
- Composables partagés entre plusieurs composants
- Components modulaires et testables
- API routes documentées et versionnées
3. Performance
- SSR pour le chargement initial
- Client-side routing pour la navigation
- Cache intelligent à tous les niveaux
4. Maintenabilité
- Code organisé par feature
- Types TypeScript stricts
- Gestion d'erreurs centralisée
5. Évolutivité
- Architecture modulaire
- Ajout facile de nouvelles features
- Séparation claire frontend/backend
Bonnes Pratiques
Pour les Components
- Garder les composants purs et prévisibles
- Utiliser les composables pour la logique complexe
- Émettre des événements plutôt que modifier les props
Pour les Composables
- Une responsabilité par composable
- Gestion d'erreurs robuste
- Types TypeScript explicites
Pour les Server Routes
- Validation systématique des inputs
- Authentification/autorisation appropriée
- Réponses d'erreur cohérentes
Pour le SSR
- Éviter les API côté serveur uniquement dans les composables
- Utiliser
process.server
/process.client
quand nécessaire - Optimiser les requêtes pour éviter les waterfalls
Cette architecture garantit une application robuste, performante et maintenant, tout en tirant parti des avantages du SSR pour l'expérience utilisateur et le SEO.
Updated about 2 months ago