Blog de Cuisine Self-Hosted
Dernière mise à jour le
🍳 Concept
Un blog de cuisine entièrement self-hosted développé avec Next.js 14 et PostgreSQL, déployé en production sur mon homeserver. Le projet permet de créer, partager et rechercher des recettes avec un système d’authentification et d’approbation par admin.
URL de production: http://<SERVER_IP>:PORT | cuisine.
🎯 Objectifs du Projet
- Self-hosting complet: Reprendre contrôle de mes données culinaires
- Apprentissage fullstack: Next.js App Router, PostgreSQL, Docker
- Authentification sécurisée: NextAuth.js avec système de rôles
- Performance: SSR pour SEO + CSR pour interactivité
- Production-ready: Docker Compose, healthchecks, rate limiting
🏗️ Architecture Technique
Stack
Frontend: Next.js 14 (App Router)
Backend: Next.js API Routes
Database: PostgreSQL 15
ORM: Prisma
Auth: NextAuth.js v4
Styling: Tailwind CSS
Deployment: Docker Compose
Reverse Proxy: Nginx Proxy Manager
Infrastructure
Container PostgreSQL (cuisine_db)
- Image:
postgres:15-alpine - Port: 5432 (interne uniquement)
- Volume: Data persistante
- Healthcheck:
pg_isready - Credentials: définis via variables d’environnement (non exposées ici)
Container Next.js (cuisine_app)
- Image custom: Multi-stage build (node:18-bullseye)
- Port: 3000
- Environment: Production optimisé
- Healthcheck:
/api/healthendpoint - Dépend de: PostgreSQL (wait-for-it)
🔐 Système d’Authentification
Workflow d’inscription
-
Utilisateur s’inscrit (
/auth/register)- Email + Password (validation stricte)
- Compte créé avec
approved=false - Cannot login until admin approval
-
Admin approuve (manuellement via DB ou future admin panel)
- Update
approved=true - User peut maintenant se connecter
- Update
-
Login (
/auth/login)- NextAuth Credentials Provider
- Password verification (bcryptjs)
- JWT session créée (30 jours)
- Role inclus dans token
Rôles Implémentés
| Rôle | Permissions |
|---|---|
| USER | Lecture recettes, commentaires (futur) |
| CONTRIBUTOR | Création recettes, modification propres recettes |
| ADMIN | Tous droits, approbation utilisateurs, modération |
Sécurité
- Passwords: Hashés avec bcryptjs (10 rounds)
- Sessions: JWT stockés en cookie httpOnly
- CSRF: Protection NextAuth intégrée
- Rate Limiting: 10 req/min sur auth, 30 req/min sur API
- Validation: Zod schemas côté serveur
📄 Routes API
Authentication
POST /api/auth/register
- Body: { email, password, name }
- Returns: { user } ou error
- Rate limit: 10/min
POST /api/auth/[...nextauth] (login)
- Credentials: { email, password }
- Returns: Session JWT
- Rate limit: 10/min
GET /api/auth/[...nextauth] (session)
- Returns: Current user session
Recipes
GET /api/recipes
- Query: ?search=keyword&category=id&sort=recent|popular
- Returns: Recipe[] avec pagination
- Public
- Rate limit: 30/min
POST /api/recipes
- Body: { title, description, ingredients, steps, categoryId, ... }
- Returns: Created recipe
- Auth: CONTRIBUTOR+
- Rate limit: 30/min
GET /api/recipes/[id]
- Returns: Recipe détaillée + metadata
- Public
User
GET /api/user/me
- Returns: Current user info
- Auth: Required
GET /api/user/profile
- Returns: Full profile + stats
- Auth: Required
Health
GET /api/health
- Returns: { status: "ok" }
- Pas de rate limit
📱 Pages Implémentées
Public
Accueil (/)
- Server-Side Rendering (SSR)
- Affiche 6 recettes les plus récentes
- Hero section avec présentation
- Call-to-action inscription
Listing Recettes (/recipes)
- Client-Side Rendering (CSR) pour interactivité
- Recherche par keyword
- Filtrage par catégorie
- Tri: Récent, Populaire, Alphabétique
- Infinite scroll (futur)
Détail Recette (/recipes/[id] - FUTUR)
- SSR pour SEO optimal
- Ingrédients + Steps structurés
- Metadata (temps, portions, difficulté)
- Commentaires (authenticated users)
Authentification
Inscription (/auth/register)
- Formulaire avec validation temps réel
- Feedback erreurs inline
- Message “En attente d’approbation” après inscription
Connexion (/auth/login)
- Redirect vers dashboard après login
- Formulaire avec gestion erreurs
- Link vers inscription
Authenticated
Dashboard (/dashboard)
- Profil utilisateur
- Statistiques personnelles
- Mes recettes créées (CONTRIBUTOR)
- Settings compte
🗄️ Schema Database (Prisma)
model User {
id String @id @default(cuid())
email String @unique
password String
name String?
role Role @default(USER)
approved Boolean @default(false)
createdAt DateTime @default(now())
recipes Recipe[]
comments Comment[]
}
enum Role {
USER
CONTRIBUTOR
ADMIN
}
model Recipe {
id String @id @default(cuid())
title String
description String
ingredients Json // Array structured
steps Json // Array structured
prepTime Int? // minutes
cookTime Int? // minutes
servings Int?
difficulty String? // EASY, MEDIUM, HARD
imageUrl String?
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId])
categoryId String
category Category @relation(fields: [categoryId])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
comments Comment[]
}
model Category {
id String @id @default(cuid())
name String @unique
slug String @unique
description String?
recipes Recipe[]
}
model Comment {
id String @id @default(cuid())
content String
authorId String
author User @relation(fields: [authorId])
recipeId String
recipe Recipe @relation(fields: [recipeId])
createdAt DateTime @default(now())
}
🎨 Frontend Features
Composants Réutilisables
- Header: Navigation avec auth state
- Footer: Links + social
- RecipeCard: Preview recette avec image
- SearchBar: Input avec debounce
- FilterDropdown: Catégories dynamiques
- AuthForm: Formulaire login/register réutilisable
State Management
- Server State: React Query (TanStack Query) - FUTUR
- Local State: useState pour forms
- Auth State: NextAuth useSession hook
Optimisations
- Image Optimization: Next.js Image component
- Code Splitting: Dynamic imports
- CSS: Tailwind JIT compilation
- Caching: SWR pour data fetching (futur)
🐳 Déploiement Docker
Multi-Stage Build
# Stage 1: Dependencies
FROM node:18-bullseye AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# Stage 2: Builder
FROM node:18-bullseye AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npx prisma generate
RUN npm run build
# Stage 3: Runner
FROM node:18-bullseye AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]
Docker Compose
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: cuisine_db
environment:
POSTGRES_USER: "${POSTGRES_USER}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
POSTGRES_DB: cuisine_db
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:PORT"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
app:
build:
context: .
dockerfile: Dockerfile
container_name: cuisine_app
ports:
- "3000:PORT"
environment:
DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:PORT/cuisine_db"
NEXTAUTH_SECRET: "${NEXTAUTH_SECRET}"
NEXTAUTH_URL: "http://<SERVER_IP>:PORT"
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:PORT/api/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
postgres_data:
📊 État Actuel (70% complet)
✅ Fonctionnalités Implémentées
- Infrastructure Docker (PostgreSQL + Next.js)
- Schema Prisma complet
- Authentification NextAuth (register + login)
- Système de rôles (USER/CONTRIBUTOR/ADMIN)
- API Routes (recipes GET/POST, auth, user)
- Rate limiting
- Pages: Home, Recipes listing, Register, Login, Dashboard
- Recherche et filtrage recettes
- Healthcheck endpoint
- Production deployment
⏳ En Cours / Prévu
- Admin panel pour approuver users
- Upload images recettes
- Page détail recette (
/recipes/[id]) - Système de commentaires
- Favoris/Bookmarks
- Export PDF recettes
- API: PATCH/DELETE recipes
- Pagination API
- Tests unitaires + E2E
- Migration vers React Query
- PWA support
- Multi-langue (i18n)
🚀 Prochaines Étapes
Immédiat
- Admin Panel: Interface pour approuver nouveaux users
- Upload Images: Multer + Sharp pour resize/optimize
- Page Détail: Template complet recette individuelle
Court Terme
- Commentaires: Thread discussions sous recettes
- Favoris: System de bookmarks par user
- Export PDF: Générer PDF recette pour impression
Moyen Terme
- Search Avancée: Elasticsearch pour full-text search
- Recommandations: ML-based similar recipes
- Analytics: Tracking views, popular recipes
🎓 Apprentissages Clés
- Next.js App Router: Migration de Pages vers App Directory
- Prisma ORM: Schema design, relations, migrations
- NextAuth.js: Customisation providers, callbacks, sessions
- Docker Compose: Multi-container orchestration, healthchecks
- Rate Limiting: Middleware custom Next.js
- Security: OWASP best practices (hashing, validation, CSRF)
- DevOps: Production deployment, monitoring, logs
📝 Documentation Technique
Variables d’Environnement
# Database
DATABASE_URL="postgresql://user:pass@host:PORT/db"
# Auth
NEXTAUTH_SECRET="generate-with-openssl-rand-base64-32"
NEXTAUTH_URL="http://<SERVER_IP>:PORT"
# App
NODE_ENV="production"
Scripts npm
npm run dev # Dev server (hot reload)
npm run build # Production build
npm run start # Start production server
npm run lint # ESLint check
npx prisma studio # Database GUI
npx prisma migrate dev # Create migration
Commandes Docker
# Démarrer stack
docker compose up -d
# Rebuild après modifs
docker compose up -d --build
# Logs
docker compose logs -f app
# Accès DB
docker compose exec postgres psql -U cuisine_user -d cuisine_db
# Shutdown
docker compose down
🔗 Ressources
Status: 🟢 En production
Dernière mise à jour: 31 janvier 2025
Prochaine feature: Admin panel pour approbation users