🇬🇧 English version | 🇫🇷 Version française Table of Contents
LaunchKit-Better is a high-performance SaaS foundation designed for developers building multi-tenant applications (B2B, B2C, or internal tools). It provides a full-featured workspace environment with advanced permission management, localized interfaces, and a premium developer experience.
- Multi-tenancy First: Every piece of data is isolated within an organization context.
- Type-Safety Everywhere: From your database schema to your UI components, powered by TypeScript.
- Base UI Foundation: We use Base UI (formerly Radix UI rival) primitives to ensure perfect accessibility and styling freedom.
- Modern Performance: Built on React 19 and the Nitro v3 server engine.
When maintaining this project, please follow these strict rules for updating dependencies:
- TanStack Start & Nitro v3: The
nitrodependency is currently pinned to abetachannel (3.0.x-beta) to support Start. Do not blindly runpnpm updateon@tanstack/react-start,@tanstack/react-router, ornitro. These packages are tightly coupled, and version mismatches can easily crash the SSR server. - Better Auth (v1.6+): If you update
better-auth, always check their changelog. Updates often introduce new columns to the database schema (especially for theorganizationplugin). You must manually verify and update yourdb/schema.tsto match their core schema requirements before pushing changes. - React 19: This boilerplate relies heavily on React 19 features (Server Functions, Actions,
usehook). Do not install legacy UI libraries that strictly require React 18, and never downgrade the corereactpackages.
This project is built on Nitro v3, the most advanced web server engine for the modern web.
- Universal Deployment: Run your app anywhere (Vercel, Cloudflare Workers, Node.js, AWS, Netlify) with a single build.
- Extreme Performance: Ultra-fast cold starts and a minimal footprint optimized for the edge.
- Integrated Server Functions: Seamlessly bridge your frontend and backend with type-safe server handlers.
- Dynamic Caching: Advanced caching layers to keep your SaaS responsive and scalable.
db/: Database core.schema.ts: Single source of truth for tables and RBAC relationships.index.ts: Optimized connection setup usingpostgres.js.
lib/: Core infrastructure.auth.ts: Better Auth configuration (Roles: Member, Admin, Owner).auth-client.ts: Browser client with organization support.
src/components/: Modular UI components.dashboard/: Sidebar (compact), Navbar, Breadcrumbs.settings/: New Tab-based settings (Account, Security, Appearance).ui/: Base UI primitives.
src/routes/: File-based routing (TanStack Router)._auth/: Public routes (Login, Signup, Verification)._app/: Secure root for organizations and settings.$slug/: Context-aware organization workspace.
src/i18n/: Full internationalization with Arabic (RTL), French, Spanish, and Portuguese support.src/hooks/: Custom hooks for theme and font management.
This section explains how to extend LaunchKit-Better by adding new features, database tables, and routes while respecting the core architecture.
LaunchKit handles backend logic using TanStack Start Server Functions (createServerFn). These are type-safe functions that run strictly on the server (Nitro) and use Zod for validation.
import { createServerFn } from '@tanstack/start'
import { z } from 'zod'
import { db } from '@/db'
import { myTable } from '@/db/schema'
export const createItem = createServerFn({ method: 'POST' })
.validator((data: unknown) => z.object({ name: z.string() }).parse(data))
.handler(async ({ data }) => {
// This runs strictly on the server
const newItem = await db.insert(myTable).values({ name: data.name }).returning();
return { item: newItem[0] };
});To add a new table or modify existing ones:
- Modify Schema: Open
db/schema.tsand define your table.export const myTable = pgTable("my_table", { id: text("id").primaryKey(), name: text("name").notNull(), organizationId: text("organization_id").references(() => organization.id), });
- Push Changes: Synchronize with your database immediately:
npx drizzle-kit push
- Explore Data: Use
npx drizzle-kit studioto manage records visually.
Tip
Always include an organizationId in your tables to ensure strict tenant isolation (Multi-tenancy).
LaunchKit uses file-based routing. To create a new page within an organization workspace:
- Create a file:
src/routes/_app/organizations/$slug/my-page.tsx. - Define the route:
import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/_app/organizations/$slug/my-page')({ component: MyPage, loader: async ({ context }) => { // Seed the TanStack Query cache for SSR await context.queryClient.ensureQueryData(myQueryOptions(context.params.slug)); } })
To ensure high performance and SEO-friendly pages, follow these rules:
- Seed the Cache: Always use
queryClient.ensureQueryDatain the routeloader. This avoids extra network requests on the client during hydration. - Internal SSR Fetching: Never
fetchyour own API over the network during SSR. Use direct server function calls or relative URLs through Nitro's internal bridge. - Route Protection: Use
beforeLoadin your routes to verify sessions and roles before any rendering occurs.
- Stable Keys: Never use array indexes as keys. Always use unique database IDs (e.g.,
key={item.id}). - Form State Reset: When a user switches organizations, reset local state by using the organization ID as a
keyon the main container (e.g.,<div key={org.id}>). - Derived State Pattern: For media (avatars/logos), use a derived state to avoid UI flickering:
const [uploadedImg, setUploadedImg] = useState<string | undefined>() const currentImg = uploadedImg || defaultValue // defaultValue from SSR/Query
LaunchKit-Better implements a multi-layered testing strategy to ensure reliability across the entire stack.
Used for testing server functions, hooks, and UI components in isolation.
- Commands:
- Run all tests:
pnpm test - Watch mode:
pnpm test:watch - UI Mode:
npx vitest --ui
- Run all tests:
- Setup: Powered by Vitest and React Testing Library with a JSDOM environment.
- Location: Files ending in
.test.tsor.test.tsxlocated within thesrc/directory.
Used for testing complete user flows in real browsers.
- Commands:
- Run E2E tests:
pnpm test:e2e - Interactive mode:
npx playwright test --ui
- Run E2E tests:
- Location: Scenarios are located in the
e2e/directory.
Monitor your testing progress and identify untested logic paths.
- Run Coverage:
pnpm test:coverage - Setup: Uses the
v8provider. Ensure you have@vitest/coverage-v8installed. - Report: A detailed HTML report is generated in the
coverage/directory after execution.
LaunchKit-Better uses a dynamic, direction-aware typography system.
The application automatically switches between premium fonts based on the document direction:
- LTR (English, French, etc.): Uses Google Sans Flex by default.
- RTL (Arabic): Uses Zain by default.
- Install the font:
pnpm add @fontsource/font-name - Import in
globals.css:@import "@fontsource/font-name"; - Register the variable:
[data-font="my-font"] { --font-family: "My Font", sans-serif; }
- Update the hook: Add the font key to
src/hooks/use-font.ts. - Add to Settings: Update the dropdown in
src/components/settings/account/appearance.tsx.
LaunchKit relies on Better Auth for robust, modern authentication and security.
- Core Authentication: Handles email/password flows, OAuth (Google), and session management securely out of the box.
- Session & Cookie Management: By default, sessions use secure, HTTP-only cookies valid for 7 days. Better Auth automatically handles session refreshing (rolling sessions), ensuring active users stay logged in securely without manual refresh token logic.
- Authentication Flow: When a user logs in, an opaque session token is generated in the database. The browser receives the secure cookie, and every subsequent request is validated by the server. OAuth access/refresh tokens are securely stored in the
accounttable. - Multi-tenancy via Plugins: We use the Better Auth
organizationplugin to seamlessly manage workspaces, meaning tenants and users are intrinsically linked at the auth layer. - Middleware & RBAC: Every request is intercepted by our server middleware. It verifies the session, identifies the active organization context, and strictly enforces Role-Based Access Control (RBAC).
- Extensibility (SSO & OIDC): You can easily add Enterprise Single Sign-On (SSO) or OpenID Connect (OIDC). Simply prompt the AI assistant to use the provided
better-auth-best-practicesandcreate-auth-skillskills to integrate these features in minutes.
Because authentication and organization state are tightly coupled through Better Auth, the application guarantees that users can never access data belonging to a workspace they aren't part of. Permissions are validated on the server before the UI even renders.
LaunchKit uses a granular permission matrix powered by the Better Auth Organization plugin.
| Capability | Member | Admin | Owner |
|---|---|---|---|
| View Dashboard | ✅ | ✅ | ✅ |
| Access Team Management | ❌ | ✅ | ✅ |
| Invite New People | ❌ | ✅ (Up to Admin) | ✅ (Full) |
| Manage Member Roles | ❌ | ✅ (non-owners) | ✅ |
| Workspace Settings | ❌ | ❌ | ✅ |
| Delete Organization | ❌ | ❌ | ✅ |
</Button>
</CardContent>
</Card>
) }
---
<a id="forms"></a>
## 📝 Forms Management (TanStack Form)
LaunchKit-Better uses **TanStack Form** for complex state management and validation. We enforce strict UI composition rules based on Base UI primitives.
### Best Practices for Forms
- **Composition**: Always use `<FieldGroup>` and `<Field>` to structure your forms. Do not use generic `<div>` tags with arbitrary spacing classes.
- **Validation State**: Validation is handled via `data-invalid` and `aria-invalid` attributes.
- **Example**:
```tsx
import { useForm } from '@tanstack/react-form'
import { FieldGroup, Field, FieldLabel, Input, FieldDescription } from '@/components/ui'
export function ProfileForm() {
const form = useForm({ defaultValues: { email: '' } })
return (
<form onSubmit={(e) => { e.preventDefault(); form.handleSubmit() }}>
<FieldGroup>
<form.Field name="email">
{(field) => (
<Field data-invalid={field.state.meta.errors.length > 0}>
<FieldLabel htmlFor={field.name}>Email</FieldLabel>
<Input
id={field.name}
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
aria-invalid={field.state.meta.errors.length > 0}
/>
{field.state.meta.errors.map(err => <FieldDescription key={err}>{err}</FieldDescription>)}
</Field>
)}
</form.Field>
</FieldGroup>
</form>
)
}
Transactional emails (invitations, password resets, verification) are powered by Resend.
- Create an Account: Go to Resend.com and sign up.
- Verify your Domain: In the Resend dashboard, add your domain and configure the provided DNS records (TXT/MX).
- Generate an API Key: Create an API key with sending permissions.
- Environment Setup: Add your key to
.env:RESEND_API_KEY="re_123456789"
- Usage: The logic is pre-configured in
lib/email.ts. Better Auth automatically triggers emails via thesendEmailutility during core authentication flows.
Our UI foundation relies on Base UI primitives composed via a specialized Shadcn CLI.
- Semantic Colors Only: Use tokens like
bg-primaryortext-muted-foreground. Never use hardcoded values likebg-blue-500ordark:bg-slate-900. - Spacing: Avoid
space-y-*orspace-x-*. Instead, useflex flex-col gap-4. - Dimensions: Use
size-*for equal width/height (e.g.,size-10). - Icons: Use the
data-icon="inline-start"property when placing icons inside buttons. - Presets & Customization: You can apply a completely new theme by generating a preset code from ui.shadcn.com and running:
npx shadcn@latest apply --preset <your-preset-code>
LaunchKit-Better uses Supabase Storage for avatars, logos, and gallery images.
To keep your SUPABASE_SERVICE_ROLE_KEY secure, we never perform uploads directly from the client. Instead, we use a dedicated server function:
- Client: The
ImageUploadcomponent sends aFormDatacontaining the file to the server. - Server: The
uploadImagefunction (src/server/storage-fns.ts) receives the file, validates its size/type, and uploads it to Supabase using the service role key. - Response: The server returns the
publicUrlof the uploaded file.
When an avatar or logo is updated, we ensure the UI reflects the change globally:
const { mutate } = useMutation({
onSuccess: () => {
// 1. Invalidate TanStack Query caches
queryClient.invalidateQueries({ queryKey: ['user-orgs'] })
// 2. Invalidate TanStack Router loaders (refreshes session and global state)
router.invalidate()
}
})Pro-tip for Low Latency: In your components, use the Derived State Pattern to avoid flickering:
const [uploadedImg, setUploadedImg] = useState<string | undefined>()
const currentImg = uploadedImg || defaultValue // defaultValue comes from TanStack Query propThis ensures that the cached logo from the previous organization is replaced instantly by the new one from the cache, without waiting for a re-render cycle.
Ensure your .env contains:
VITE_SUPABASE_URL: Your Supabase project URL.SUPABASE_SERVICE_ROLE_KEY: Your service role key (kept on server).- Bucket: Ensure a bucket named
avatarsexists in your Supabase dashboard with public read access.
Run this in your Supabase SQL Editor to initialize the storage:
-- 1. Create the "avatars" bucket
insert into storage.buckets (id, name, public)
values ('avatars', 'avatars', true)
on conflict (id) do nothing;
-- 2. Allow public access to read files
create policy "Public Access"
on storage.objects for select
using ( bucket_id = 'avatars' );
-- 3. Allow service role (server) to manage files (already default, but good to ensure)
-- Note: Our server functions use the Service Role Key, which bypasses RLS.This project is optimized for Vercel using the high-performance Nitro v3 engine.
- Preset Configuration: The build command is pre-configured in
package.json:"build": "NITRO_PRESET=vercel vite build"
- Environment Variables: Ensure the following are set in your Vercel Dashboard:
DATABASE_URL: Your Supabase connection string.BETTER_AUTH_SECRET: Generate viaopenssl rand -base64 32.BETTER_AUTH_URL: Your production URL (e.g.,https://myapp.com).
- Database Sync: Run migrations before/after deployment using:
npx drizzle-kit push
Create a .env file based on .env.example:
DATABASE_URL="postgresql://..."
BETTER_AUTH_SECRET="..."
BETTER_AUTH_URL="http://localhost:3000"
RESEND_API_KEY="re_..."pnpm install
npx drizzle-kit push # Sync schema with DB
pnpm dev # Launch dev server on port 3000LaunchKit-Better is built with AI coding assistants in mind (Antigravity/Gemini, Windsurf, Claude, Cursor, etc.). The repository includes specialized "Skills" in the .agents/skills/ directory that teach the AI exactly how to write code for this specific stack.
When asking your AI assistant to build a feature, explicitly @mention the relevant skill file or ask the AI to read it before coding.
- UI & Components: "Create a new Pricing table component using the guidelines in
@.agents/skills/shadcn/SKILL.md." (This forces the AI to use Base UI,FieldGroup, and semantic colors). - Authentication: "Add a Google SSO login button. Read
@.agents/skills/better-auth-best-practices/SKILL.mdfirst to understand our auth setup." - General Architecture: Always refer the AI to
@AGENTS.md, which acts as the supreme context file for the whole repository.
To provide even deeper context for your AI agent, you can add specialized skills for each technology in our stack:
pnpm dlx skills add shadcn/ui # UI & Base UI components
pnpm dlx skills add tanstack # Router, Query, Start, Form
pnpm dlx skills add drizzle # Database & ORM
pnpm dlx skills add supabase # Storage & Infrastructure
pnpm dlx skills add better-auth # Authentication & OrganizationsPro tip: The more you explicitly reference these markdown skills in your prompt, the less hallucination you'll get, and the code will perfectly match the project's strict typing and composition rules.
LaunchKit-Better est une fondation SaaS haute performance conçue pour les développeurs créant des applications multi-tenant (B2B, B2C ou outils internes). Il offre un environnement de travail complet avec gestion avancée des rôles, interfaces localisées et une expérience développeur premium.
- Multi-tenancy Natif : Chaque donnée est isolée dans le contexte d'une organisation.
- Sûreté Typée : Du schéma de base de données aux composants UI, tout est rigoureusement typé avec TypeScript.
- Base UI Foundation : Utilisation des primitives Base UI pour une accessibilité parfaite et une liberté de design totale.
- Performance Moderne : Propulsé par React 19 et le moteur de serveur Nitro v3.
- 🏗️ Architecture
- 🛠️ Guide de Développement
- 🧪 Stratégie de Tests
- 🌐 i18n & Typographie
- 🔒 Auth & Sécurité
- 👥 Rôles & RBAC
- 📝 Formulaires
- 📧 Emails
- 🎨 Design Système
- 🖼️ Médias & Stockage
- 🚀 DevOps & Déploiement
LaunchKit-Better utilise un système de typographie dynamique qui s'adapte à la direction de lecture.
L'application change automatiquement de police selon la direction du document :
- LTR (Français, Anglais, etc.) : Utilise Google Sans Flex par défaut.
- RTL (Arabe) : Utilise Zain par défaut.
- Installer la police :
pnpm add @fontsource/nom-de-la-police - Importer dans
globals.css:@import "@fontsource/nom-de-la-police"; - Enregistrer la variable :
[data-font="ma-police"] { --font-family: "Ma Police", sans-serif; }
- Mettre à jour le hook : Ajoutez la clé de la police dans
src/hooks/use-font.ts. - Ajouter aux paramètres : Mettez à jour le menu déroulant dans
src/components/settings/account/appearance.tsx.
LaunchKit s'appuie sur Better Auth pour une authentification robuste et moderne.
- Authentification Centrale : Gère nativement les flux e-mail/mot de passe, OAuth (Google), et la sécurité des sessions.
- Gestion des Sessions & Cookies : Les sessions utilisent des cookies sécurisés (HTTP-only) valides pour 7 jours par défaut. Better Auth gère automatiquement le rafraîchissement des sessions (rolling sessions) pour maintenir les utilisateurs actifs connectés en toute sécurité.
- Le Flux d'Authentification : Lors de la connexion, un token de session opaque est généré en base de données. Le navigateur reçoit le cookie sécurisé, et chaque requête suivante est validée par le serveur. Les tokens d'accès/rafraîchissement OAuth sont stockés en toute sécurité dans la table
account. - Multi-tenancy via Plugins : L'utilisation du plugin
organizationde Better Auth permet de lier intrinsèquement les utilisateurs à leurs espaces de travail dès la couche d'authentification. - Middleware & RBAC : Chaque requête est interceptée par notre middleware serveur. Il vérifie la session, identifie le contexte de l'organisation active et applique strictement le contrôle d'accès basé sur les rôles (RBAC).
- Extensibilité (SSO & OIDC) : Vous pouvez facilement ajouter le Single Sign-On (SSO) d'entreprise ou OpenID Connect (OIDC). Demandez simplement à l'assistant IA d'utiliser les compétences
better-auth-best-practicesetcreate-auth-skillpour intégrer ces fonctionnalités en quelques minutes.
Étant donné que l'authentification et l'état de l'organisation sont étroitement couplés via Better Auth, l'application garantit qu'un utilisateur ne peut jamais accéder aux données d'un espace de travail dont il ne fait pas partie. Les permissions sont validées côté serveur avant même le rendu de l'interface.
LaunchKit utilise une matrice de permissions granulaire via le plugin Organization de Better Auth.
| Capacité | Membre | Admin | Propriétaire |
|---|---|---|---|
| Voir le Dashboard | ✅ | ✅ | ✅ |
| Accéder à l'Équipe | ❌ | ✅ | ✅ |
| Inviter des Membres | ❌ | ✅ (Admin max) | ✅ (Tous) |
| Gérer les Rôles | ❌ | ✅ (Sauf Owner) | ✅ |
| Paramètres Workspace | ❌ | ❌ | ✅ |
| Supprimer l'Organisation | ❌ | ❌ | ✅ |
Cette section explique comment étendre LaunchKit-Better en ajoutant de nouvelles fonctionnalités, des tables de base de données et des routes, tout en respectant l'architecture de base.
LaunchKit gère la logique backend via les TanStack Start Server Functions (createServerFn). Ce sont des fonctions typées qui s'exécutent strictement sur le serveur (Nitro) et utilisent Zod pour la validation.
import { createServerFn } from '@tanstack/start'
import { z } from 'zod'
import { db } from '@/db'
import { maTable } from '@/db/schema'
export const createItem = createServerFn({ method: 'POST' })
.validator((data: unknown) => z.object({ name: z.string() }).parse(data))
.handler(async ({ data }) => {
// S'exécute uniquement sur le serveur
const newItem = await db.insert(maTable).values({ name: data.name }).returning();
return { item: newItem[0] };
});Pour ajouter une nouvelle table ou modifier l'existant :
- Modifier le Schéma : Ouvrez
db/schema.tset définissez votre table.export const maTable = pgTable("ma_table", { id: text("id").primaryKey(), name: text("name").notNull(), organizationId: text("organization_id").references(() => organization.id), });
- Pousser les Changements : Synchronisez immédiatement avec votre base de données :
npx drizzle-kit push
- Explorer les Données : Utilisez
npx drizzle-kit studiopour gérer vos enregistrements visuellement.
Tip
Incluez toujours un organizationId dans vos tables pour garantir une isolation stricte des données (Multi-tenancy).
LaunchKit utilise un routage basé sur les fichiers. Pour créer une nouvelle page dans un espace de travail :
- Créez un fichier :
src/routes/_app/organizations/$slug/ma-page.tsx. - Définissez la route :
import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/_app/organizations/$slug/ma-page')({ component: MaPage, loader: async ({ context }) => { // Alimente le cache TanStack Query pour le SSR await context.queryClient.ensureQueryData(maQueryOptions(context.params.slug)); } })
Pour garantir des performances élevées et des pages optimisées pour le SEO :
- Alimenter le Cache : Utilisez toujours
queryClient.ensureQueryDatadans leloaderde la route. Cela évite des requêtes réseau inutiles sur le client lors de l'hydratation. - Appels SSR Internes : Ne faites jamais de
fetchvers votre propre API via le réseau pendant le SSR. Utilisez des appels directs de fonctions serveur ou des URL relatives via Nitro. - Protection des Routes : Utilisez
beforeLoadpour vérifier les sessions et les rôles avant tout rendu.
- Clés Stables : N'utilisez jamais les index de tableau comme clés. Utilisez toujours des IDs uniques (ex:
key={item.id}). - Réinitialisation d'État : Lorsqu'un utilisateur change d'organisation, réinitialisez l'état local en utilisant l'ID de l'organisation comme
keysur le conteneur principal (ex:<div key={org.id}>). - Pattern d'État Dérivé : Pour les médias, utilisez un état dérivé pour éviter tout scintillement :
const [uploadedImg, setUploadedImg] = useState<string | undefined>() const currentImg = uploadedImg || defaultValue // defaultValue provient du SSR/Query
LaunchKit-Better implémente une stratégie de tests multi-couches pour garantir la fiabilité de l'ensemble de la stack.
Utilisés pour tester les fonctions serveur, les hooks et les composants UI isolés.
- Commandes :
- Lancer les tests :
pnpm test - Mode Watch :
pnpm test:watch - Mode UI :
npx vitest --ui
- Lancer les tests :
- Configuration : Propulsé par Vitest et React Testing Library avec un environnement JSDOM.
- Emplacement : Fichiers se terminant par
.test.tsou.test.tsxdans le dossiersrc/.
Utilisés pour tester les flux utilisateurs complets dans de vrais navigateurs.
- Commandes :
- Lancer les tests E2E :
pnpm test:e2e - Mode Interactif :
npx playwright test --ui
- Lancer les tests E2E :
- Emplacement : Les scénarios sont situés dans le dossier
e2e/.
Suivez l'avancement de vos tests et identifiez les chemins logiques non testés.
- Lancer la Couverture :
pnpm test:coverage - Configuration : Utilise le fournisseur
v8. Assurez-vous que@vitest/coverage-v8est installé. - Rapport : Un rapport HTML détaillé est généré dans le dossier
coverage/après l'exécution.
LaunchKit-Better utilise TanStack Form pour la gestion d'état et la validation. Nous imposons des règles strictes de composition d'interface basées sur Base UI.
- Composition : Utilisez toujours
<FieldGroup>et<Field>pour structurer vos formulaires. N'utilisez pas de<div>avec des classes d'espacement arbitraires. - Validation : Les états d'erreur utilisent les attributs
data-invalidetaria-invalid. - Exemple :
import { useForm } from '@tanstack/react-form' import { FieldGroup, Field, FieldLabel, Input, FieldDescription } from '@/components/ui' export function ProfileForm() { const form = useForm({ defaultValues: { email: '' } }) return ( <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit() }}> <FieldGroup> <form.Field name="email"> {(field) => ( <Field data-invalid={field.state.meta.errors.length > 0}> <FieldLabel htmlFor={field.name}>Email</FieldLabel> <Input id={field.name} value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} aria-invalid={field.state.meta.errors.length > 0} /> {field.state.meta.errors.map(err => <FieldDescription key={err}>{err}</FieldDescription>)} </Field> )} </form.Field> </FieldGroup> </form> ) }
Les e-mails transactionnels (invitations, réinitialisation de mot de passe) sont gérés par Resend.
- Créer un Compte : Inscrivez-vous sur Resend.com.
- Vérifier le Domaine : Ajoutez votre domaine dans le dashboard et configurez vos enregistrements DNS.
- Générer une clé API : Créez une clé avec les droits d'envoi.
- Environnement : Ajoutez-la au fichier
.env:RESEND_API_KEY="re_123456789"
- Utilisation : La logique est centralisée dans
lib/email.ts. Better Auth utilise automatiquementsendEmailpour les flux d'authentification.
Notre fondation UI repose sur Base UI et est gérée via un CLI Shadcn spécialisé.
- Couleurs Sémantiques Uniquement : Utilisez
bg-primaryoutext-muted-foreground. N'utilisez jamais de valeurs brutes commebg-blue-500oudark:bg-slate-900. - Espacement : Évitez
space-y-*ouspace-x-*. Privilégiezflex flex-col gap-4. - Dimensions : Utilisez
size-*pour les largeurs/hauteurs égales (ex:size-10). - Icônes : Utilisez
data-icon="inline-start"pour aligner correctement les icônes dans les boutons. - Presets & Personnalisation : Vous pouvez appliquer un nouveau thème global généré sur ui.shadcn.com en lançant :
npx shadcn@latest apply --preset <votre-code-preset>
LaunchKit-Better utilise Supabase Storage pour les avatars, les logos et la galerie d'images.
Pour garantir la sécurité de votre SUPABASE_SERVICE_ROLE_KEY, nous n'effectuons jamais d'uploads directement depuis le client. Nous utilisons une fonction serveur dédiée :
- Client : Le composant
ImageUploadenvoie unFormDatacontenant le fichier au serveur. - Serveur : La fonction
uploadImage(src/server/storage-fns.ts) reçoit le fichier, valide sa taille/type, et l'upload vers Supabase via la clé service role. - Réponse : Le serveur renvoie la
publicUrldu fichier uploadé.
Lorsqu'un avatar ou un logo est mis à jour, nous forçons l'UI à se rafraîchir partout :
const { mutate } = useMutation({
onSuccess: () => {
// 1. Invalider les caches TanStack Query
queryClient.invalidateQueries({ queryKey: ['user-orgs'] })
// 2. Invalider les loaders TanStack Router (rafraîchit la session et l'état global)
router.invalidate()
}
})Astuce pour une latence zéro : Dans vos composants, utilisez le Pattern d'État Dérivé pour éviter tout scintillement :
const [uploadedImg, setUploadedImg] = useState<string | undefined>()
const currentImg = uploadedImg || defaultValue // defaultValue provient de TanStack QueryCeci garantit que le logo en cache de la nouvelle organisation s'affiche immédiatement, sans attendre un cycle de re-rendu ou un délai d'effet.
Assurez-vous que votre .env contient :
VITE_SUPABASE_URL: L'URL de votre projet Supabase.SUPABASE_SERVICE_ROLE_KEY: Votre clé service role (uniquement côté serveur).- Bucket : Créez un bucket nommé
avatarsdans votre dashboard Supabase avec un accès public en lecture.
Exécutez ceci dans votre éditeur SQL Supabase pour initialiser le stockage :
-- 1. Créer le bucket "avatars"
insert into storage.buckets (id, name, public)
values ('avatars', 'avatars', true)
on conflict (id) do nothing;
-- 2. Autoriser l'accès public en lecture
create policy "Public Access"
on storage.objects for select
using ( bucket_id = 'avatars' );
-- 3. Note : Nos fonctions serveur utilisent la Service Role Key, qui contourne les politiques RLS.Ce projet est optimisé pour Vercel grâce au moteur ultra-performant Nitro v3.
- Configuration du Build : La commande est pré-configurée dans le
package.json:"build": "NITRO_PRESET=vercel vite build"
- Variables d'Environnement : Assurez-vous que les variables suivantes sont définies dans votre tableau de bord Vercel :
DATABASE_URL: Votre chaîne de connexion Supabase.BETTER_AUTH_SECRET: Généré viaopenssl rand -base64 32.BETTER_AUTH_URL: Votre URL de production (ex:https://monapp.com).
- Synchronisation DB : Lancez les synchronisations de schéma avant/après le déploiement :
npx drizzle-kit push
Créez votre .env à partir de .env.example :
DATABASE_URL="postgresql://..."
BETTER_AUTH_SECRET="..."
BETTER_AUTH_URL="http://localhost:3000"
RESEND_API_KEY="re_..."pnpm install
npx drizzle-kit push # Synchronise le schéma avec la base de données
pnpm dev # Lance le serveur de dev (port 3000)LaunchKit-Better est conçu pour être utilisé avec des assistants de codage IA (Antigravity/Gemini, Windsurf, Claude, Cursor, etc.). Le dépôt inclut des "Skills" spécialisés dans le dossier .agents/skills/ qui apprennent à l'IA exactement comment coder pour cette stack technique spécifique.
Lorsque vous demandez à votre assistant IA de créer une fonctionnalité, mentionnez explicitement (@mention) le fichier de skill pertinent ou demandez à l'IA de le lire avant de coder.
- UI & Composants : "Crée un nouveau composant de tableau de prix en suivant les règles de
@.agents/skills/shadcn/SKILL.md." (Cela force l'IA à utiliser Base UI,FieldGroup, et les couleurs sémantiques). - Authentification : "Ajoute un bouton de connexion SSO Google. Lis d'abord
@.agents/skills/better-auth-best-practices/SKILL.mdpour comprendre notre configuration." - Architecture Générale : Renvoyez toujours l'IA vers le fichier
@AGENTS.md, qui agit comme le fichier de contexte suprême pour tout le dépôt.
Pour fournir un contexte encore plus riche à votre agent IA, vous pouvez ajouter des "skills" spécialisés pour chaque technologie de notre stack :
pnpm dlx skills add shadcn/ui # Composants UI & Base UI
pnpm dlx skills add tanstack # Router, Query, Start, Form
pnpm dlx skills add drizzle # Base de données & ORM
pnpm dlx skills add supabase # Stockage & Infrastructure
pnpm dlx skills add better-auth # Authentification & OrganisationsAstuce pro : Plus vous référencez explicitement ces skills markdown dans votre prompt, moins l'IA hallucine, et plus le code généré respectera parfaitement les règles strictes de typage et de composition du projet.
