Guide Complet shadcn/ui avec Next.js : Construire une Interface Moderne

Introduction
shadcn/ui est devenu la référence incontournable pour construire des interfaces modernes avec React et Next.js. Contrairement aux bibliothèques de composants traditionnelles comme Material UI ou Chakra UI, shadcn/ui adopte une approche radicalement différente : au lieu d'installer un package npm, vous copiez les composants directement dans votre projet. Cela vous donne un contrôle total sur le code, le style et le comportement de chaque composant.
Dans ce tutoriel, vous allez apprendre à mettre en place shadcn/ui dans un projet Next.js, personnaliser le thème, construire des composants complexes comme des formulaires et des tableaux de données, et appliquer les bonnes pratiques pour créer une application de qualité professionnelle.
Ce que vous allez apprendre
- Installer et configurer shadcn/ui dans un projet Next.js
- Utiliser les composants les plus courants (Button, Card, Dialog, Form)
- Personnaliser le thème avec des variables CSS
- Construire un formulaire complet avec validation via React Hook Form et Zod
- Créer un tableau de données interactif avec tri et filtrage
- Implémenter le mode sombre (dark mode)
- Organiser vos composants de manière maintenable
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 18+ installé sur votre machine
- Des connaissances de base en React et TypeScript
- Une familiarité avec Tailwind CSS
- Un éditeur de code (VS Code recommandé)
Étape 1 : Créer le projet Next.js
Commencez par créer un nouveau projet Next.js avec TypeScript et Tailwind CSS :
npx create-next-app@latest mon-app-shadcn --typescript --tailwind --eslint --app --src-dir
cd mon-app-shadcnSélectionnez les options suivantes lors de la configuration :
- Would you like to use App Router? Yes
- Would you like to customize the default import alias? Yes, utilisez
@/*
Étape 2 : Installer shadcn/ui
Exécutez la commande d'initialisation de shadcn/ui :
npx shadcn@latest initL'assistant de configuration vous posera plusieurs questions :
Which style would you like to use? › New York
Which color would you like to use as base color? › Neutral
Do you want to use CSS variables for colors? › yes
Cette commande va :
- Créer un fichier
components.jsonà la racine du projet - Ajouter les utilitaires nécessaires dans
lib/utils.ts - Configurer les variables CSS dans votre fichier
globals.css - Mettre à jour
tailwind.config.tsavec les chemins des composants
Vérifions le fichier components.json généré :
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}Étape 3 : Ajouter vos premiers composants
shadcn/ui propose une CLI pour ajouter des composants individuellement. Ajoutons les composants essentiels :
npx shadcn@latest add button card input labelChaque composant est copié dans src/components/ui/. Examinons le composant Button :
// src/components/ui/button.tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }Le pattern est clair : chaque composant utilise cva (Class Variance Authority) pour gérer les variantes et cn (un wrapper autour de clsx et tailwind-merge) pour fusionner les classes CSS.
Étape 4 : Créer une page avec les composants
Modifions la page d'accueil pour utiliser nos composants :
// src/app/page.tsx
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
export default function Home() {
return (
<main className="container mx-auto py-10">
<h1 className="text-4xl font-bold mb-8">
Mon Application shadcn/ui
</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<CardHeader>
<CardTitle>Composants Accessibles</CardTitle>
<CardDescription>
Basés sur Radix UI, tous les composants sont
accessibles par défaut.
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Navigation au clavier, lecteurs d'écran, et
conformité ARIA intégrés.
</p>
</CardContent>
<CardFooter>
<Button>En savoir plus</Button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>Personnalisable</CardTitle>
<CardDescription>
Le code est dans votre projet. Modifiez tout
selon vos besoins.
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Variables CSS, Tailwind, et composants
modifiables à volonté.
</p>
</CardContent>
<CardFooter>
<Button variant="outline">Explorer</Button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle>TypeScript Natif</CardTitle>
<CardDescription>
Types stricts et autocomplétion pour une
expérience développeur optimale.
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Chaque composant est entièrement typé avec
des props bien définies.
</p>
</CardContent>
<CardFooter>
<Button variant="secondary">Découvrir</Button>
</CardFooter>
</Card>
</div>
</main>
)
}Lancez le serveur de développement pour voir le résultat :
npm run devÉtape 5 : Personnaliser le thème
Le système de theming de shadcn/ui repose sur des variables CSS. Ouvrez src/app/globals.css pour voir les variables générées :
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
/* ... autres variables sombres */
}
}Pour créer un thème personnalisé, modifiez ces variables. Par exemple, pour un thème bleu professionnel :
:root {
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--radius: 0.75rem;
}Vous pouvez aussi utiliser le générateur de thèmes en ligne de shadcn/ui pour créer visuellement votre palette de couleurs et copier les variables CSS générées.
Étape 6 : Formulaire avec validation
L'un des cas d'usage les plus courants est la création de formulaires. shadcn/ui s'intègre parfaitement avec React Hook Form et Zod pour la validation.
Installez les dépendances nécessaires :
npx shadcn@latest add form select textarea toast
npm install zodCréez un formulaire de contact complet :
// src/components/contact-form.tsx
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { useToast } from "@/hooks/use-toast"
const formSchema = z.object({
nom: z
.string()
.min(2, "Le nom doit contenir au moins 2 caractères"),
email: z
.string()
.email("Adresse email invalide"),
sujet: z
.string()
.min(1, "Veuillez sélectionner un sujet"),
message: z
.string()
.min(10, "Le message doit contenir au moins 10 caractères")
.max(500, "Le message ne doit pas dépasser 500 caractères"),
})
type FormValues = z.infer<typeof formSchema>
export function ContactForm() {
const { toast } = useToast()
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
nom: "",
email: "",
sujet: "",
message: "",
},
})
function onSubmit(values: FormValues) {
console.log(values)
toast({
title: "Message envoyé !",
description: "Nous vous répondrons dans les 24 heures.",
})
form.reset()
}
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6"
>
<FormField
control={form.control}
name="nom"
render={({ field }) => (
<FormItem>
<FormLabel>Nom complet</FormLabel>
<FormControl>
<Input placeholder="Jean Dupont" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="jean@exemple.fr"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="sujet"
render={({ field }) => (
<FormItem>
<FormLabel>Sujet</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Choisir un sujet" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="general">
Question générale
</SelectItem>
<SelectItem value="support">
Support technique
</SelectItem>
<SelectItem value="commercial">
Demande commerciale
</SelectItem>
<SelectItem value="partenariat">
Partenariat
</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="message"
render={({ field }) => (
<FormItem>
<FormLabel>Message</FormLabel>
<FormControl>
<Textarea
placeholder="Décrivez votre demande..."
className="min-h-[120px]"
{...field}
/>
</FormControl>
<FormDescription>
Entre 10 et 500 caractères.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
Envoyer le message
</Button>
</form>
</Form>
)
}Ce formulaire offre :
- Validation côté client avec des messages d'erreur en français
- Types TypeScript automatiquement inférés depuis le schéma Zod
- Accessibilité : labels associés, messages d'erreur liés aux champs
- Feedback utilisateur via les notifications toast
Étape 7 : Tableau de données interactif
Les tableaux de données sont un autre composant fréquemment utilisé. shadcn/ui fournit un composant Table qui s'intègre avec TanStack Table pour le tri, le filtrage et la pagination.
npx shadcn@latest add table badge dropdown-menu
npm install @tanstack/react-tableCréez un tableau de données pour afficher une liste d'utilisateurs :
// src/components/users-table.tsx
"use client"
import { useState } from "react"
import {
ColumnDef,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getSortedRowModel,
SortingState,
useReactTable,
} from "@tanstack/react-table"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { ArrowUpDown } from "lucide-react"
type User = {
id: string
nom: string
email: string
role: "admin" | "editeur" | "lecteur"
statut: "actif" | "inactif"
}
const data: User[] = [
{
id: "1",
nom: "Alice Martin",
email: "alice@exemple.fr",
role: "admin",
statut: "actif",
},
{
id: "2",
nom: "Bob Dupont",
email: "bob@exemple.fr",
role: "editeur",
statut: "actif",
},
{
id: "3",
nom: "Claire Bernard",
email: "claire@exemple.fr",
role: "lecteur",
statut: "inactif",
},
]
const columns: ColumnDef<User>[] = [
{
accessorKey: "nom",
header: ({ column }) => (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
Nom
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
),
},
{
accessorKey: "email",
header: "Email",
},
{
accessorKey: "role",
header: "Rôle",
cell: ({ row }) => {
const role = row.getValue("role") as string
const variant =
role === "admin"
? "default"
: role === "editeur"
? "secondary"
: "outline"
return <Badge variant={variant}>{role}</Badge>
},
},
{
accessorKey: "statut",
header: "Statut",
cell: ({ row }) => {
const statut = row.getValue("statut") as string
return (
<Badge
variant={statut === "actif" ? "default" : "destructive"}
>
{statut}
</Badge>
)
},
},
]
export function UsersTable() {
const [sorting, setSorting] = useState<SortingState>([])
const [globalFilter, setGlobalFilter] = useState("")
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter,
state: { sorting, globalFilter },
})
return (
<div className="space-y-4">
<Input
placeholder="Rechercher un utilisateur..."
value={globalFilter}
onChange={(e) => setGlobalFilter(e.target.value)}
className="max-w-sm"
/>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.length > 0 ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
Aucun résultat.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
)
}Étape 8 : Implémenter le mode sombre
shadcn/ui supporte nativement le mode sombre grâce aux variables CSS. Installez next-themes pour gérer le basculement :
npm install next-themes
npx shadcn@latest add dropdown-menuCréez un provider de thème :
// src/components/theme-provider.tsx
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
return (
<NextThemesProvider {...props}>
{children}
</NextThemesProvider>
)
}Ajoutez-le au layout racine :
// src/app/layout.tsx
import { ThemeProvider } from "@/components/theme-provider"
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="fr" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
)
}Créez un bouton de basculement de thème :
// src/components/theme-toggle.tsx
"use client"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function ThemeToggle() {
const { setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Changer le thème</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Clair
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Sombre
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
Système
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}Étape 9 : Composants Dialog et Sheet
Les modales et panneaux latéraux sont essentiels dans les applications modernes :
npx shadcn@latest add dialog sheetExemple de dialogue de confirmation :
// src/components/confirm-dialog.tsx
"use client"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
interface ConfirmDialogProps {
titre: string
description: string
onConfirm: () => void
children: React.ReactNode
}
export function ConfirmDialog({
titre,
description,
onConfirm,
children,
}: ConfirmDialogProps) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>
{children}
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{titre}</AlertDialogTitle>
<AlertDialogDescription>
{description}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Annuler</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm}>
Confirmer
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}Étape 10 : Organiser les composants
Pour maintenir un projet propre, adoptez cette structure :
src/
├── components/
│ ├── ui/ # Composants shadcn/ui (générés)
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── dialog.tsx
│ │ └── ...
│ ├── forms/ # Formulaires composés
│ │ ├── contact-form.tsx
│ │ └── login-form.tsx
│ ├── tables/ # Tableaux de données
│ │ └── users-table.tsx
│ ├── layout/ # Navigation, Footer, etc.
│ │ ├── header.tsx
│ │ ├── sidebar.tsx
│ │ └── footer.tsx
│ └── shared/ # Composants réutilisables personnalisés
│ ├── confirm-dialog.tsx
│ └── theme-toggle.tsx
├── hooks/ # Hooks personnalisés
│ └── use-toast.ts
└── lib/
└── utils.ts # Utilitaire cn()
Règles d'organisation :
- Ne modifiez jamais les fichiers dans
ui/pour des changements spécifiques à un cas d'usage. Créez plutôt un wrapper dansshared/ - Préfixez les composants composés avec leur contexte :
ContactForm,UsersTable - Exportez depuis des fichiers index si vous avez beaucoup de composants dans un dossier
Bonnes pratiques
1. Utiliser la fonction cn() systématiquement
// Toujours utiliser cn() pour combiner les classes
import { cn } from "@/lib/utils"
function MonComposant({ className }: { className?: string }) {
return (
<div className={cn("p-4 rounded-lg", className)}>
Contenu
</div>
)
}2. Composition plutôt que configuration
// Préférer la composition
<Card>
<CardHeader>
<CardTitle>Titre</CardTitle>
</CardHeader>
<CardContent>Contenu</CardContent>
</Card>
// Plutôt que des props complexes
// <Card title="Titre" content="Contenu" /> // À éviter3. Accessibilité intégrée
shadcn/ui est construit sur Radix UI, qui gère automatiquement :
- La navigation au clavier
- Les attributs ARIA
- Le piège de focus dans les modales
- Les annonces pour les lecteurs d'écran
Assurez-vous de toujours inclure des labels pour les champs de formulaire et du texte alternatif pour les éléments visuels.
4. Performances avec React Server Components
shadcn/ui fonctionne avec les React Server Components. Les composants qui ne nécessitent pas d'interactivité peuvent rester comme composants serveur :
// Ce composant peut être un Server Component
// Pas besoin de "use client"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
export function StatCard({ titre, valeur }: { titre: string; valeur: string }) {
return (
<Card>
<CardHeader>
<CardTitle className="text-sm font-medium">{titre}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">{valeur}</p>
</CardContent>
</Card>
)
}Dépannage
Le composant ne s'affiche pas correctement
Vérifiez que tailwind.config.ts inclut le chemin des composants :
content: [
"./src/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
],Erreur "Module not found" après ajout d'un composant
Redémarrez le serveur de développement. Parfois les nouveaux fichiers ne sont pas détectés par le hot reload :
# Ctrl+C puis
npm run devLes styles du mode sombre ne fonctionnent pas
Assurez-vous que darkMode: "class" est dans votre tailwind.config.ts et que le ThemeProvider enveloppe votre application.
Prochaines étapes
Maintenant que vous maîtrisez les bases de shadcn/ui avec Next.js, voici comment aller plus loin :
- Explorez plus de composants : shadcn/ui propose plus de 40 composants (Accordion, Calendar, Combobox, Command palette, etc.)
- Créez un Design System : utilisez les variables CSS et les variantes pour créer une identité visuelle cohérente
- Intégrez avec une API : connectez vos formulaires et tableaux à une API backend avec TanStack Query
- Ajoutez des animations : utilisez Framer Motion pour des transitions fluides entre les états
Conclusion
shadcn/ui représente une évolution majeure dans la façon dont nous construisons des interfaces avec React. En vous donnant la propriété complète du code, il élimine les contraintes des bibliothèques de composants traditionnelles tout en offrant une base solide, accessible et personnalisable.
Les points clés à retenir :
- Propriété du code : vous contrôlez chaque aspect de vos composants
- Accessibilité native : Radix UI gère les interactions complexes
- Theming flexible : les variables CSS permettent une personnalisation totale
- Intégration TypeScript : types stricts pour une meilleure expérience développeur
- Écosystème riche : React Hook Form, Zod, TanStack Table s'intègrent naturellement
Avec cette base, vous êtes prêt à construire des applications professionnelles avec une interface utilisateur de haute qualité.
Discutez de votre projet avec nous
Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.
Trouvons les meilleures solutions pour vos besoins.
Articles connexes

Construire une application full-stack en temps réel avec Convex et Next.js 15
Apprenez à construire une application full-stack en temps réel avec Convex et Next.js 15. Ce tutoriel couvre la conception de schémas, les requêtes, les mutations, les abonnements en temps réel, l'authentification et le téléchargement de fichiers — le tout avec une sécurité de types de bout en bout.

Authentifier votre application Next.js 15 avec Auth.js v5 : Email, OAuth et contrôle des rôles
Apprenez à ajouter une authentification prête pour la production à votre application Next.js 15 avec Auth.js v5. Ce guide complet couvre Google OAuth, les identifiants email/mot de passe, les routes protégées, le middleware et le contrôle d'accès basé sur les rôles.

Internationalisation Next.js avec next-intl — Guide complet i18n pour App Router
Construisez une application Next.js entièrement internationalisée avec next-intl. Ce guide complet couvre la configuration App Router, le support RTL, le routage dynamique, la pluralisation et les patterns de production.