npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-outil

2️⃣ 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 statique

i18n.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:modules

Votre 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 SSG
  • generateMetadata() 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 :

  1. Pré-générer toutes les pages /tools/[slug]
  2. Optimiser les imports dynamiques
  3. Générer les métadonnées SEO
  4. 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 ici

Modifier 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-webp ou career-timeline)
  • [ ] Renommer le dossier avec le slug de l'outil (kebab-case)
  • [ ] Modifier index.ts → changer TOOL_SLUG
  • [ ] Modifier i18n.ts → traduire title, description, labels
  • [ ] Modifier seo.ts → remplir les 3 langues (fr/en/es)
  • [ ] Modifier tool.client.tsx ou tool.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

  1. Vérifier que le slug dans index.ts correspond au nom du dossier
  2. Vérifier que l'outil est déclaré dans tools.registry.ts
  3. Vérifier que status est "active" (pas "coming-soon")
  4. Rebuild : pnpm build:modules

Erreur TypeScript

# Vérifier les types
cd packages/module-tools
pnpm build

L'outil fonctionne en dev mais pas en production

  1. Vérifier que le composant n'utilise pas de dépendance Node.js
  2. Pour les Client Components : ajouter "use client" en haut du fichier
  3. 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 !