Le build Vercel échouait car le composant accédait à siteConfig.services
au niveau module (hors fonction), ce qui créait une erreur TypeScript
lors de la phase de collecte des pages.
Corrections :
- Remplace le module-level .map() sur siteConfig.services par un tableau
statique SERVICE_LINKS (même données, mais sans inférence TypeScript complexe)
- Refonte éditoriale complète du composant : hero dark + diagonal panel,
stats border-l orange, services grid dark, texte split 2-col,
zones communes voisines, contact split dark/light
- Suppression des rounded-xl, rounded-2xl → style squared cohérent
- Boutons : .btn .btn-fill / .btn-outline-light / .btn-outline-dark
https://claude.ai/code/session_01Uec4iHjcPwB1pU41idWEdF
Cause du build error :
Le CI avait dans son cache .next des routes compilées d'une ancienne
version du projet (/api/admin/candidatures/[id]/approve) qui instanciait
un SDK Anthropic sans clé API.
Corrections :
- next.config.ts : ajout de cleanDistDir: true pour forcer la suppression
des fichiers .next orphelins à chaque build
- package.json : suppression des dépendances non utilisées héritées de
l'ancienne version (sanity, @sanity/*, stripe, @stripe/*, @supabase/*)
- package-lock.json : supprimé (sera régénéré proprement par npm install)
https://claude.ai/code/session_01Uec4iHjcPwB1pU41idWEdF
Contact :
- Redesign complet split dark/light identique à la homepage
- Infos gauche (dark navy) : téléphone animé, email, adresse, zones, stats
- Formulaire droit (stone-bg) avec labels uppercase et champs squarés
Services :
- Liste éditoriale sur dark navy — layout grid 12 cols numéroté
- Keywords en pills squared, arrow animée, border-top orange
Réalisations :
- Filtres squared (suppression rounded-full)
- Cards avec overlay slide-from-bottom rouge brique
- Badge catégorie orange + CTA bottom dark navy
Partenaires :
- Hero dark + diagonal panel
- Bloc stats 2-col avec border-l orange
- Grid 4-col squared, grayscale → couleur au hover
- CTA dark avec texture
ServicePageLayout (composant partagé) :
- Composant réutilisable pour toutes les pages de service
- Hero dark + diagonal + back link
- Stats border-l-2 orange
- Items grid dark avec service-card-dark
- SEO text 2-col
- Contact split dark/light
Pages mises à jour avec ServicePageLayout :
- renovation/page.tsx
- assainissement/page.tsx
- creation-acces/page.tsx
- demolition/page.tsx
construction-maison/page.tsx :
- Redesign complet en standalone avec le même pattern éditorial
https://claude.ai/code/session_01Uec4iHjcPwB1pU41idWEdF
Complete transformation of the Next.js project into a professional
showcase site for OBC Maçonnerie (Benoît Colin, maçon in Nord 59).
Key changes:
- Remove all HookLab/Sanity/Supabase/Stripe/admin/training infrastructure
- Full OBC Maçonnerie identity: logo, colors, contact info, SIREN
- Schema.org LocalBusiness structured data for Benoît Colin
- SEO metadata for all pages targeting Nord 59 keywords
New pages created (23 total):
- Home page with 10 sections (hero, services, pillars, partners,
zone, realisations, testimonials, FAQ, contact form, footer)
- Service pages: construction-maison, renovation, assainissement,
creation-acces, demolition, services
- Secondary pages: realisations, partenaires, contact
- Blog: listing + 6 SEO articles with static content
- 8 local SEO pages: Orchies, Douai, Valenciennes, Mouchin,
Flines-lès-Raches, Saint-Amand-les-Eaux
- Legal pages: mentions-legales, cgv, confidentialite (OBC adapted)
Components:
- Navbar with OBC branding + mobile menu
- Footer with dark navy theme, services + navigation links
- ContactForm client component (devis request)
- LocalSEOPage reusable component for local SEO pages
- CookieBanner updated with OBC cookie key
Config:
- layout.tsx: OBC metadata, Schema.org, no Sanity CDN
- globals.css: stone color variables added
- next.config.ts: removed Sanity CDN remotePatterns
- sitemap.ts: all 30 OBC pages
- robots.ts: allow all except /api/
- api/contact/route.ts: OBC devis email template
https://claude.ai/code/session_01Uec4iHjcPwB1pU41idWEdF
- Remplacer hooklab.fr par hooklab.eu partout (domaine réel du site)
- Ajouter instructions hosts file (Mac/Windows) pour tester WordPress
sans toucher au DNS Vercel existant
- Ajouter note Phase 6 : DNS à faire en dernier, Vercel reste intact
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
- SSH : corriger root@ → ubuntu@ (user par défaut OVH Ubuntu)
- Phase 2 : marquer installation WordOps comme faite
- Mettre à jour la commande : --wpsubdir → --wpsubdomain (mode réel)
- Mettre à jour la structure des sous-sites en sous-domaines
- Ajouter note DNS wildcard *.hooklab.fr
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
- Corriger Ubuntu 24 → Ubuntu 22.04
- Marquer phases 1.1, 1.3, 1.4 comme faites (WordOps installé)
- Ajouter 4 cas de dépannage pour l'erreur SSH "Permission denied"
- Simplifier Phase 2 avec les commandes WordOps (2 lignes au lieu de 4 étapes manuelles)
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
Replace "Chacun son métier" with "Mon rôle ? Faire savoir que vous êtes le meilleur."
New body text + 3 solution cards (touristes, concurrents, zéro jargon) with updated engagement block.
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
Replace all NextResponse.redirect() calls with a proxyImage() helper that
fetches the upstream URL server-side and streams the response body directly.
This eliminates:
- Redirect chains (API → Supabase signed URL → S3/CDN)
- Overly long redirect URLs (Supabase JWT tokens)
- Potential empty/invalid redirect targets
Also adds X-Robots-Tag: noindex, nofollow on all responses from this
technical route to prevent Google from crawling it directly.
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
- Installe sharp pour le traitement d'image côté serveur
- Conversion automatique de tout upload en WebP (meilleur ratio qualité/web)
- Auto-rotation basée sur l'orientation EXIF (corrige les photos de téléphone)
- Strip de toutes les métadonnées personnelles (GPS, appareil, EXIF)
- Compression adaptative par paliers (q82 → q72 → q62 → q50) pour viser ≤ 1 Mo
- Augmentation de la limite brute à 20 Mo (avant optimisation)
- Métadonnées Supabase Storage : Content-Type + Cache-Control 1 an
- UI : stats d'optimisation affichées après upload (ex: "2400 Ko → 680 Ko (WebP q82)")
- Mise à jour du texte d'aide dans l'admin images
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
1. MIME spoofing (upload) — app/api/admin/upload/route.ts
- Ajout de la validation par magic bytes : lit les premiers octets du
fichier et vérifie la signature binaire réelle (JPEG FF D8 FF,
PNG 89 50 4E 47, GIF 47 49 46 38, WebP RIFF+WEBP, AVIF ftyp box)
- Extension dérivée exclusivement du MIME validé côté serveur
(MIME_TO_EXT), jamais du nom de fichier fourni par le client
- Un fichier .exe renommé en .jpg est désormais rejeté
2. Générateur de mot de passe non-cryptographique — stripe/webhook/route.ts
- Remplace Math.random() (non-déterministe mais prévisible) par
crypto.getRandomValues() (CSPRNG, conforme Web Crypto API)
3. Headers HTTP de sécurité manquants — middleware.ts (nouveau)
- X-Content-Type-Options: nosniff (anti MIME-sniffing navigateur)
- X-Frame-Options: SAMEORIGIN (anti clickjacking)
- Referrer-Policy: strict-origin-when-cross-origin
- Permissions-Policy: désactive camera, micro, geolocation
- Content-Security-Policy: whitelist stricte par type de ressource
(scripts, styles, images Unsplash/Supabase/Sanity, connect Supabase/Stripe,
frames Stripe uniquement, object-src none, form-action self)
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
Le typage Supabase inférait "never" sur data car site_images n'est pas
dans le schéma TypeScript généré. Cast de res.data après le await au
lieu du cast global sur .single().
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
Cause racine : les Signed URLs Supabase étaient générées au moment du
rendu ISR et embarquées dans le HTML mis en cache. Si Vercel servait
le HTML avant régénération ou si la signed URL expirait, l'image
était cassée et necessitait plusieurs rechargements.
Solution — proxy /api/img/[key] :
- app/api/img/[key]/route.ts : nouvelle route publique force-dynamic
qui génère une Signed URL fraîche à chaque requête image (TTL 1h),
avec Cache-Control 55 min côté navigateur/CDN ; fallback sur l'image
Unsplash par défaut si createSignedUrl échoue ; 302 direct pour les
URLs externes
- lib/site-images.ts : getSiteImages() ne génère plus jamais de Signed
URL — les chemins storage: retournent /api/img/<key> (URL permanente),
les URLs externes sont retournées telles quelles
Résultat : le HTML statique/ISR ne contient plus jamais de signed URL
éphémère → zéro image cassée, zéro rechargement nécessaire.
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
- lib/site-images.ts : nouvelle clé macon_photo_cyprien (url vide par
défaut, l'admin upload la vraie photo)
- app/macon/page.tsx : remplace le placeholder SVG par un <img> conditionnel
— affiche l'image quand la clé est renseignée, conserve le placeholder
texte "Photo de Cyprien (sur le chantier)" tant qu'aucune photo n'est
uploadée
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
- app/macon/page.tsx + app/paysagiste/page.tsx : ajout de
export const revalidate = 60 pour activer l'ISR (les pages étaient
générées statiquement à la build, getSiteImages() n'était jamais
rappelé entre deux déploiements)
- app/api/admin/site-images/route.ts : appel de revalidatePath() après
chaque PUT réussi pour purger immédiatement le cache de la page
concernée (macon_, paysagiste_ → leur page démo, sinon → /)
Résultat : la page se met à jour dans la seconde qui suit la sauvegarde
dans l'admin, sans attendre le délai de 60s
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
- Nouvelle route POST /api/admin/upload : upload multipart vers le bucket
private-gallery, validation MIME + taille (max 5 Mo), retourne storage:path
- lib/site-images.ts : détecte le préfixe "storage:" et génère une Signed
URL temporaire (60 min) côté serveur avant chaque rendu de page
- GET /api/admin/site-images : résout aussi les signed URLs pour les previews
admin (champ previewUrl distinct de url brute)
- PUT /api/admin/site-images : accepte désormais les chemins "storage:..."
en plus des URLs externes
- Page admin images : drag & drop + input file avec upload automatique +
sauvegarde en BDD, badge "bucket privé", instructions SQL pour créer
la table et la policy du bucket private-gallery
https://claude.ai/code/session_01PzA98VhLMmsHpzs7gnLHGs
- login, register, candidature, cgv, confidentialite, mentions-legales:
add bg-dark class so white text is visible on dark background
- admin shell + protected layout: same fix for admin/dashboard pages
- CookieBanner: update styling to match navy/orange branding, add RGPD
compliance text, include Accepter/Refuser buttons, link to confidentialite
- layout.tsx: import and render CookieBanner globally
https://claude.ai/code/session_01V8YAjpqRQ3bfBYsABYsEgo