@lastbrain/module-tools
v0.1.3
Published
## 🎯 Principe
Readme
Module Tools - Architecture automatique "1 outil = 1 dossier"
🎯 Principe
Pour ajouter un outil : copiez un dossier, modifiez-le et ajoutez 1 ligne dans le registry.
Chaque outil est 100% isolé dans son propre dossier avec :
- ✅ Traductions UI (fr/en/es)
- ✅ Contenu SEO complet (fr/en/es)
- ✅ Composant React (client ou server)
- ✅ Métadonnées (catégorie, statut, ordre)
L'outil sera automatiquement listé et accessible sur /tools et /tools/[slug].
📁 Structure du projet
packages/module-tools/src/
├── tools.build.config.ts # Configuration module (routes, menu)
├── index.ts # Exports publics
├── server.ts # Exports server-side
└── web/
└── public/
├── HomePage.tsx # HomePage client
├── HomePageServer.tsx # HomePage server avec metadata
└── tools/
├── _registry/
│ ├── types.ts # Types TypeScript
│ └── tools.registry.ts # ⚙️ REGISTRY - Liste des outils
├── _components/
│ ├── ToolCard.tsx # Carte outil (grille)
│ ├── ToolsGrid.tsx # Grille responsive
│ ├── ToolLayoutDesktop.tsx # Layout desktop (aside + SEO)
│ └── ToolLayoutMobile.tsx # Layout mobile (accordéons)
├── page.tsx # 📄 /tools (liste)
├── [slug]/
│ └── page.tsx # 📄 /tools/[slug] (détail)
│
├── png-to-webp/ # ✅ OUTIL 1 (interactif)
│ ├── i18n.ts # Traductions UI
│ ├── seo.ts # Contenu SEO (2800 lignes)
│ ├── tool.client.tsx # Composant "use client"
│ └── index.ts # Export standardisé
│
└── career-timeline/ # ✅ OUTIL 2 (statique)
├── i18n.ts # Traductions UI
├── seo.ts # Contenu SEO
├── tool.server.tsx # Composant serveur
└── index.ts # Export standardisé✨ Ajouter un nouvel outil (4 étapes)
1️⃣ Copier un dossier existant
cd packages/module-tools/src/web/public/tools/
# Pour un outil interactif (upload, conversion, etc.)
cp -r png-to-webp mon-outil
# Pour un outil statique (affichage, documentation)
cp -r career-timeline mon-outil2️⃣ Modifier les fichiers du dossier
Vous avez 4 fichiers à éditer :
index.ts - Point d'entrée
import { translations } from "./i18n";
import { seoContent } from "./seo";
export const TOOL_SLUG = "mon-outil"; // ⚠️ Doit correspondre au nom du dossier
export function getToolDefinition() {
return {
slug: TOOL_SLUG,
category: "productivity", // image | development | design | productivity
status: "active", // active | beta | coming-soon
isInteractive: true, // true = client | false = server
featured: false,
order: 10,
i18n: translations,
seo: seoContent,
relatedTools: ["png-to-webp"], // Outils suggérés
};
}
// Choisir selon le type
export { default as ToolClient } from "./tool.client"; // Si interactif
// OU
// export { default as ToolClient } from "./tool.server"; // Si statiquei18n.ts - Traductions UI
import type { ToolI18n } from "../_registry/types";
export const translations: ToolI18n = {
fr: {
title: "Mon Outil",
description: "Description courte pour la card",
labels: {
// Labels spécifiques à votre outil
uploadButton: "Charger un fichier",
convert: "Convertir",
download: "Télécharger",
},
},
en: {
title: "My Tool",
description: "Short description for card",
labels: {
uploadButton: "Upload file",
convert: "Convert",
download: "Download",
},
},
es: {
title: "Mi Herramienta",
description: "Descripción breve para la tarjeta",
labels: {
uploadButton: "Cargar archivo",
convert: "Convertir",
download: "Descargar",
},
},
};seo.ts - Contenu SEO riche
import type { ToolSEO } from "../_registry/types";
export const seoContent: ToolSEO = {
fr: {
title: "Mon Outil - LastBrain Tools",
description: "Meta description optimisée (150-160 caractères)",
h1: "Titre principal H1 pour SEO",
keywords: ["mot-clé1", "mot-clé2", "mot-clé3"],
sections: {
why: {
title: "Pourquoi utiliser cet outil ?",
content:
"Explication détaillée des bénéfices, cas d'usage, pourquoi c'est utile...",
},
useCases: {
title: "Cas d'usage",
items: [
"Cas d'usage 1 : explication",
"Cas d'usage 2 : explication",
"Cas d'usage 3 : explication",
],
},
howTo: {
title: "Comment l'utiliser ?",
steps: [
"Étape 1 : Description claire",
"Étape 2 : Description claire",
"Étape 3 : Description claire",
],
},
faq: [
{
question: "Question fréquente 1 ?",
answer: "Réponse détaillée avec contexte...",
},
{
question: "Question fréquente 2 ?",
answer: "Réponse détaillée avec contexte...",
},
],
},
},
en: {
title: "My Tool - LastBrain Tools",
description: "Optimized meta description (150-160 chars)",
// ... même structure en anglais
},
es: {
title: "Mi Herramienta - LastBrain Tools",
description: "Meta descripción optimizada (150-160 caracteres)",
// ... même structure en espagnol
},
};tool.client.tsx - Composant interactif (option A)
"use client";
import { useState } from "react";
import { Button, Card } from "@lastbrain/ui";
import type { Language } from "../_registry/types";
import { translations } from "./i18n";
interface ToolClientProps {
lang: Language;
}
export default function MonOutilClient({ lang }: ToolClientProps) {
const [result, setResult] = useState("");
const t = translations[lang].labels;
const handleAction = () => {
// Votre logique métier ici
setResult("Résultat de l'opération");
};
return (
<Card className="p-6">
<div className="space-y-4">
<Button onClick={handleAction} color="primary">
{t.uploadButton}
</Button>
{result && (
<div className="p-4 bg-success-50 rounded-lg">
<p className="text-success-700">{result}</p>
</div>
)}
</div>
</Card>
);
}tool.server.tsx - Composant statique (option B)
import { Card } from "@lastbrain/ui";
import type { Language } from "../_registry/types";
import { translations } from "./i18n";
interface ToolServerProps {
lang: Language;
}
export default function MonOutilServer({ lang }: ToolServerProps) {
const t = translations[lang].labels;
return (
<Card className="p-6">
<h2 className="text-xl font-bold mb-4">{translations[lang].title}</h2>
<p className="text-default-600">{translations[lang].description}</p>
{/* Votre contenu statique ici */}
<div className="mt-6">
<p>Exemple de contenu...</p>
</div>
</Card>
);
}3️⃣ Déclarer dans le registry
Ouvrir _registry/tools.registry.ts et ajouter 1 ligne :
const TOOL_MODULES = {
"png-to-webp": () => import("../png-to-webp"),
"career-timeline": () => import("../career-timeline"),
"mon-outil": () => import("../mon-outil"), // ✅ Ajoutez cette ligne
} as const;C'est tout ! Le reste est automatique.
4️⃣ Rebuild
cd apps/lastbrain
pnpm build:modulesVotre outil est maintenant disponible sur :
/tools(dans la liste)/tools/mon-outil(page détail)
🏗️ Architecture technique
Registry Pattern
Le système utilise un registry avec imports dynamiques :
// _registry/tools.registry.ts
const TOOL_MODULES = {
"png-to-webp": () => import("../png-to-webp"),
// ... lazy loading de chaque outil
} as const;
// Détection automatique des slugs disponibles
export type ToolSlug = keyof typeof TOOL_MODULES;
// Chargement à la demande
export async function getToolDefinition(slug: string) {
const loader = TOOL_MODULES[slug];
const module = await loader();
return module.getToolDefinition();
}Layouts Responsive
Deux layouts automatiques selon le device :
Desktop (ToolLayoutDesktop.tsx)
┌─────────────────────────────────────────┐
│ Aside (related tools) │ Main Content │
│ │ │
│ - Tool 1 │ H1 + Desc │
│ - Tool 2 │ [Tool UI] │
│ - Tool 3 │ │
│ │ SEO Sections: │
│ │ • Pourquoi ? │
│ │ • Cas d'usage │
│ │ • Comment ? │
│ │ • FAQ │
└────────────────────────────────────────┘Mobile (ToolLayoutMobile.tsx)
┌───────────────────┐
│ H1 + Description │
├───────────────────┤
│ [Tool UI] │
├───────────────────┤
│ 📖 À propos ▼ │ (Accordion)
├───────────────────┤
│ 💡 Conseils ▼ │ (Accordion)
├───────────────────┤
│ ❓ FAQ ▼ │ (Accordion)
└───────────────────┘Pages générées
/tools - Liste (page.tsx)
- Server Component
generateMetadata()pour SEO- Filtre par catégorie
- Grille de cards responsive
/tools/[slug] - Détail ([slug]/page.tsx)
- Server Component
generateStaticParams()pour SSGgenerateMetadata()dynamique- Layout Desktop/Mobile conditionnel
📊 Types TypeScript
ToolDefinition
interface ToolDefinition {
slug: string;
category: "image" | "development" | "design" | "productivity";
status: "active" | "beta" | "coming-soon";
isInteractive: boolean; // Client vs Server Component
featured: boolean;
order: number;
i18n: ToolI18n;
seo: ToolSEO;
relatedTools?: string[];
}ToolI18n
interface ToolI18n {
fr: { title: string; description: string; labels: Record<string, string> };
en: { title: string; description: string; labels: Record<string, string> };
es: { title: string; description: string; labels: Record<string, string> };
}ToolSEO
interface ToolSEO {
[lang: string]: {
title: string;
description: string;
h1: string;
keywords: string[];
sections: {
why: { title: string; content: string };
useCases: { title: string; items: string[] };
howTo?: { title: string; steps: string[] };
faq?: Array<{ question: string; answer: string }>;
};
};
}🚀 Déploiement Vercel
Cette architecture est 100% compatible Vercel :
✅ Server Components : Standard Next.js 15
✅ Imports dynamiques : import() géré nativement
✅ generateStaticParams : SSG au build
✅ generateMetadata : SEO optimal
✅ Edge-ready : Pas de dépendance Node.js
Au build, Vercel va :
- Pré-générer toutes les pages
/tools/[slug] - Optimiser les imports dynamiques
- Générer les métadonnées SEO
- Créer un bundle minimal par outil
🎨 Personnalisation
Ajouter une catégorie
Modifier _registry/types.ts :
export type ToolCategory =
| "image"
| "development"
| "design"
| "productivity"
| "ma-nouvelle-categorie"; // ✅ Ajoutez iciModifier le layout
Éditer _components/ToolLayoutDesktop.tsx ou ToolLayoutMobile.tsx :
- Tous les outils héritent automatiquement des changements
- Préserve la cohérence visuelle
Ajouter des icônes
Utiliser lucide-react (déjà installé) :
import { Zap, Star, Heart } from "lucide-react";📝 Checklist ajout outil
- [ ] Copier un dossier existant (
png-to-webpoucareer-timeline) - [ ] Renommer le dossier avec le slug de l'outil (kebab-case)
- [ ] Modifier
index.ts→ changerTOOL_SLUG - [ ] Modifier
i18n.ts→ traduire title, description, labels - [ ] Modifier
seo.ts→ remplir les 3 langues (fr/en/es) - [ ] Modifier
tool.client.tsxoutool.server.tsx→ coder la logique - [ ] Ajouter 1 ligne dans
_registry/tools.registry.ts - [ ] Rebuild :
cd apps/lastbrain && pnpm build:modules - [ ] Tester :
http://localhost:3000/tools/mon-outil
🆘 Troubleshooting
L'outil n'apparaît pas dans la liste
- Vérifier que le slug dans
index.tscorrespond au nom du dossier - Vérifier que l'outil est déclaré dans
tools.registry.ts - Vérifier que
statusest"active"(pas"coming-soon") - Rebuild :
pnpm build:modules
Erreur TypeScript
# Vérifier les types
cd packages/module-tools
pnpm buildL'outil fonctionne en dev mais pas en production
- Vérifier que le composant n'utilise pas de dépendance Node.js
- Pour les Client Components : ajouter
"use client"en haut du fichier - Vérifier les imports : utiliser chemins relatifs corrects
📚 Exemples complets
Référez-vous aux outils existants :
png-to-webp/: Exemple complet d'outil interactif (upload, canvas, download)career-timeline/: Exemple complet d'outil statique (affichage de données)
Ces deux outils contiennent tous les patterns nécessaires pour créer n'importe quel type d'outil.
🎯 En résumé : Copiez un dossier, modifiez 4 fichiers, ajoutez 1 ligne dans le registry, rebuild. C'est tout !
