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
- globals.css: restore missing dark-*, primary, error, warning color tokens
and gradient-text/gradient-bg utility classes so login, admin, mentions-legales,
and confidentialite pages render correctly (white text was invisible on white bg)
- layout.tsx: remove "Candidature Formation" from JSON-LD SiteNavigationElement
- candidature/layout.tsx: remove "Formation" from title/description, add noindex
- robots.ts: disallow /candidature/ from crawlers
- sitemap.ts: remove /candidature from sitemap
https://claude.ai/code/session_01V8YAjpqRQ3bfBYsABYsEgo
- Redesign Hero section with new copy focused on the triptych offering
- Add Process component (replaces System) with zigzag layout for 3 pillars:
Google Maps reviews, managed Facebook, converting website
- Redesign AboutMe with orange background and stats row
- Add admin panel for managing site image URLs (replaces Sanity dependency)
- Create site_images API routes and Supabase-backed storage with defaults
- Update FAQ to reference built-in admin panel
- Add site_images table type to database types
- Pass images prop through homepage components
https://claude.ai/code/session_01V8YAjpqRQ3bfBYsABYsEgo
- Full-screen hero with background photo, dark overlay, centered text
- Two large service cards (Création / Entretien) with bg images and green "En savoir +" buttons
- Services description section with checklist and side image
- "Qui sommes-nous" section with team photo and stats (150+ jardins, 10+ ans)
- Values section on dark forest green (#1a3c1a) with arch-shaped cards
- Contact form section with full-width background image and overlay
- Dark green footer with 3 columns (logo, prestations, contact)
- Transparent nav overlay on hero with glass-morphism logo
- Subtle HookLab CTA badge in bottom-left corner
- Removed old light-bg hero, seasonal banner, and flat card layout
https://claude.ai/code/session_01V8YAjpqRQ3bfBYsABYsEgo
- Add SVG favicon (H logo navy+orange) for all sizes (32, 180, 192, 512)
- Add web manifest (site.webmanifest) for PWA compatibility
- Add theme-color meta tag (#1B2A4A navy)
- Add 3 structured data schemas: LocalBusiness, WebSite, SiteNavigationElement
- WebSite schema enables Google to show site name + search box
- SiteNavigationElement signals main pages for sitelinks display
- Add canonical URLs to all pages (macon, paysagiste, plombier, legal pages)
- Add metadata to pages missing it (mentions-legales, confidentialite, cgv)
- Add candidature/layout.tsx for metadata on client component page
- Optimize sitemap with consistent BASE_URL and candidature/cgv pages
- Add telephone, email, opening hours to LocalBusiness schema
https://claude.ai/code/session_01V8YAjpqRQ3bfBYsABYsEgo
Updated the mentions légales page with improved formatting and corrected capitalization. Added sections for legal information, hosting, intellectual property, and data protection.
- Hero: replace aggressive "Arrêtez de perdre des chantiers" with
professional "Un site à la hauteur de votre savoir-faire"
- Macon: rewrite hero as client-facing (artisan's real site),
add Unsplash before/after photos to MagicReveal sliders
- Paysagiste: rewrite hero "Transformez votre extérieur" instead of
"Ne vendez pas des travaux", add real photos to gallery
- Plombier: rewrite hero "Votre plombier réactif et transparent"
instead of "Convaincre en 3 secondes chrono"
- MagicReveal: now accepts avantImage/apresImage URLs and renders
real photos with the interactive slider
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
- Disable Sanity CDN cache so published changes appear immediately
- Add revalidate=60 to page so Next.js refreshes data every 60s
- Wire AboutMe component to use siteSettings from Sanity (name, bio,
photo, address, map coordinates)
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
Transformation complète du site HookLab de formation TikTok Shop
vers une landing page haute conversion pour agence web locale ciblant
les artisans du bâtiment dans le Nord (Douai, Orchies, Valenciennes).
- Nouveau design system : bleu nuit/orange sur fond clair (mobile-first)
- Hero avec promesse artisan + CTA orange "Réserver mon Audit"
- Section "Le Système" (3 étapes : Trouvé, Choisi, Contacté)
- Portfolio connecté à Sanity.io (fallback data intégré)
- Section "Qui suis-je" avec carte OpenStreetMap interactive
- FAQ orientée artisans avec JSON-LD pour Google
- Formulaire contact audit gratuit
- SEO local : 12 keywords artisans, JSON-LD LocalBusiness
- Sanity.io schemas (portfolio, siteSettings) + client conditionnel
- Accessibilité : skip-to-content, focus-visible, aria-labels
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
- Supprime SocialProofTicker (notifications fake visiblement artificielles)
- Navbar passe de fixed à sticky pour s'empiler correctement sous la bannière
- Bannière d'annonce responsive (texte court sur mobile, bouton close adapté)
- Hero: réduit le padding top (plus besoin de compenser un navbar fixed)
- Exit-intent popup fonctionne sur mobile (trigger au scroll-up après 60% de page)
- Popup responsive: tailles ajustées pour mobile
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
- Email sending now independent from Stripe payment link generation
- Professional dark-themed HTML email template matching HookLab branding
- Return emailSent/emailError/stripeError status in API response
- Admin UI shows detailed status after approve action
- Default to onboarding@resend.dev when no custom domain
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
Allow sending emails without custom domain by defaulting to Resend's
free onboarding address. Set RESEND_FROM_EMAIL env var later when
hooklab.fr domain is purchased and verified.
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
- Wrap useSearchParams() in Suspense boundary (Next.js requirement)
- Read ?redirect= query param for post-login redirect to /admin
- Auto-detect admin users and redirect to /admin
- Fallback to /dashboard for regular users
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
- Add /setup-admin page for first-time admin account creation
- Add /api/admin/setup route (only works when no admin exists)
- Update login to redirect admins to /admin instead of /dashboard
- Setup page creates auth user + profile with is_admin=true
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
- /admin page with secret-key authentication
- List all candidatures with details (expandable cards)
- Approve: updates status + generates Stripe checkout URL + sends email
- Reject: updates status
- Checkout URL displayed on screen for manual copy if Resend not configured
- Protected by ADMIN_SECRET env var
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
- Add auth options (autoRefreshToken: false, persistSession: false) to
createAdminClient so service role key works correctly with supabase-js
- Return actual Supabase error message in candidature API for debugging
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
- Add serverExternalPackages for @supabase/ssr in next.config.ts
- Add export const runtime = 'nodejs' to all pages/routes using Supabase
- Replace createAdminClient to use @supabase/supabase-js directly (no SSR)
- Prevents @supabase/ssr from running in Edge runtime on Vercel
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
Next.js 16.1.6 is not yet fully supported by Vercel's build system,
causing 404 on all routes. Downgraded to Next.js 15.5.12 + React 18,
fixed ESLint config, TypeScript config, and lint errors.
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y
- Remove middleware.ts entirely (caused __dirname ReferenceError in Edge)
- Auth protection handled by dashboard layout.tsx (server-side redirect)
- Move pages out of (marketing) and (auth) route groups to fix 404 on /
- Keep (protected) route group for dashboard/formations/profil shared layout
https://claude.ai/code/session_01H2aRGDaKgarPvhay2HxN6Y